5.3.17 · D3 · HinglishBuild Systems & Toolchain

Worked examplesDisassembly — objdump, reading assembly

3,448 words16 min read↑ Read in English

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

Prerequisites jinpe hum depend karte hain: Calling Conventions (System V x86-64), Stack Frames & The Stack Pointer, Compiler Optimization Levels, aur parent Disassembly.


Scenario matrix

Is topic ke har possible case, aur unhe cover karne wala example:

Cell Scenario class Covered by
C1 Signed comparison, positive branch (>) Example 1
C2 Signed comparison, negative operand / opposite sign (<) Example 2
C3 Unsigned comparison — sign ka trap Example 3
C4 Zero / degenerate input — empty function Example 4
C5 Loop (neeche zero ki taraf count karna, back-edge jump) Example 5
C6 Byte-level decode by hand (variable length) Example 6
C7 Limiting case — optimizer sab kuch delete kar deta hai (-O2) Example 7
C8 Real-world word problem — crash address → source line Example 8
C9 Exam twist — same mnemonic, do meanings (lea vs mov) Example 9
C10 Tail call — jmp jahan call expect karte the Example 10

Upar se neeche padho; har cell pichle wale pe build karta hai.


C1 — Signed comparison, positive branch

Steps

  1. test edi, edi compute karta hai edi & edi. Yeh step kyun? Ek number ko khud se AND karne se kuch change nahi hota lekin flags zaroor set hote haincmp edi, 0 se sasta hai. Sign flag (SF) end mein a ke top bit ke barabar hota hai, zero flag (ZF) = 1 agar a == 0.
  2. jle 0xb ka matlab hai "jump if less-than-or-equal (signed)". Yeh step kyun? Source kehta hai return 1 tab karo jab a > 0; assembly ise invert karke return 0 block ki taraf jump karti hai jab a <= 0 ho. Inversion normal pattern hai (parent ka if example).
  3. a = 5 ke liye: 5 <= 0 false hai → jump nahi → mov eax, 1. Result 1.
  4. a = 0 ke liye: 0 <= 0 true hai → 0xb par jump → mov eax, 0. Result 0.
  5. a = -3 ke liye: -3 <= 0 true → jump → 0.
Recall

jle kyun aur jbe kyun nahi? jle signed version hai. jbe ("below or equal") unsigned version hai. C type int (signed) tha, isliye compiler ko signed jump chunnna padta hai. Yahi choice C3 ka pura subject hai.

Verify: C function return karta hai a > 0 ? 1 : 0. {5, 0, -3} ke liye yeh hoga {1, 0, 0} — steps 3-5 se match karta hai. ✔


C2 — Negative operand, < branch

Steps

  1. 0xfffffffc decode karo. Yeh step kyun? 32-bit two's complement mein, top bit sign hai. 0xfffffffc = 4294967292 unsigned mein, lekin signed int mein yeh hai . Compiler ne -4 is tarah likha.
  2. cmp [rbp-0x4], -4 compute karta hai x - (-4) = x + 4 aur flags set karta hai. Yeh step kyun? cmp koi result store nahi karta; sirf flags ko agle jump ke liye set karta hai (parent ka rule).
  3. jge 0xf = "jump if greater-or-equal (signed)". Source chahta hai y=1 jab x < -4; assembly else (y=2) ki taraf jump karta hai jab x >= -4 ho.
  4. x = -10 ke liye: kya -10 >= -4? Nahi. Toh hum jump nahi karte → fall through karke y = 1.

Verify: -10 < -4 true hai, toh y = 1 — step 4 se match karta hai. Aur immediate: -4 ko unsigned mein store karo toh . ✔


C3 — Unsigned trap

Steps

  1. Same bit pattern, do readings. Yeh step kyun? 0xffffffff signed int mein -1 hai, lekin unsigned mein 4294967295. Bits bilkul same hain; sirf compiler ka chuna hua instruction decide karta hai kaise compare hoga.
  2. setg signed semantics use karta hai. Toh f poochta hai: kya -1 > 3? Nahi → al = 0.
  3. seta unsigned semantics use karta hai. Toh g poochta hai: kya 4294967295 > 3? Haan → al = 1.
  4. Asm padhne ke liye yeh kyun zaroori hai? Mnemonic tumhe C type bata deta hai. g/a-family = unsigned, l/ge-family = signed. Tum signedness recover kar sakte ho jo raw bytes se chhupi rehti hai.

Verify: signed -1 > 3 = 0, unsigned 4294967295 > 3 = 1. ✔


C4 — Degenerate input: empty function

Steps

  1. Kuch nahi karte huye bhi, -O0 phir bhi prologue (push rbp; mov rbp, rsp) aur epilogue (pop rbp; ret) emit karta hai. Yeh step kyun? -O0 mechanical hai: yeh frame set up karta hai locals ke liye jo shayad chahiye hon, phir tod deta hai. Dekho Stack Frames & The Stack Pointer.
  2. Akela 90 ek nop (no-operation) hai. Yeh step kyun? Yeh (empty) function body ko mark karta hai aur alignment mein help kar sakta hai. Koi state nahi badlata.
  3. Registers par net effect: rbp push hua phir pop hua → unchanged; rsp wapas jahan tha.

Verify: bytes gino: 55 (1) + 48 89 e5 (3) + 90 (1) + 5d (1) + c3 (1) = 7 bytes, aur last address 6 hai 1-byte ret ke liye, toh function 0x0..0x6 inclusive span karta hai = 7 bytes. ✔ Register net change = zero. ✔


C5 — Ek loop jo neeche count karta hai (back-edge jump)

Figure — Disassembly — objdump, reading assembly

Steps

  1. Initialize eax=0 (sum), edx=3 (i). Yeh step kyun? Compiler dono ko tight loop ke liye registers mein rakhta hai — koi stack spill ki zaroorat nahi.
  2. add eax, edx phir sub edx, 0x1. Yeh reordered order kyun? Compiler ne pehle body run ki, decrement baad mein ki, taaki loop ek single jne par end ho jo sub ke flags check kare — ek branch per iteration.
  3. jne 0xa = "jump if not equal" — sub ka zero flag padhta hai. Jab tak i 0 tak nahi pahuncha, loop karte hain. Yeh step kyun? Zero tak neeche count karna sasta hai: CPU sub par ZF free mein set karta hai, toh alag cmp ki zaroorat nahi.
  4. Loop kaisa dikhta hai (inline describe kiya, agar figure load na ho): chaar instructions add → sub → jne ek seedhi vertical column mein hain, lekin jne address 0xf par address 0xa ko target karta hai — ek address jo khud se pehle ka hai. Yeh upward jump back-edge hai (figure mein right side ki thick red curved arrow ke roop mein drawn): yeh control ko body ke bottom se add eax, edx ke top par wapas loop karta hai. Jab i finally 0 par pahunchta hai toh zero flag set hota hai, jne taken nahi hota, aur control seedha ret par fall through karta hai — loop se bahar nikal jaata hai.
  5. Trace: sum=0,i=3 → add→sum=3, sub→i=2, back-edge taken. → add→sum=5,i=1, taken. → add→sum=6,i=0, not taken → fall through → ret.

Verify: . Final eax = 6. ✔


C6 — Raw bytes haath se decode karo (variable length)

Steps

  1. Pehla byte 48 ek REX prefix hai. Yeh step kyun? Range 0x40–0x4f mein, 48 matlab "yeh operation 64-bit operands use karta hai" (parent ne note kiya tha 48 = 64-bit). Yeh ek prefix hai, standalone op nahi — real opcode aage aata hai.
  2. Agla byte 83 = opcode "arithmetic with an 8-bit immediate" ek 32/64-bit register par. Yeh step kyun? 0x83 compact form hai jo tab use hota hai jab constant ek signed byte mein fit ho.
  3. c0 ModR/M byte hai jo operation select karta hai (/0 field = ADD) aur register (rax). 05 immediate = 5 hai.
  4. Meaning assemble karo: add rax, 0x5, total length 4 bytes. Yeh kyun matter karta hai: tum x86 ko RISC ki tarah fixed 4-byte words mein nahi kaad sakte — dekho CISC vs RISC. Tumhe chalana hoga prefix → opcode → ModR/M → immediate.

Verify: instruction length yahan hai prefix(1) + opcode(1) + modrm(1) + imm8(1) = 4 bytes, aur 0x05 decimal 5 decode hota hai. ✔


C7 — Limiting case: optimizer sab kuch delete kar deta hai

Steps

  1. Sab kuch constant hai. Yeh step kyun? Optimizer constant folding karta hai: yeh 2*3+4 = 10, phir 10*10 = 100 compile time par evaluate karta hai. Dekho Compiler Optimization Levels.
  2. 0x64 wahi folded constant hai. Yeh step kyun? . Pura function collapse ho jaata hai "100 load karo, return karo" mein.
  3. Yeh optimization ka limiting behaviour hai: jab ek result runtime par kisi cheez par depend nahi karta, runtime work bilkul gayab ho jaata hai.

Verify: , , aur . ✔


C8 — Real-world: crash address → source line

Steps

  1. Bug division by zero hai. Yeh step kyun? esi = 0 ke saath idiv esi ek #DE (divide error) CPU exception trigger karta hai — ek hardware trap, memory segfault nahi, lekin OS ise tumhare process ko surface karta hai.
  2. idiv se pehle cdq. Yeh step kyun? idiv edx:eax mein 64-bit value ko divide karta hai. cdq eax ka sign bit edx ke pura across copy karta hai taaki signed divide sahi ho.
  3. Faulting instruction 0x401133 (idiv) par hai. Crash line 0x40113a kyun print hua? Bilkul: #DE fault par CPU faulting rip khud (0x401133) kernel stack par push karta hai — saved instruction pointer idiv par point karta hai, usse aage nahi. Lekin tumhare program ke apne signal/crash reporter ne woh raw rip print nahi kiya; usne symbol boundary print ki jo usne address resolve ki. 0x40113a yahan agle symbol ki start hai — functions commonly 16-byte boundary par pad hote hain, toh linker ne divide (ending at 0x401135) aur agle function ke beech 0x40113a par gap bytes chhode. Ek naive reporter jo "main kaunse function ke sabse near hoon" upward round karta hai, ya stack se stale return address padhta hai, us padded boundary par land karta hai.
  4. Reliable method: rounded address par kabhi trust mat karo. Sabse bada instruction address dhundho jo ≤ reported address ho aur kisi real function ke andar ho. 0x401133 (idiv) last real instruction hai ≤ 0x40113a divide ke andar, toh wahi culprit hai. Live rip Debugging with GDB mein confirm karo rather than printed line par trust karo.

Verify: idiv 0x401133 par hai; agle symbol tak padding gap bytes hai (ret ke baad), aur undefined hai → divide error. ✔


C9 — Exam twist: same brackets, do meanings

Steps

  1. [rdi+rsi] ek address expression hai: 100 + 4 = 104. Yeh step kyun? Intel syntax mein square brackets matlab "yeh address compute karo".
  2. mov eax, [rdi+rsi] (A) dereference karta hai: address 104 par jao, wahan stored value load karo. Yeh step kyun? mov bracket source ke saath memory padhta hai. Toh eax = 77.
  3. lea eax, [rdi+rsi] (B) memory touch nahi karta — yeh address itself store karta hai. Yeh step kyun? lea = "load effective address"; yeh tumhe 104 ek number ke roop mein deta hai. Optimizer ise free adder ke roop mein abuse karta hai (parent ka -O2 add example). Toh eax = 104.
  4. Trap: identical brackets, opposite behaviour — ek RAM padhta hai, ek number compute karta hai.

Verify: (A) memory[104] = 77 load karta hai. (B) 100 + 4 = 104 compute karta hai. ✔


C10 — Tail call: ek jmp jahan call expect karte the

Steps

  1. add edi, 0x1 argument prepare karta hai. Yeh step kyun? edi arg 1 hold karta hai (System V convention). helper(a+1) ko edi mein a+1 chahiye, toh compiler ise in-place increment karta hai.
  2. Last instruction jmp helper hai, call helper nahi. Yeh step kyun? Yeh ek tail call hai: wrapper ka ek hi kaam bacha — jo bhi helper return kare use return karna. call helper (jo ek return address push karta) phir ret karne ki bajaye, compiler wrapper ka apna return address reuse karta hai — woh seedha helper mein jmp karta hai. Jab helper apna ret run karta hai, woh seedha wrapper ke caller ko return karta hai, wrapper ko skip karke.
  3. Yahan ret kyun nahi? helper return hone ke baad kuch karna bacha hi nahi, toh wrapper ka koi epilogue hi nahi. Stack frame count flat rehta hai — yahi essence hai tail-call optimization ki.
  4. Reading rule: ek function ke end mein kisi aur function ke label par jmp na loop hai na bug — yeh tail call hai. Return value untouched flow karta hai.

Verify: wrapper(a) return karta hai helper(a+1). a = 7 ke liye, edi 8 ban jaata hai, aur result jo bhi helper(8) yield kare — wrapper kuch extra nahi add karta. Toh observable result helper(8) ke barabar hai, yaani +1 hi ek maatra transformation hai. ✔


Question — setg on 0xffffffff (as signed int > 3)?
0, kyunki -1 > 3 false hai.
Question — seta on 0xffffffff (as unsigned > 3)?
1, kyunki 4294967295 > 3 true hai.
Question — down-counting loop 3+2+1 ka final eax?
6.
Question — -O2 constant-folded compute mein 0x64 decode karo?
100.
Question — mov/lea mein se [rdi+rsi] par kaun RAM padhta hai?
mov RAM padhta hai; lea sirf address number compute karta hai.
Question — ek function mein ret ke bina end mein jmp helper?
Ek tail call — control helper mein jump karta hai, jo seedha original caller ko return karta hai.

Connections

  • 5.3.17 Disassembly — objdump, reading assembly (Hinglish) — parent topic, saath mein padho.
  • Calling Conventions (System V x86-64) — kyun args edi/esi mein aur results eax mein hote hain.
  • Stack Frames & The Stack Pointer — C4 mein prologue/epilogue aur C10 mein tail call.
  • Compiler Optimization Levels — C7 mein constant folding, C9 mein lea reuse, C10 mein tail calls.
  • Debugging with GDB — C8 crash mein live rip confirm karo.
  • CISC vs RISC — kyun C6 ke bytes variable length hain.