5.3.17 · D5Build Systems & Toolchain
Question bank — Disassembly — objdump, reading assembly
Before we start, one anchor so nothing here uses an unearned word:
Before the traps — five words this page reuses
The questions below lean on a handful of jargon words. So nothing is used before it is earned, here they are in plain language, each anchored to a picture.




True or false — justify
Each is a statement. Decide, then say why — the reasoning is the point.
T/F: Disassembly perfectly recovers the original C source.
False — it recovers instructions, not source. Variable names, comments, types, and which lines got merged/deleted by the optimizer are gone; disassembly is a one-way street mapping bytes → mnemonics, not bytes → C.
T/F: Every x86 instruction is the same number of bytes.
False — x86 is a CISC design; instructions run from 1 byte (
c3 = ret) up to 15. This is why you cannot locate the next instruction by "address + 4".T/F: objdump -d dumps the entire file including strings and data.
False —
-d only touches the executable .text section. Constant strings, globals, and relocation info live in other ELF sections and need -s, -r, or -D.T/F: In Intel syntax, mov rbp, rsp copies rbp into rsp.
False — Intel puts the destination first, so
mov rbp, rsp means rbp = rsp. The trap is reading left-to-right as source→dest, which is the AT&T convention.T/F: cmp a, b stores the value a - b somewhere for you to use.
False —
cmp computes a - b only to set the CPU flags (ZF/SF/CF/OF), then throws the numeric result away. Nothing is written to a register; the flags are the whole product.T/F: Fewer assembly instructions always means faster code.
False — one
div can cost 30+ cycles while ten movs might cost ~3. Count tells you what runs; only profiling tells you how fast (pipelining, cache, dependency chains dominate).T/F: A jle (jump if ≤) after an if (x > 5) is a compiler bug.
False — it is normal branch inversion. Source says "do the body if
x > 5", so the compiler jumps away when the opposite (x <= 5) holds, skipping the body. Inverted conditions are the standard pattern.T/F: lea eax, [rdi+rsi] reads memory at address rdi+rsi.
False —
lea (load effective address) computes the address but never dereferences it. Here the optimizer abuses it as a pure adder: eax = rdi + rsi, with no memory access and no flag side effects.T/F: The 48 prefix in 48 89 e5 is a separate instruction.
False —
48 is a REX prefix (one byte, 40–4f) meaning "operate on 64-bit operands". It modifies the following opcode 89 (mov); it is not an instruction of its own.T/F: -M intel changes what the CPU executes.
False —
-M intel only changes how objdump prints the same bytes. The machine code is identical; you are choosing a display dialect, not recompiling.Spot the error
Each line contains a wrong claim or reading. Name the flaw.
"The bytes 55 48 89 e5 disassemble to four mov instructions."
Wrong count and wrong ops —
55 is push rbp (1 byte) and 48 89 e5 is mov rbp, rsp (3 bytes, where 48 is the REX 64-bit prefix). That is the two-instruction prologue, not four movs; you cannot slice x86 into equal chunks."I read mov %eax, %ebx in AT&T as eax = ebx."
Backwards — the
% sigils mark AT&T, where source comes first, so mov %eax, %ebx means ebx = eax. Check the sigils (% on registers, $ on immediates) before assigning direction."objdump -D is just -d with more useful output."
Misleading —
-D disassembles all ELF sections, so data bytes get decoded as if they were instructions and print as garbage mnemonics. It's noisier, not strictly "more useful"."The result of add eax, edx will be in edx since that was the second operand."
Wrong — Intel destination is first, so
add eax, edx does eax = eax + edx; the sum lands in eax, which the System V ABI designates as the integer return register."There's no stack frame in the -O2 version, so the compiler forgot the prologue."
Not forgotten — the optimizer proved the frame was unnecessary (no locals spilled, no calls needing a stable base) and omitted it deliberately. Absent prologue is an optimization, not an omission.
"The address column 1149: is where the data this line uses lives."
Wrong — the address column is where the instruction itself sits (its offset / virtual address), not the address of any operand it touches.
Why questions
Why does GNU default to AT&T syntax while most tutorials use Intel?
Historical: the GNU toolchain grew from Unix conventions that used AT&T (source first,
% on registers, $ on immediates). Intel syntax reads like dst = src, matching how you think in C, so learners flip it on with -M intel.Why does cmp exist as a separate instruction instead of just using sub?
sub overwrites its destination with the difference; often you want the comparison flags (ZF/SF/CF/OF) without destroying the operand. cmp gives exactly the flag side effects while leaving both values intact.Why does -O0 code spill args like edi/esi straight to the stack?
At
-O0 the compiler prioritizes debuggability and simple, literal translation, giving every variable a fixed stack home so a debugger can find it. Higher -O levels keep values in registers instead.Why can a crash address be more useful than a C line number?
Optimizers reorder, inline, and delete code, so a source line may not correspond to any single instruction. The faulting instruction address is ground truth about what actually executed at the moment of the crash.
Why is ret a single byte (c3) while sub rsp, 0x8 is four?
ret needs no operands — it just pops and jumps, so one opcode byte suffices. sub rsp, 0x8 carries a REX prefix (64-bit), an opcode, an operand descriptor, and an immediate, so it needs four bytes.Why does reading assembly matter if you never plan to write it?
Because it answers "did the compiler do what I think?" — you read it to verify optimizations, understand crashes, and inspect binaries you have no source for, not to hand-author instructions.
Why does the integer return value go in eax/rax specifically?
It's a contract: the System V x86-64 ABI designates
rax as the return register so caller and callee agree where to look, without inspecting the function's internals.Edge cases
What happens if you run objdump -d on a file with no .text section (pure data)?
You get essentially nothing disassembled —
-d skips non-executable sections, so a data-only object shows no instructions. You'd need -s to view the raw bytes.What does disassembling a .o (unlinked object) look like versus a linked executable?
The
.o has addresses starting near 0 and shows unresolved call/jump targets as placeholders; -r reveals the relocations (the "patch-me-later" notes) the linker must resolve. The executable has final addresses filled in.What if objdump starts decoding halfway into a multi-byte instruction?
It produces nonsense — the same byte stream means different things depending on where decoding begins, so mis-aligned disassembly (common when data is treated as code by
-D) yields plausible-looking but wrong mnemonics.What does an empty function body void f(){} disassemble to at -O2?
Often just
ret (c3) — no prologue, no work, a single-byte return. The optimizer keeps only what's observable, and an empty function only needs to return.Can two different byte sequences disassemble to the same mnemonic?
Yes — many operations have multiple encodings (e.g. different immediate sizes or register combos), so
mov, add, etc. correspond to whole families of byte patterns, not one fixed sequence.What does -S show if the binary was compiled without -g?
Little to no source interleaving —
-S relies on debug info to map instructions back to C lines, so without -g you get plain disassembly with the source column mostly empty.Connections
- Disassembly — objdump, reading assembly — the parent this bank drills.
- Compilation Pipeline · Calling Conventions (System V x86-64) · Stack Frames & The Stack Pointer · Compiler Optimization Levels · Debugging with GDB · ELF Object Files & Sections · CISC vs RISC