Setup#
Assembly has no single toolchain. Pick an architecture (x86_64, ARM64, RISC-V), a syntax (Intel vs AT&T, NASM vs GAS), an assembler, and a linker. The choice is driven by the target the operator is reading or writing for.
Install#
1. Pick an assembler.
Assembler |
Syntax |
Architectures |
Notes |
|---|---|---|---|
|
Intel |
x86, x86_64 |
The default for hand-written x86 / x64. |
|
Intel / GAS |
x86, x86_64 |
NASM-compatible, alternative implementation. |
|
AT&T (default), Intel via |
everything GCC supports |
Ships with binutils; what GCC drives. |
|
integrated |
everything LLVM supports |
Bundled with clang / LLVM. |
2. Install on Linux.
$ sudo apt install nasm yasm binutils gdb # Debian / Ubuntu
$ sudo dnf install nasm yasm binutils gdb # Fedora / RHEL
$ brew install nasm yasm binutils gdb # macOS
3. Install a disassembler and reverse-engineering tool.
$ sudo apt install radare2 binutils # objdump, nm, strings
# Ghidra, IDA Free, Cutter are GUI installs
4. Verify.
$ nasm -v
$ as --version
$ ld --version
$ objdump -v
$ gdb --version
Setup project#
1. Lay out the source.
my-asm/
├── Makefile
├── src/
│ ├── hello.s # GAS / AT&T
│ └── exit.asm # NASM / Intel
└── README.md
2. NASM “hello, world” on Linux x86_64.
; src/hello.asm
section .data
msg: db "hello", 0x0A
len: equ $ - msg
section .text
global _start
_start:
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, msg
mov rdx, len
syscall
mov rax, 60 ; sys_exit
xor rdi, rdi
syscall
3. Build.
$ nasm -f elf64 src/hello.asm -o hello.o
$ ld hello.o -o hello
$ ./hello
4. Minimal Makefile.
AS = nasm
LD = ld
AFLAGS = -f elf64
hello: hello.o
^I$(LD) -o $@ $<
%.o: src/%.asm
^I$(AS) $(AFLAGS) -o $@ $<
clean:
^Irm -f hello *.o
.PHONY: clean
Inline assembly in C#
The common case where write a few lines of
assembly. GCC / clang use __asm__ blocks; the syntax follows
AT&T by default.
#include <stdint.h>
static inline uint64_t rdtsc(void) {
uint32_t lo, hi;
__asm__ volatile ("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
}
Cross-architecture#
For ARM, RISC-V, or non-Linux targets.
$ sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
$ sudo apt install gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
$ aarch64-linux-gnu-as src/arm.s -o arm.o
$ aarch64-linux-gnu-ld arm.o -o arm
# Run cross-arch binaries with qemu-user
$ sudo apt install qemu-user
$ qemu-aarch64 ./arm
Common Tasks#
Disassemble a binary.
$ objdump -d -M intel hello
$ r2 -A hello # interactive (radare2)
See what GCC produces for a C function.
$ gcc -S -O2 -masm=intel example.c -o example.s
Step through a binary in gdb.
$ gdb hello
(gdb) break _start
(gdb) run
(gdb) layout asm
(gdb) si # step one instruction
Convert bytes into NASM-style data.
$ xxd -i shellcode.bin | sed 's/.*= */shellcode db */'
Compute the size of a function.
$ nm --size-sort hello
References#
Tooling for
gdb,objdump,radare2, Ghidra,qemu.Intel SDM, the reference x86_64 reference.
Arm ARM, the ARMv8/9 reference.
OSDev wiki, for boot-time and freestanding work.