5.3.17 · D2 · HinglishBuild Systems & Toolchain

Visual walkthroughDisassembly — objdump, reading assembly

2,620 words12 min read↑ Read in English

5.3.17 · D2 · Coding › Build Systems & Toolchain › Disassembly — objdump, reading assembly

Hum poore walkthrough mein sirf ek hi function use karenge:

int add(int a, int b) {
    return a + b;
}

Neeche sab kuch zero se build kiya gaya hai — har box, arrow, aur word ko use karne se pehle define kiya gaya hai.


Step 1 — Do languages, aur unke beech ka bridge

KYA HAI. Ek .c file woh text hai jo ek insaan type karta hai. CPU text nahi padh sakta; woh machine code padhta hai — numbers ki ek stream. Beech mein hota hai assembler: ek fixed rulebook jo chhote word-jaisi names (mnemonics) ko un numbers mein convert karta hai. Disassembly us rulebook ko ulta chalti hai.

KYUN. Pehle hum ek bhi assembly line padh sakein, humein yeh agree karna hoga ki ek line hoti kya hai: yeh ek three-way translation mein ek row hai. Agar hum yeh skip kar dein, toh baad ke har symbol (rbp, edi, 0x8) bina kisi ghar ke float karta rahega.

PICTURE. Step-1 figure dekho. Orange arrow (left→right) woh hai jo compiler + assembler karta hai. Teal arrow (right→left) woh hai jo objdump karta hai. Middle column — numbers 55 48 89 e5 ... — woh ek aur sirf ek cheez hai jo CPU kabhi dekhta hai. Assembly (right column) ek human costume hai jo exactly un bytes ke upar pehna gaya hai.

Figure — Disassembly — objdump, reading assembly

Step 2 — CPU ke chhote boxes: registers

KYA HAI. Ek register ek chhota named box hai jo CPU ke andar hota hai aur ek number hold karta hai. Yeh RAM mein nahi hota; yeh wahan hota hai jahan arithmetic hoti hai, isliye yeh sabse fast storage hai. x86-64 par jo boxes hume yahan care karne chahiye woh hain rax, rbp, rsp, rdi, rsi, rdx.

KYUN. Humari function ko a + b add karna hai. Addition registers ke beech hoti hai. Isliye values add ho sakein, unhein pehle registers mein arrive karna hoga. Hamari assembly ki har line actually numbers ko in aur between in boxes mein move karne ke baare mein hai. Tum derivation nahi padh sakte bina boxes ki mental picture ke — toh yeh rahi, draw ki hue.

PICTURE. Step-2 figure mein chhe boxes hain. Nesting note karo: eax, 64-bit rax ke lower 32 bits hai. Names r.. (64-bit) aur e.. (32-bit) ek hi box hain jo do widths par dekhe gaye hain. Kyunki hamara int 32 bits ka hai, code e-named views use karega.

Figure — Disassembly — objdump, reading assembly

Step 3 — Contract: a aur b kahan arrive karte hain?

KYA HAI. Jab koi add(3, 4) call karta hai, toh 3 kahan jaata hai, aur 4 kahan jaata hai? Yeh koi choice nahi hai jo compiler freely karta hai — yeh ek calling convention follow karta hai, ek fixed agreement taaki caller aur callee ek doosre ko samjhein.

KYUN. Bina kisi agreed rule ke, caller arguments rdi mein chhod sakta hai jabki function unhein rsi mein dhundhe — chaos. Rule (System V x86-64, Linux/macOS par use hota hai) kehta hai: integer arg 1 → rdi, arg 2 → rsi, aur return value rax mein wapas aati hai. Hume yeh zaroor jaanna chahiye pehle ki disassembly ki line 4 samajh mein aaye.

PICTURE. Step-3 figure: do incoming values edi aur esi (arg slots) mein slide karti hain, aur ek dashed teal arrow dikhata hai ki result baad mein eax se nikalta hai. Yeh shape yaad rakho — yeh wahi hai har function ke liye jo tum kabhi bhi disassemble karoge.

Figure — Disassembly — objdump, reading assembly

Step 4 — Prologue: ek frame banana

KYA HAI. Pehle do instructions -O0 par hamesha same hote hain:

0:  55           push   rbp
1:  48 89 e5     mov    rbp, rsp

KYUN. Ek function ko apne local variables dhundhne ke liye ek stable "home base" address chahiye, chahe stack grow aur shrink hoti rahe. rbp (base pointer) woh home base hai. Lekin rbp abhi caller ka home base hold karta hai, isliye hum (1) use stack par push karke save karte hain, phir (2) current stack top (rsp) ko rbp mein copy karke ek fresh base banate hain. Yeh pair function prologue hai.

PICTURE. Step-4 figure: stack ko boxes ke downward stack ke roop mein draw kiya gaya hai (addresses upar jaane par chhoti hoti hain — yeh x86 convention hai). push rbp old base likhta hai aur rsp ko 8 bytes neeche drop karta hai; phir rbp naye top par plant kiya jaata hai. Term-by-term:

Figure — Disassembly — objdump, reading assembly

Step 5 — Arguments ko stack par spill karna

KYA HAI.

4:  89 7d fc     mov    [rbp-0x4], edi   ; store a
7:  89 75 f8     mov    [rbp-0x8], esi   ; store b

KYUN. -O0 ("no optimization") par compiler jaan-boojhkar literal hota hai: woh har variable ko memory mein ek real ghar deta hai taaki ek debugger use hamesha dhundh sake. Isliye woh a (jo edi mein hai) aur b (jo esi mein hai) ko registers se stack slots mein copy karta hai. Registers se stack par is copying ko spill kehte hain.

PICTURE. Step-5 figure: rbp offset 0 par anchor hai; [rbp-0x4] iske 4 bytes neeche hai (ek int), [rbp-0x8] 8 bytes neeche hai (agla int). Square brackets ka matlab hai "is address par memory", address khud nahi. Operand padhna:

Figure — Disassembly — objdump, reading assembly

Step 6 — Wapas load karo aur add karo

KYA HAI.

a:  8b 55 fc     mov    edx, [rbp-0x4]   ; edx = a
d:  8b 45 f8     mov    eax, [rbp-0x8]   ; eax = b
10: 01 d0        add    eax, edx         ; eax = eax + edx = b + a

KYUN. Arithmetic register-to-register kaam karti hai, isliye jo values abhi spill ki hain unhein wapas load karna hoga registers mein. Compiler a ko edx mein aur b ko eax mein pull karta hai, phir unhein add karta hai. Usne b ke liye eax deliberately choose kiya: sum eax mein land karta hai, jo — Step 3 se — exactly wahan hai jahan return value honi chahiye. Isliye baad mein koi extra move ki zaroorat nahi.

PICTURE. Step-6 figure: do loads (memory → register, teal arrows) phir ek add jo edx ko eax mein fold karta hai (orange). Add par term-by-term:

Figure — Disassembly — objdump, reading assembly

Step 7 — Epilogue: tear down aur return

KYA HAI.

12: 5d           pop    rbp   ; caller ka base restore karo
13: c3           ret          ; caller ke paas wapas jump karo

KYUN. Humne rbp borrow kiya tha; caller ka unhe wapas karna hoga. pop rbp woh value padhta hai jo humne Step 4 mein save ki thi aur rsp ko wapas upar bump karta hai — prologue ko exactly undo karta hai. Phir ret return address (original call dwara stack par rakha gaya) ko pop karta hai aur us par jump karta hai. Answer eax mein pehle se baitha hua hai, Step 3 ke contract ko fulfill karta hua.

PICTURE. Step-7 figure: stack un-wind hoti hai — rsp wapas climb karta hai jahan se shuru hua tha, rbp caller ke base par wapas aata hai, aur control teal arrow ke saath wapas udta hai eax result carry karte hue.

Figure — Disassembly — objdump, reading assembly

Step 8 — Degenerate/optimized case: sab collapse ho jaata hai

KYA HAI. -O2 ke saath recompile karo aur poori eight-instruction story sirf do mein simat jaati hai:

0:  8d 04 37     lea    eax, [rdi+rsi]
3:  c3           ret

KYUN. Optimization on hone par, compiler prove karta hai ki local variables aur stack frame ki kabhi zaroorat nahi: kuch debug nahi hota, kuch call ke baad nahi bachta. Isliye woh prologue, spills, reloads, aur epilogue delete kar deta hai. Phir woh lea use karta hai — ek instruction jo rdi + rsi jaisa address compute karne ke liye bani hai — ek sneaky one-shot adder ki tarah jo seedha eax mein likhta hai. Yeh woh edge case hai jise tumhe recognize karna hoga: wahi C, radically different bytes.

PICTURE. Step-8 figure: baai taraf lamba -O0 tower, ek arrow labelled "optimizer", aur daai taraf chhota sa do-line -O2 result — jo sab remove hua uske upar dotted crossings-out ke saath.

Figure — Disassembly — objdump, reading assembly

Ek-picture summary

Upar sab kuch, ek canvas par: C bilkul baai taraf, do register arg-slots feed karte hue, -O0 ka stack ke through shuffle, value eax mein land karti hui, aur -O2 shortcut ek dotted express lane ki tarah draw kiya gaya jo poora detour skip karta hai.

Figure — Disassembly — objdump, reading assembly
Recall Feynman retelling — poora walkthrough plain words mein

Do numbers edi aur esi naam ke do labelled cubbies mein front door par arrive karte hain. Function pehle ek chhota signpost (rbp) zameen mein thokta hai taaki woh apna saman dhundh sake, phir — kyunki humne compiler ko kaha clever mat bano (-O0) — woh carefully dono numbers ko signpost ke neeche do floor tiles mein copy karta hai. Thodi der baad woh unhe wapas uthata hai, ek edx mein aur ek eax mein, aur unhein add karta hai. Usne deliberately eax answer ke liye choose kiya kyunki eax woh "outbox" hai jise sab padhte hain jab function done hota hai. Aakhir mein woh signpost utha leta hai, zameen ko waise chhod deta hai jaisi thi, aur answer eax mein carry karte hue ghar wapas jump karta hai. Jab hum clever bano (-O2) ka switch flip karte hain, compiler realize karta hai ki woh saari copying pointless thi, use throw away karta hai, aur ek single address-math trick (lea) se dono cubbies ko seedha outbox mein add karta hai. Same answer, ek-chauthai instructions.


Connections

  • Disassembly — objdump, reading assembly (index 5.3.17) — parent note; yeh page iska central example zero se derive karta hai.
  • Calling Conventions (System V x86-64) — Step 3 ka edi/esi/eax contract.
  • Stack Frames & The Stack Pointer — Steps 4–7 (prologue, spills, epilogue).
  • Compiler Optimization Levels — Step 8 ka -O0 vs -O2 collapse.
  • Compilation Pipeline — woh forward arrow jise disassembly reverse karta hai.
  • Debugging with GDB — kyun -O0 sab kuch stack par rakhta hai.
  • ELF Object Files & Sections — woh .text section jahan yeh bytes rehte hain.
  • CISC vs RISC — kyun x86 bytes variable length hote hain aur lea math kar sakta hai.