cmp, Conditional Jumps & if
How a decision becomes a jump
- › Explain what cmp does and why it changes no register
- › Turn an if-statement into a cmp plus a conditional jump, by hand
- › Choose the right jump (je/jne/jg/jl/ja/jb) and say why signed and unsigned differ
- › Read an if/else in disassembly and reconstruct the C condition
There is no "if" in a CPU
A processor cannot branch on a condition the way you write if (x > 0). It can do exactly two
relevant things: arithmetic that sets flags, and jumps that read flags. An if is those two
glued together. Once you see that, every conditional in every language becomes readable.
cmp: ask a question, keep your data
Yesterday you met it: `cmp a, b` computes a - b, sets ZF/SF/CF/OF, and *discards the
result*. It's a subtraction whose only purpose is the receipt. You use it to *ask a question about
two values without disturbing them*:
cmp rax, rbxthen the flags encode how rax compares to rbx.- If
rax == rbx, the subtraction is 0 → ZF=1. - If
rax < rbx(unsigned), the subtraction borrows → CF=1. - Signed comparisons read SF and OF together.
The jump reads the receipt
A conditional jump looks at the flags and either jumps to a label or falls through. Its mnemonic tells you the condition it tests:
| Jump | Jumps if… | Flags | Signedness |
|---|---|---|---|
| je / jz | equal (zero) | ZF=1 | either |
| jne / jnz | not equal | ZF=0 | either |
| jg | greater | signed > | signed |
| jl | less | signed < | signed |
| jge / jle | greater/less or equal | signed | signed |
| ja | above | unsigned > | unsigned |
| jb | below | unsigned < | unsigned |
jg/jl for signed, ja/jb for
unsigned — even though the cmp before them is identical. Why: the *bits* don't say whether 0xFF
means 255 or −1 (day 1 again). jg reads SF and OF (the signed verdict); ja reads CF and ZF (the
unsigned verdict). Pick the wrong family and −1 > 5 can come out "true." This is a real, common
bug — and a real exploit primitive later.An if, compiled by hand
Take this C:
1if (x > 10) {2 y = 1;3} else {4 y = 0;5}Assuming x is a signed int in eax and y lives in ecx, the machine version is:
1 cmp eax, 10 ; compute x - 10, set flags (x is signed → use signed jump)2 jg .then ; if x > 10, jump to the 'then' block3 mov ecx, 0 ; else: y = 04 jmp .end5.then:6 mov ecx, 1 ; then: y = 17.end:Read it top to bottom: compare, conditional jump to one branch, the other branch falls
through, and an unconditional jmp skips past the branch not taken. That skeleton — cmp, conditional
jump, two blocks, a joining label — *is* an if/else. Every one you've ever written looks like this
underneath.
x is an unsigned int in eax holding 0xFFFFFFFF (which is 4294967295 unsigned, or −1 if you
misread it as signed). The code does cmp eax, 10 then `jg .then`. Does it jump? Should it?jg/jl/jge/jle → the C used signed types; ja/jb/jae/jbe → unsigned
types. The jump family tells you the *sign* of the variables, not just the comparison.Compile an if in your head, then check
Write branch.c containing a function that returns 1 if its int argument is greater than 10 and 0 otherwise. FIRST, on paper, write the cmp + conditional-jump assembly you expect (using a signed jump, since int is signed). THEN compile with `gcc -O0 -g`, disassemble with objdump -d -M intel, and compare your hand-written version to the compiler's. Finally change the parameter type to `unsigned` and confirm the jump mnemonic changes from a signed one (jg) to an unsigned one (ja).
$ gcc -O0 -g branch.c -o branch$ objdump -d -M intel branch- › Your hand-written cmp + jump matches the shape the compiler produced
- › The signed version uses jg (or jle); the unsigned version uses ja (or jbe)
- › You can explain why the mnemonic changed when only the type changed
- › You can point at the unconditional jmp that skips the untaken branch