Assemble and Disassemble x86 with llvm-mc-20

When you need to know exactly what bytes an instruction assembles to, without pulling in a whole compiler, llvm-mc-20 does it in one line. This walkthrough assembles a small x86 source file, inspects the encoding, writes an ELF object, and disassembles raw hex back into instructions. Allow about fifteen minutes. You need the llvm-20 package and a writable working directory: the examples are read-only until a step deliberately creates a file, and none of them need sudo.

1. Check the installed version and target list

Confirm which executable your shell will actually run before you trust its output. This guide follows the installed Ubuntu package version 20.1.8. The manpage calls the tool an LLVM machine code playground, and documents assembly as its default action.

$ command -v llvm-mc-20
/usr/bin/llvm-mc-20
$ llvm-mc-20 --version
Ubuntu LLVM version 20.1.8
...
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-20
llvm-20 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~ubuntu...

The version output also lists the registered targets. Keep that list in mind when you pick a triple. A target is not cosmetic, it decides which assembly syntax and instruction set will even parse.

2. Assemble source and show its encoding

Feed assembly on standard input and select x86-64 explicitly. The percent signs have to reach LLVM unchanged, so the command below uses a single-quoted format string with doubled percent signs for printf.

$ printf 'movl %%eax, %%ebx\n' | llvm-mc-20 --triple=x86_64 --show-encoding
        movl    %eax, %ebx                      # encoding: [0x89,0xc3]

This runs the default --assemble action and prints the assembly back out with its byte encoding as a comment. Nothing is saved to disk. Treat the encoding as a handy inspection result, not as an object file.

Checkpoint: rerun the command and confirm it exits successfully and reports [0x89,0xc3]. An error near %eax usually means the shell ate a percent sign. An unknown-instruction error means the target triple or dialect does not match what you typed.

3. Write a relocatable object

To create a native object, add --filetype=obj and point -o at a destination. The file type has three documented values: asm for assembly text, obj for a native object, and null for timing runs with no output at all.

$ printf 'movl %%eax, %%ebx\n' | llvm-mc-20 \
    --triple=x86_64 --filetype=obj -o move.o
$ file move.o
move.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

The command creates or silently replaces move.o. Shell redirection and -o never ask before overwriting a destination, so pick a fresh filename or back one up before you point this at anything valuable. The safe recovery here is simply to remove move.o when you are done with it, provided it holds no work you need:

$ rm -- move.o

Warning: that removal is irreversible. Do not run it against a source tree, or a path someone else handed you, without checking it first. An object file is not an executable either; linking and any platform-specific loader steps come later.

4. Disassemble raw hexadecimal bytes

Disassembly is a different action entirely. Use --disassemble with --hex, which tells llvm-mc to read whitespace-separated hex as raw bytes. Keep the same target triple that produced the bytes in the first place.

$ printf '89 d8\n' | llvm-mc-20 --triple=x86_64 \
    --disassemble --hex --output-asm-variant=1
        mov     eax, ebx

Output syntax is controlled by --output-asm-variant: variant 1 gives the Intel-style register order shown here for this target. Do not guess an instruction from raw bytes without knowing the architecture, mode and syntax variant together. The same bytes can be invalid, or mean something else entirely, under a different target.

Checkpoint: confirm the command prints one instruction and returns zero. Omit --hex and the input gets parsed as assembly instead, so the byte sequence is rejected. Omit --triple and the host or build defaults may not match the bytes you are actually examining.

5. Keep assembly and disassembly tests repeatable

For a repeatable test, put the input in a named file and pass it as the final argument. The documented usage is llvm-mc [options] <input file>; standard input suits a quick probe, a file makes review and reruns much easier.

$ cat > /tmp/mc-example.s <<'EOF'
        .text
        movl %eax, %ebx
EOF
$ llvm-mc-20 --triple=x86_64 --show-encoding /tmp/mc-example.s
        .text
        movl    %eax, %ebx                      # encoding: [0x89,0xc3]

The here-document writes only under /tmp. Do not copy an untrusted assembly file into a build directory just to look at it. llvm-mc parses input and can emit objects, but nothing in this workflow ever executes the resulting bytes, so keep that boundary clear when you are handling files from outside your own project.

6. Diagnose the usual failures

Use llvm-mc-20 --help for the installed option spelling, and llvm-mc-20 --version for the target list. The manpage's --arch entry points back to that version output, while --triple picks the full target triple for assembly or disassembly.

Most of this is ordinary user work. Elevated privileges have no part in assembling a file in your own directory. If a destination or input genuinely needs root access, stop and check its ownership and purpose before reaching for sudo; changing permissions or writing system files sits outside this workflow entirely.

Done means