Loops at the Instruction Level
for and while are just labels and jumps
- › Explain why hardware has no 'loop' — only compare, jump, and a counter you maintain
- › Translate a for and a while loop into a label + body + conditional back-edge
- › Trace a loop's registers by hand across the first iterations
- › Recognize the loop shape in disassembly and reconstruct the source loop
There is no loop instruction (that matters)
Yesterday: no "if," only cmp + jump. Same story here — there is no real "loop" in the machine. A loop is just a jump that goes backward. You place a label, run a body, and conditionally jump *back up* to the label. The only extra ingredient is a counter you increment yourself and test each time. for, while, do-while — all three are this one idea with the pieces arranged slightly differently.
A while loop, taken apart
Start with the most literal shape:
1int i = 0;2while (i < 5) {3 // body4 i++;5}1 mov ecx, 0 ; i = 0 (i lives in ecx)2.loop:3 cmp ecx, 5 ; test i < 54 jge .done ; if i >= 5, leave the loop5 ; --- body goes here ---6 inc ecx ; i++7 jmp .loop ; back-edge: jump UP to re-test8.done:Four parts, and every loop has them: an init (set the counter), a test at the top (cmp +
conditional jump *out*), the body, and the back-edge (jmp up to the test). The word "loop"
never appears — it's a downward test and an upward jump around a body. The label .loop is the
target the back-edge returns to; .done is the exit.
jmp (or conditional jump) whose
target address is *lower* than the jump itself. Spot a backward jump and you've found a loop; the
label it targets is the loop top, and the conditional jump that can skip past it is the exit test.A for loop is the same, just gathered up
for (init; test; step) is literally a while with the three controls named in one line:
1int sum = 0;2for (int i = 0; i < 4; i++) {3 sum += i;4}1 xor eax, eax ; sum = 0 (xor reg,reg is the idiom for 'set to 0')2 xor ecx, ecx ; i = 03.loop:4 cmp ecx, 4 ; test i < 45 jge .done6 add eax, ecx ; sum += i7 inc ecx ; i++8 jmp .loop9.done: ; sum (in eax) = 0+1+2+3 = 6xor eax, eax sets eax to 0 (anything XOR itself is 0). Compilers use it instead of mov eax, 0
because it's smaller and the CPU treats it specially — you'll see it constantly, so read it as
"zero this register."Trace it, don't trust it
Understanding a loop means being able to *predict its state*. Here's the for above, traced by hand
for each pass — this is the habit that makes loops (and later, exploits) obvious rather than scary:
pass | i (ecx) | i<4? | body: sum += i | sum (eax)
-----+---------+------+----------------+----------
1 | 0 | yes | sum = 0+0 | 0
2 | 1 | yes | sum = 0+1 | 1
3 | 2 | yes | sum = 1+2 | 3
4 | 3 | yes | sum = 3+3 | 6
5 | 4 | NO | exit | 6 ← resulti <= 4 (jg .done becomes... which jump, and how many times does the body
run?). With for (int i = 0; i <= 4; i++), how many passes execute the body, and what final sum?Build a loop, then read one
Two parts. (1) In loop.asm, write a loop that sums the integers 1..10 into a register using the init/test/body/back-edge skeleton from this lesson, and exit with that sum as the exit code (echo $? → 55). (2) Write the same sum as a C for-loop, compile at -O0, disassemble, and find the four parts (init, top test, body, back-edge) plus the backward jump. Everything needed is here: cmp, a conditional jump out, add/inc, and a jmp back.
$ nasm -f elf64 loop.asm -o loop.o && ld loop.o -o loop$ ./loop; echo $? # expect 55$ gcc -O0 -g loopc.c -o loopc && objdump -d -M intel loopc- › Your assembly loop has a clear init, top test, body, and backward jmp
- › echo $? prints 55
- › In the C disassembly you identified the backward jump (the loop's tell)
- › You hand-traced at least the first three iterations before trusting it