Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Assembly I

The examples use GNU assembler’s AT&T syntax:

# source, destination; % prefixes a register and $ prefixes an immediate
movq $1, %rax
movq (%rdi), %rax

For a complete introduction to the instruction set, use the Intel 64 and IA-32 Software Developer’s Manuals. For the assembler syntax used here, see GNU as’s i386 syntax reference.

PC-Relative

An address can be encoded as an absolute value or as a displacement from the current instruction. x86-64 code commonly uses the second form. It makes the same machine code usable after the code segment is moved, which is the basis of position-independent code.

In x86-64, the program counter is the %rip register. A RIP-relative operand uses the address of the next instruction plus a signed displacement:

movq global(%rip), %rax       # load a nearby symbol
leaq message(%rip), %rdi      # form a nearby address

The assembler and linker fill in the displacement through a relocation entry. The exact relocation type depends on whether the symbol is local, preemptible, or supplied by another object or shared library.

Position-Independent Code (PIC)

Position-independent code can execute correctly when loaded at different virtual addresses. Shared libraries are normally built this way. PIC allowing the loader to combine it with address-space layout randomization (ASLR).

PIC avoids embedding absolute addresses in instructions. Local data can often be reached with RIP-relative addressing; addresses of externally visible objects are commonly loaded through the Global Offset Table (GOT). Function calls may use the Procedure Linkage Table (PLT), discussed below.

The names of the compiler options describe the intended output:

# Shared-library object and shared library
gcc -fPIC -c library.c -o library.o
gcc -shared library.o -o libexample.so

# Position-independent executable
gcc -fPIE -c main.c -o main.o
gcc -pie main.o -o app

-fpic/-fPIC affect compilation, while -pie affects linking. On a toolchain that defaults to PIE, -fno-PIE -no-pie can be used for a deliberately position-dependent executable when that is useful for an experiment.

Further reading:

Relative Addressing

A direct jmp or call normally stores a displacement from the end of that instruction to its target. The target does not have to be at a fixed absolute address:

jmp loop
call function

The assembler emits a relocation for a symbol whose final distance is not known yet. The static linker resolves references between input objects; a dynamic linker may apply remaining relocations when a program or shared library is loaded.

Do not confuse a direct relative call with an indirect call through a register or memory operand:

call function       # direct, normally PC-relative
call *%rax          # indirect, target comes from %rax

GNU as documents x86 jumps and calls and RIP-relative memory operands. The ELF specification’s relocation chapter explains why the linker needs these relocation entries.

Memory Addressing

To evaluate a[5], generated code must locate the object a, compute an offset of five elements, and load from the resulting address. The instruction used for the load depends on where the symbol lives:

movq 24(%rsp), %rax                 # a local value in the current stack frame
movq global(%rip), %rax             # a nearby global symbol
movq external@GOTPCREL(%rip), %rax  # address/value reached through the GOT

The first form uses a register-relative displacement. The second uses a link-time PC-relative relocation. The third asks the linker to resolve the address of an external symbol through a GOT entry. These are representative forms; compiler output also depends on symbol visibility, code model, optimization, and whether the value or its address is needed.

Useful tools for seeing the result are:

objdump -drwC -Mintel program.o
readelf -rW program.o
readelf -SW program

Read GNU as’s memory reference documentation, the ELF specification, and the AMD64 ELF ABI index for the complete relocation and object-file rules.

PLT & GOT

The Global Offset Table (GOT) is a table of addresses or pointers that may need to be changed by the dynamic linker. A compiler can reach a GOT entry with a RIP-relative relocation and then load the current value from that entry.

The Procedure Linkage Table (PLT) is a set of small stubs for calling functions whose final addresses are not known when an object is compiled. A typical lazy-binding sequence is:

  1. The call enters a PLT entry.
  2. The PLT entry jumps through its GOT slot.
  3. On the first call, the dynamic linker resolves the symbol and updates that slot.
  4. Later calls use the resolved address directly.

For example, call printf@PLT names a PLT relocation rather than the final address of printf. Local, non-preemptible functions can usually be called directly without a PLT entry.

Binding policy is separate from the call sequence. -Wl,-z,lazy permits deferred function binding, while -Wl,-z,now asks the linker/loader to resolve it at load time. GCC’s -fno-plt changes how external calls are generated; it should not be confused with -z now.

Further reading:

Addressing Modes

In AT&T syntax, the general x86 memory operand is:

displacement(base, index, scale)

Its effective address is:

displacement + base + index * scale

The base and index are registers. The scale is one of 1, 2, 4, or 8, which makes common array element sizes cheap to express:

movq (%rax), %rdx                 # *a
movq 40(%rax), %rdx               # a[5] when sizeof(*a) == 8
movq (%rax,%rsi,8), %rdx          # a[i] when i is in %rsi
movq 320(%rcx,%rdi,8), %rdx       # b[5][j] for double b[8][8]

If a row stride is not one of the available scales, the compiler can use leaq or another instruction to form an intermediate address. leaq computes an address-shaped integer and does not access memory.

The GNU assembler manual has the formal x86 memory-reference syntax. Intel’s instruction reference is useful when checking the operand-size and addressing restrictions of a particular instruction.

Extra

Condition Flags

Many integer instructions update bits in RFLAGS. The flags most often used by conditional branches are:

FlagMeaning after an arithmetic operation
ZFResult is zero
SFResult has its sign bit set
CFUnsigned carry or borrow
OFSigned overflow
PFResult has even parity in its low byte

cmp performs a subtraction and discards the result; test performs an AND and discards the result. In AT&T syntax, cmpq %rsi, %rdi computes %rdi - %rsi for flag purposes:

cmpq %rsi, %rdi
je equal             # ZF == 1
jl signed_less       # SF != OF
jb unsigned_less     # CF == 1

Signed and unsigned comparisons use different condition codes. lea does not modify condition flags, and inc/dec preserve CF; do not assume that every arithmetic-looking instruction sets every flag.

For exact flag definitions and every jcc/setcc condition, see Intel’s instruction-set reference.

Name of Registers

You may wonder why registers are named %rax, %rbx, %rsi, %r8, %r9, etc., other than %rax, %rbx…%rmx, %rnx(abc ver.) or %r0, %r1…%r14, %r15(123 ver.).

Well, almost all ISA today use 123 ver. to name their registers (Risc-V for example), and all extensions to x86-64 also use 123 ver. However, x86 has a long history and its ancient ancestor refused to use 123 ver. to name its registers.

That old stuff used a more “meaningful” naming norm, like this:

  • EAX - Accumulator Register
  • EBX - Base Register
  • ECX - Counter Register
  • EDX - Data Register
  • ESI - Source Index
  • EDI - Destination Index
  • EBP - Base Pointer
  • ESP - Stack Pointer

So abc ver. coincidentally becomes abc ver., huh?

To explore more about their meaning, click this link:

The Art of Picking Intel Registers (swansontec.com)

But today, the meaning of these registers’ name becomes legacy, and the meaningful thing becomes meaningless. We could never predict what would happen in the future, right?

GCC Compiler Flags

These options are most useful when the goal is to inspect compiler-generated assembly. Options can affect compilation, assembling, or linking, so keep the stages separate.

Emit Assembly and Debug Information

# Generate AT&T-syntax assembly without assembling it.
gcc -S -O0 -g -fverbose-asm -fno-omit-frame-pointer \
    -o demo.s demo.c

# Generate an executable with DWARF information for GDB.
gcc -g -O0 -fno-omit-frame-pointer -o demo demo.c

# Select Intel syntax instead of GCC's default AT&T syntax.
gcc -S -masm=intel -o demo.s demo.c
  • -S stops after compilation and writes an assembly file.
  • -g emits debugging information. With -S, it also makes source-location and related assembler directives visible; keep -g for the final link when GDB should show source lines.
  • -fverbose-asm adds human-readable comments to GCC’s assembly output. The comments are for inspection and are not a stable interface.
  • -masm=att and -masm=intel select the assembly dialect.

See GCC’s overall options, debugging options, and code-generation options.

Control Optimization

gcc -S -O0 demo.c -o demo.s       # disable most optimization passes
gcc -S -Og demo.c -o demo.s       # debug-friendly optimization
gcc -S -O2 -fno-inline \
    -fno-omit-frame-pointer -fno-optimize-sibling-calls \
    demo.c -o demo.s

-O0 is the clearest starting point, while -Og often produces a more useful debugging experience. Individual -fno-... options are useful when investigating a specific transformation, but they do not make optimized output equivalent to -O0. GCC documents these levels and switches in its optimization options.

Choose PIC/PIE and PLT Behavior

# Compile code for a shared library.
gcc -fPIC -c library.c -o library.o

# Compile and link a position-independent executable.
gcc -fPIE -pie main.c -o app

# Deliberately build a position-dependent executable.
gcc -fno-PIE -no-pie main.c -o app

# Compare external-call sequences.
gcc -S -fplt external.c -o with-plt.s
gcc -S -fno-plt external.c -o through-got.s
  • -fPIC/-fpic generate position-independent object code for shared libraries.
  • -fPIE/-fpie generate position-independent code intended for an executable; -pie enables the corresponding link mode.
  • -fno-pic/-fno-PIE controls compilation, while -no-pie controls the final link. A distribution’s GCC driver may enable PIE by default, so inspect the complete command when comparing output.
  • -fplt requests PLT-based external calls (the usual default on targets that support it). -fno-plt loads the function address from the GOT at the call site.

For binding policy, pass linker options through GCC: -Wl,-z,lazy permits lazy function binding and -Wl,-z,now requests eager binding. Read the GCC PIC/PIE options, link options, and GNU ld’s -z options.

GDB

GDB is the GNU Debugger. It can stop a running program, inspect registers, memory, stack frames, and variables, disassemble instructions, and modify state while investigating a bug. Compile with debug information first:

gcc -g -O0 -fno-omit-frame-pointer demo.c -o demo
gdb ./demo

The following commands cover a first assembly-level session:

CommandPurpose
break main / b mainSet a breakpoint
run / rStart or restart the program
next / nExecute one source line, stepping over calls
step / sExecute one source line, stepping into calls
nexti / niExecute one machine instruction, stepping over calls
stepi / siExecute one machine instruction, stepping into calls
continue / cContinue until the next stop
finishRun until the current function returns
disassemble /m mainShow mixed source and assembly for main
info registersPrint register values
p/x $raxPrint %rax in hexadecimal
x/8gx $rspExamine eight 8-byte words at the stack pointer
backtrace / btShow the call stack
info localsShow locals in the selected frame
set disassembly-flavor intelUse Intel syntax in disassembly
quit / qExit GDB

Inside GDB, $pc and $sp refer to the program counter and stack pointer; on x86-64 they correspond to %rip and %rsp. Use help command to inspect any command’s syntax.

Start with the official sample GDB session, then use the GDB documentation index and the full GDB manual. For a compact command reference, see the GDB Cheat Sheet from the University of Pittsburgh.