Turn LLVM 18 Bitcode into Readable IR with llvm-dis
You will finish with a safe way to turn an LLVM bitcode file into human-readable LLVM IR, choose the output destination deliberately, and confirm whether a failure is a missing file or a bad input. The examples use llvm-dis-18 from Ubuntu package llvm-18, version 1:18.1.3-1ubuntu1.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell, an installed LLVM 18 toolchain, and a bitcode file that you are allowed to read. This command only disassembles bitcode. It does not execute the module, optimise it, or turn it into native machine code. No example needs sudo.
1. Check the installed tool
Start by confirming that the command in your path is the one you expect. This is an ordinary read-only check:
$ command -v llvm-dis-18
/usr/bin/llvm-dis-18
$ llvm-dis-18 --version
Ubuntu LLVM version 18.1.3
Optimized build.
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-18
llvm-18 1:18.1.3-1ubuntu1
The binary is named llvm-dis-18 on this installation. Keep the versioned name in scripts when you need LLVM 18 specifically, rather than silently selecting another llvm-dis from a different toolchain.
Checkpoint: if command -v prints nothing, stop and install or enable the appropriate LLVM package through your normal system-management process. Do not work around a missing executable by downloading an unrelated binary into a project directory.
2. Disassemble a bitcode file to the default .ll file
Give the input path as the final argument:
$ llvm-dis-18 /path/to/module.bc
For a regular file, the default destination is derived from the input name. An existing .bc suffix is removed and .ll is added, so the command above writes /path/to/module.ll. The result is text containing LLVM IR, not a native assembly listing.
That default can be inconvenient in a source tree. It can also replace a file at the destination, so check before running a command that writes beside valuable work:
$ test ! -e /path/to/module.ll && echo 'destination is free'
$ llvm-dis-18 /path/to/module.bc
$ test -s /path/to/module.ll && echo 'LLVM IR written'
Do not use > to redirect over a useful .ll file unless that replacement is intentional. If you have overwritten an output and still need the previous version, recover it from version control or your backup system. There is no undo operation in llvm-dis-18.
3. Choose an output path explicitly
Use -o when the destination matters:
$ llvm-dis-18 /path/to/module.bc -o /tmp/module-inspection.ll
$ test -s /tmp/module-inspection.ll && echo 'inspection file is non-empty'
inspection file is non-empty
The output argument belongs to llvm-dis-18, not to the bitcode module. A path such as /tmp/module-inspection.ll is useful for a temporary investigation because it keeps generated text away from the source directory. Do not put sensitive IR in a shared temporary directory without considering its permissions and lifetime.
If you want the text on standard output, set the output filename to a single hyphen:
$ llvm-dis-18 /path/to/module.bc -o - | less
This is a read-only inspection pipeline from the shell's point of view: llvm-dis-18 does not create an output file. The displayed module may contain source paths, symbol names or other build details, so treat captured output as potentially sensitive.
4. Read bitcode from standard input
Omit the input filename, or pass -, when another command supplies the bitcode. Send the result to standard output if you do not want a file:
$ cat /path/to/module.bc | llvm-dis-18 -o - | less
This is the useful distinction to remember: a named input defaults to a derived .ll file, while standard input defaults to standard output. Making both sides explicit, as in the example, prevents an unexpected file from appearing in the current directory.
For a quick machine-checkable probe, select a known symbol in the resulting text with a tool such as grep:
$ llvm-dis-18 -o - < /path/to/module.bc | grep -F 'define i32 @answer'
define i32 @answer() {
The exact line depends on the module. A missing match means that symbol is not present; it is not proof that disassembly failed. Check the exit status of llvm-dis-18 separately when a pipeline is part of automation.
5. Use help for the installed option set
LLVM tools expose more options than the short manpage needs to describe. Ask this exact binary when you need the local interface:
$ llvm-dis-18 -help
OVERVIEW: llvm .bc -> .ll disassembler
USAGE: llvm-dis-18 [options] [input bitcode]...
The documented options relevant to this workflow are -o for the output filename, -help for a command summary, and -f to enable binary output on terminals. Leave -f alone for normal IR inspection. It is a terminal-safety override for raw bitcode output, not a way to make a source listing clearer.
6. Diagnose a failed run
A successful disassembly exits with status 0. An error exits non-zero:
$ llvm-dis-18 /path/to/missing.bc -o - >/tmp/llvm-dis.stdout 2>/tmp/llvm-dis.stderr
$ printf 'status: %s\n' "$?"
status: 1
$ sed -n '1p' /tmp/llvm-dis.stderr
error: /path/to/missing.bc: No such file or directory
Check the path and read permission before changing anything:
$ ls -l /path/to/module.bc
$ test -r /path/to/module.bc && echo readable
If the file exists but is not valid LLVM bitcode, keep the original untouched and capture the diagnostic in a separate file. Do not rename a native executable, object file or arbitrary archive to .bc; the suffix does not convert its contents. If a producer created textual LLVM IR, use the matching assembler such as llvm-as-18 to make bitcode, then run llvm-dis-18 on that bitcode.
Done means
- You confirmed that
llvm-dis-18is LLVM 18.1.3 from the installedllvm-18package. - You can disassemble a named
.bcfile and predict its default.lldestination. - You use
-owhen an explicit file or standard output is safer. - You know that omitted input reads standard input and that
-o -writes standard output. - You check the exit status and preserve the original bitcode when diagnosing errors.