Assemble, Disassemble and Inspect Instructions with llvm-mc-18

You will use llvm-mc-18 to assemble one x86-64 instruction, show its byte encoding, and decode bytes back into assembly. Allow about 15 minutes. The examples use the llvm-18 package, version 18.1.3 on the system documented here. No root access is needed.

1. Confirm the installed tool and target

Start by checking that the versioned executable is on your path. The target triple matters: assembly syntax and instruction availability depend on the architecture you ask LLVM to use.

$ command -v llvm-mc-18
/usr/bin/llvm-mc-18
$ llvm-mc-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 installed binary lists its registered targets in the version output. This guide uses x86_64-pc-linux-gnu, which selects 64-bit x86 syntax and object conventions. If you are testing another architecture, replace the triple and use source written for that target.

Checkpoint: You have a versioned binary and a target triple that match the assembly you intend to test.

2. Assemble source and show its encoding

By default, llvm-mc assembles a .s input and prints assembly output. Give it source through standard input for a quick experiment. The -show-encoding option adds the machine bytes to each instruction.

$ printf '%s\n' 'movl $1, %eax' | llvm-mc-18 \
    -triple=x86_64-pc-linux-gnu -show-encoding
        .text
        movl    $1, %eax                        # encoding: [0xb8,0x01,0x00,0x00,0x00]

The output is text for inspection, not an object file. The five bytes are the encoding for this particular instruction in this target mode. Keep the $ and % characters inside the quoted shell argument: the first is meaningful to the assembler syntax, while an unquoted dollar sign could be expanded by the shell.

For repeatable work, put the source in a file and pass that file as the final argument:

$ printf '%s\n' 'movl $1, %eax' > example.s
$ llvm-mc-18 -triple=x86_64-pc-linux-gnu -show-encoding example.s
        .text
        movl    $1, %eax                        # encoding: [0xb8,0x01,0x00,0x00,0x00]

This changes the working directory by creating example.s. Remove that file later only when you no longer need it. Do not overwrite an existing source file with the redirection command unless you have checked its contents first.

3. Write a relocatable object file

Use --filetype=obj when the result is intended for an assembler or linker workflow. The default output file type is assembly text; an object file requires this explicit choice. The -o option names the destination.

$ llvm-mc-18 -triple=x86_64-pc-linux-gnu \
    --filetype=obj -o example.o example.s
$ file example.o
example.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

A successful exit status only says that LLVM accepted the source and wrote output. Inspect the object with an installed object-file tool if its sections or symbols matter:

$ llvm-readelf-18 -h example.o | sed -n '1,12p'
$ llvm-objdump-18 -d example.o

example.o:  file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
       0: b8 01 00 00 00     movl    $1, %eax

The exact headings and spacing can vary between LLVM tools, but the file format and instruction bytes should agree. If either command is not installed, use file as the basic format check and do not treat a missing inspection tool as an assembly failure.

Safety boundary: Creating an object file does not execute it. Linking it into a program and running that program is a separate action. Keep test outputs in a scratch directory when reviewing unfamiliar source.

4. Disassemble known bytes

Select --disassemble to make the input a stream of byte tokens rather than assembly source. LLVM's accepted form uses hexadecimal values with a 0x prefix. The same target triple is essential because one byte sequence can have different meanings on different architectures.

$ printf '%s\n' '0x48 0x89 0xe5' | llvm-mc-18 \
    -triple=x86_64-pc-linux-gnu --disassemble
        .text
        movq    %rsp, %rbp

Three related output modes are available in the LLVM 18 manpage. --disassemble emits ordinary assembly, --mdis emits marked-up operands such as <reg:%rsp>, and --cdis emits coloured output when the terminal supports it. Prefer the ordinary mode in scripts because terminal colour and markup are presentation details.

$ printf '%s\n' '0x48 0x89 0xe5' | llvm-mc-18 \
    -triple=x86_64-pc-linux-gnu --mdis
        .text
        movq    <reg:%rsp>, <reg:%rbp>

A common trap is to paste bare tokens such as 48 89 e5. In the installed LLVM 18 binary that is rejected as an invalid input token. Add 0x to each byte, and check the exit status when using a pipeline:

$ set -o pipefail
$ printf '%s\n' '0x48 0x89 0xe5' | llvm-mc-18 \
    -triple=x86_64-pc-linux-gnu --disassemble
$ printf 'status: %s\n' "$?"
status: 0

5. Use validation and timing modes deliberately

Keep assembly output while debugging syntax. Once you only need to know whether input is accepted, --filetype=null discards output and is useful for timing or a lightweight validation pass:

$ printf '%s\n' 'movl $1, %eax' | llvm-mc-18 \
    -triple=x86_64-pc-linux-gnu --filetype=null
$ printf 'status: %s\n' "$?"
status: 0

A non-zero status means that parsing or the selected output operation failed. Read the diagnostic on standard error, then verify the target, spelling and operand order. Do not hide diagnostics by redirecting standard error while diagnosing a failed build.

Options such as --mcpu and --mattr refine target features. Use them only when the CPU or feature set is part of the test. --mcpu=help and --mattr=help ask the installed program for available values; the list is version and target specific.

Done means