Skip to content
Lesson 5.5Executable Maps, Instruction References, and Scans

Instruction Reference: Decisions, Stack, and Bits

Explore comparisons, branches, call stacks, masks, shifts, and debugger stops one instruction at a time.

Lesson 5.5
Read or listenPrint chapter or book
About 35 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 5.5Key ideas, reference and questions

A one-page summary of “Instruction Reference: Decisions, Stack, and Bits” you can keep beside your notes. Plain Markdown.

Lesson 5.5 — Instruction Reference: Decisions, Stack, and Bits: cheatsheet

  • Remember: A stack is last-in, first-out. Keep the return address in the right place and reverse your temporary saves in the right order. Popped bytes may remain in memory without being live stack data.

Quick reference

  • Equal or affordable?: cmp sets subtraction flags; pick signed or unsigned jcc deliberately.
  • Is this bit present?: test value, mask keeps the original value.
  • Where does execution go?: jmp always redirects; jcc reads a condition; neither creates a return address.
  • Where does the helper return?: Near call saves the continuation; ret consumes it.
  • How is a temporary value preserved?: Balance push/pop and the convention's other stack work.
  • How do bits move?: shl fills low bits with zero; shr fills high bits with zero; sar repeats the sign.
  • Which bit operation is needed?: and keeps, or sets, xor toggles, not inverts.
  • Why did execution stop?: nop continues; int3 enters breakpoint-exception handling.

Review

  • Q: A shop checks a gold amount against a cost using a comparison. What changes during that comparison alone? A: The comparison flags change while the gold value stays put
  • Q: A status word contains several independent bits. You want to inspect one bit while keeping every status value intact. What operation is needed? A: A temporary masked comparison that discards its bit result
Download cheatsheet (.md)

A function does more than calculate values: it chooses a path, calls other functions, and returns to the right place. These worked examples use a fictional owned, resettable 32-bit x86 game, Intel syntax, and valid toy memory labels. The traces are small teaching models; they do not execute machine code or attach to a process.

Start with values and arithmetic if registers, brackets, or ZF/SF/CF/OF are unfamiliar. Each example is already populated. Changing an input is optional exploration, not a test.

reg, imm, and r/m retain their meanings from the previous reference: register, encoded number, and register-or-memory. The forms below are common 32-bit forms, not the entire instruction set. Intel’s manuals give encoding, exception, mode, and special-form details.

Meaning: subtract for comparison, keep the flags, and discard the numeric result.

Common forms: cmp reg, r/m/imm, cmp memory, reg/imm. Effects: read operands without writing either; set OF, SF, ZF, AF, CF, and PF as a subtraction would.

mov eax, [gold]
cmp eax, 25
mov ecx, eax ; gold was not spent

Compare gold without spending it. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 100
cmp eax, 25
mov ecx, eax

ecx receives the same 100 gold. A following jump can read the comparison flags because mov does not change them.

Final values: price = 25; eax = 100; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; discarded difference bits = 0x0000004B; ecx = 100.

Read every step of the lesson’s example
  1. Step 1, line 1. Load 100 gold into eax. Nothing has been spent.

    price = 25; eax = 100; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. Compute 100 minus 25 for the flags, then discard the numeric result. eax remains 100; CF=0, ZF=0.

    price = 25; eax = 100; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; discarded difference bits = 0x0000004B.

  3. Step 3, line 3. ecx receives the same 100 gold. A following jump can read the comparison flags because mov does not change them.

    price = 25; eax = 100; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; discarded difference bits = 0x0000004B; ecx = 100.

Takeaway: the flags changed; the compared gold did not.

Meaning: compute a bitwise AND for comparison, then discard its numeric result.

Common forms: test r/m, reg or test r/m, imm. Effects: leave operands unchanged; set ZF, SF, and PF from the temporary AND, clear CF/OF, and leave AF undefined. test eax, eax asks whether the complete value is zero; test eax, 4 examines bit 2.

mov eax, [status]
test eax, 4 ; bit 2 means stunned here
mov ecx, eax ; all status bits survive

Inspect a status bit without clearing the others. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
test eax, 4
mov ecx, eax

The original status 5 is still in both eax and ecx. The flags, not a changed status word, report the test.

Final values: status = 5; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; discarded AND result = 4; ecx = 5.

Read every step of the lesson’s example
  1. Step 1, line 1. eax contains all status bits. Testing one bit must not destroy the others.

    status = 5; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  2. Step 2, line 2. Compute the temporary AND result 4, then discard it. ZF=0 answers whether bit 2 is clear; CF and OF become zero.

    status = 5; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; discarded AND result = 4.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  3. Step 3, line 3. The original status 5 is still in both eax and ecx. The flags, not a changed status word, report the test.

    status = 5; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; discarded AND result = 4; ecx = 5.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

Takeaway: a mask can inspect a field without deleting its other bits.

Meaning: continue at another instruction unconditionally.

Common near forms: jmp label uses an encoded relative displacement; jmp reg32/memory32 reads an indirect destination. Effects: change the instruction pointer; do not create a return address or alter arithmetic flags. Memory-indirect forms read the pointer.

mov eax, 5
jmp ready
mov eax, 99 ; skipped on this path
ready: mov [ammo], eax

Skip an instruction unconditionally. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
jmp ready
mov eax, 99
ready: mov [ammo], eax

The skipped assignment never ran. Ammo receives five even though the skipped line would have written 99.

Final values: skipped = 99; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; next line = 4; ammo = 5.

Read every step of the lesson’s example
  1. Step 1, line 1. eax starts at five. The next instruction chooses where execution continues.

    skipped = 99; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. jmp sets the instruction pointer to ready. It does not test or change the comparison flags.

    skipped = 99; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; next line = 4.

  3. Step 3, line 4. The skipped assignment never ran. Ammo receives five even though the skipped line would have written 99.

    skipped = 99; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; next line = 4; ammo = 5.

    Memory: +0 = 05 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: a nearby instruction is not necessarily an executed instruction.

Meaning: jump only when the selected flag condition is true; otherwise continue with the next instruction.

jcc is a name for the family, not a literal mnemonic to type. Common form: a specific conditional mnemonic such as jb label. Effects: read the specified flags and potentially change the instruction pointer; preserve registers, arithmetic flags, and game memory. Short and near relative forms have different reach and encoded lengths.

mov eax, [gold]
cmp eax, 25
jb cannot_buy ; unsigned gold is below cost
sub eax, 25 ; taken only on the other path

Here cannot_buy names an exit path outside the excerpt. The interactive shows both paths completely and lets you change the conditional mnemonic.

Choose a branch by interpreting the same flags. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, -1
cmp eax, 2
jl selected
mov [chosen], 0
jmp done
selected: mov [chosen], 1
done:

The result is chosen=1. A signed comparison and an unsigned comparison can legitimately choose different paths for identical bits.

Final values: case = signed; jump = jl; eax = -1; rhs = 2; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 0; taken = true; chosen = 1.

Read every step of the lesson’s example
  1. Step 1, line 1. eax holds 0xFFFFFFFF, signed -1 or unsigned 4294967295. The branch mnemonic decides which interpretation to use.

    case = signed; jump = jl; eax = -1; rhs = 2; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. cmp keeps eax, discards 0xFFFFFFFD, and sets ZF=0, SF=1, CF=0, OF=0, PF=0.

    case = signed; jump = jl; eax = -1; rhs = 2; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 0.

  3. Step 3, line 3. jl asks for signed less. Its condition is true; jump to selected. No flags change.

    case = signed; jump = jl; eax = -1; rhs = 2; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 0; taken = true.

  4. Step 4, line 6. Only the selected path writes chosen=1.

    case = signed; jump = jl; eax = -1; rhs = 2; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 0; taken = true; chosen = 1.

  5. Step 5, line 7. The result is chosen=1. A signed comparison and an unsigned comparison can legitimately choose different paths for identical bits.

    case = signed; jump = jl; eax = -1; rhs = 2; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 0; taken = true; chosen = 1.

Mnemonic and common aliases Condition Interpretation after cmp left, right
je / jz ZF = 1 equal
jne / jnz ZF = 0 unequal
jb / jnae / jc CF = 1 unsigned below / carry
jae / jnb / jnc CF = 0 unsigned at least / no carry
jbe / jna CF = 1 or ZF = 1 unsigned below or equal
ja / jnbe CF = 0 and ZF = 0 unsigned above
jl / jnge SF ≠ OF signed less
jge / jnl SF = OF signed at least
jle / jng ZF = 1 or SF ≠ OF signed less or equal
jg / jnle ZF = 0 and SF = OF signed greater
jo / jno OF = 1 / OF = 0 signed overflow present / absent
js / jns SF = 1 / SF = 0 top result bit set / clear
jp / jpe, jnp / jpo PF = 1 / PF = 0 even / odd low-byte parity

jcxz/jecxz test a counter register rather than these flags. Do not apply this table blindly to them, or to flags produced by a floating-point comparison in the next reference.

Takeaway: find the producer of the flags and the type intended by the branch before naming the condition.

The stack is a thread’s working storage for active calls and temporary data. In these ordinary 32-bit examples it grows toward lower addresses. Saving four bytes lowers esp by four; removing them advances it by four. Real functions also follow a calling convention for arguments, saved registers, alignment, and cleanup.

Meaning: save where execution should resume, then enter another function.

Common near forms: call label or call reg32/memory32. Effects: push the address immediately after the call onto the stack, update esp, and change the instruction pointer. A near call does not itself change arithmetic flags. The callee’s instructions can change registers, flags, and memory afterward.

mov eax, [gold]
call grant_coin ; our helper adds one in eax
mov [gold], eax ; resume here after its ret

Save a continuation before entering the helper. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 10
call grant_coin
mov [gold], eax
jmp done
grant_coin: add eax, 1
ret
done:

The round trip ends with 11 gold and the original esp. The saved stack bytes can remain, but they are no longer an active return address.

Final values: gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = done.

Read every step of the lesson’s example
  1. Step 1, line 1. The caller loads gold. esp starts at an illustrative address; no argument passing convention is being modeled.

    gold = 10; eax = 10; esp = 0x00008000; continuation = not saved yet; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. A near 32-bit call lowers esp by four, stores the address of the instruction after call, then enters grant_coin.

    gold = 10; eax = 10; esp = 0x00007FFC; continuation = line 3: store gold; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eip = grant_coin.

    Memory: 0x00007FFC = line 3: store gold (current top: esp points here).

  3. Step 3, line 5. The helper adds one coin. The return address is still on top of the stack.

    gold = 10; eax = 11; esp = 0x00007FFC; continuation = line 3: store gold; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = grant_coin.

    Memory: 0x00007FFC = line 3: store gold (current top: esp points here).

  4. Step 4, line 6. ret reads the saved address, advances esp by four, and resumes line 3. It does not itself put the result in eax.

    gold = 10; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = line 3: store gold.

    Memory: 0x00007FFC = line 3: store gold (inactive slot: esp is above it).

  5. Step 5, line 3. The caller stores 11 gold. eax carries the helper's result because this example chose that convention.

    gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = line 3: store gold.

    Memory: +0 = 0B (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

  6. Step 6, line 4. Jump past the helper body; otherwise ordinary fall-through could enter it a second time.

    gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = done.

    Memory: +0 = 0B (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

  7. Step 7, line 7. The round trip ends with 11 gold and the original esp. The saved stack bytes can remain, but they are no longer an active return address.

    gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = done.

    Memory: +0 = 0B (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: a call saves a continuation; it does not guarantee what the helper will return or preserve.

Meaning: read the saved return address from the stack and resume the caller there.

Common near forms: ret; ret imm16 additionally removes a fixed argument-byte count after popping the return address. Effects: update the instruction pointer and esp; leave arithmetic flags alone. Returning a value in eax is a calling-convention choice, not work performed by ret itself.

grant_coin:
add eax, 1
ret ; eax was changed by add, not ret
call saves the way back; ret takes itThe caller reads gold and calls grant_coin. The stack holds the address of the instruction after the call until ret removes it.
  1. 1 Before the call: gold is in eax

    Registers, then the stack
    registersstack: S − 4, then S
    esp = S
    eax = gold
    freeS − 4
    callerdata at S

    The caller has read gold. esp points at S, the top of the caller’s stack.

  2. 2 Inside grant_coin, after add

    Registers, then the stack
    registersstack: S − 4, then S
    esp = S − 4
    eax = gold + 1
    return addressthe mov after the call
    callerdata at S

    call lowered esp by 4 and stored the address of the next mov at S − 4. The helper then added one to eax.

  3. 3 ret: pop the address and resume

    Registers, then the stack
    registersstack: S − 4, then S
    esp = S
    eax = gold + 1
    return addresspopped; resume there
    callerdata at S

    ret reads that address, raises esp by 4 and continues at mov [gold], eax.

The caller reads gold and calls grant_coin. The stack holds the address of the instruction after the call until ret removes it.

Resume the caller from the stack. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 10
call grant_coin
mov [gold], eax
jmp done
grant_coin: add eax, 1
ret
done:

The round trip ends with 11 gold and the original esp. The saved stack bytes can remain, but they are no longer an active return address.

Final values: gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = done.

Read every step of the lesson’s example
  1. Step 1, line 1. The caller loads gold. esp starts at an illustrative address; no argument passing convention is being modeled.

    gold = 10; eax = 10; esp = 0x00008000; continuation = not saved yet; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. A near 32-bit call lowers esp by four, stores the address of the instruction after call, then enters grant_coin.

    gold = 10; eax = 10; esp = 0x00007FFC; continuation = line 3: store gold; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eip = grant_coin.

    Memory: 0x00007FFC = line 3: store gold (current top: esp points here).

  3. Step 3, line 5. The helper adds one coin. The return address is still on top of the stack.

    gold = 10; eax = 11; esp = 0x00007FFC; continuation = line 3: store gold; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = grant_coin.

    Memory: 0x00007FFC = line 3: store gold (current top: esp points here).

  4. Step 4, line 6. ret reads the saved address, advances esp by four, and resumes line 3. It does not itself put the result in eax.

    gold = 10; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = line 3: store gold.

    Memory: 0x00007FFC = line 3: store gold (inactive slot: esp is above it).

  5. Step 5, line 3. The caller stores 11 gold. eax carries the helper's result because this example chose that convention.

    gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = line 3: store gold.

    Memory: +0 = 0B (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

  6. Step 6, line 4. Jump past the helper body; otherwise ordinary fall-through could enter it a second time.

    gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = done.

    Memory: +0 = 0B (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

  7. Step 7, line 7. The round trip ends with 11 gold and the original esp. The saved stack bytes can remain, but they are no longer an active return address.

    gold = 11; eax = 11; esp = 0x00008000; continuation = consumed; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; eip = done.

    Memory: +0 = 0B (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: an unbalanced stack can make ret read data as an address; restoring one register is not enough.

Meaning: place a value on top of the stack.

Common 32-bit forms: push reg32/memory32/imm. Effects: lower esp by four and write the saved value there; preserve ordinary arithmetic flags. These widths are specific to this example: a normal 64-bit push usually uses an eight-byte slot.

mov ebx, [health]
push ebx ; preserve this value
mov ebx, 0 ; temporary work
pop ebx
mov [health], ebx

Save a temporary register value on the stack. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov ebx, 80
push ebx
mov ebx, 0
pop ebx
mov [health], ebx

Store the restored health value 80. The push/pop pair balanced esp and preserved the flags.

Final values: health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

Read every step of the lesson’s example
  1. Step 1, line 1. Load health into ebx. The illustrative 32-bit stack grows toward lower addresses.

    health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. push lowers esp by four and writes the current ebx value into that stack slot.

    health = 80; ebx = 80; esp = 0x00007FFC; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: 0x00007FFC = 80 (current top: esp points here).

  3. Step 3, line 3. Reuse ebx for temporary work. The saved copy remains on the stack.

    health = 80; ebx = 0; esp = 0x00007FFC; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: 0x00007FFC = 80 (current top: esp points here).

  4. Step 4, line 4. pop reads the top slot into ebx, then advances esp by four. It does not erase the old memory.

    health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: 0x00007FFC = 80 (inactive slot: esp is above it).

  5. Step 5, line 5. Store the restored health value 80. The push/pop pair balanced esp and preserved the flags.

    health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: +0 = 50 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: stack storage can preserve a value while its register is reused.

Meaning: copy the top stack value to a destination and remove that slot from the active stack.

Common forms: pop reg32/memory32. Effects: read stack memory, write the destination, and normally advance esp by four. Arithmetic flags survive. Special destinations involving esp need their exact manual rules; the example uses ebx. The old memory bytes are not erased.

mov ebx, [health]
push ebx
mov ebx, 0
pop ebx ; recover the last saved value
mov [health], ebx

Recover the last saved value. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov ebx, 80
push ebx
mov ebx, 0
pop ebx
mov [health], ebx

Store the restored health value 80. The push/pop pair balanced esp and preserved the flags.

Final values: health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

Read every step of the lesson’s example
  1. Step 1, line 1. Load health into ebx. The illustrative 32-bit stack grows toward lower addresses.

    health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. push lowers esp by four and writes the current ebx value into that stack slot.

    health = 80; ebx = 80; esp = 0x00007FFC; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: 0x00007FFC = 80 (current top: esp points here).

  3. Step 3, line 3. Reuse ebx for temporary work. The saved copy remains on the stack.

    health = 80; ebx = 0; esp = 0x00007FFC; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: 0x00007FFC = 80 (current top: esp points here).

  4. Step 4, line 4. pop reads the top slot into ebx, then advances esp by four. It does not erase the old memory.

    health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: 0x00007FFC = 80 (inactive slot: esp is above it).

  5. Step 5, line 5. Store the restored health value 80. The push/pop pair balanced esp and preserved the flags.

    health = 80; ebx = 80; esp = 0x00008000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: +0 = 50 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: removing a slot changes which bytes are active, not whether those bytes still contain an old value.

Meaning: shift bits left, filling the newly opened low positions with zeros.

Common forms: shl r/m32, imm8 or shl r/m32, cl; sal is an alias. Effects: write the operand; CF records the last bit shifted out, ZF/SF/PF describe the result, and AF is undefined for a nonzero effective count. OF has a defined shift result only for a count of one. An effective count of zero preserves value and flags. For a 32-bit operand, the count is masked to its low five bits.

mov eax, [packed_value]
shl eax, 1
mov [packed_value], eax

Shift a packed value left. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
shl eax, 1
mov [packed], eax

Store 0x0000000A. Its signed reading is 10. Left shift keeps only the bits that fit in the selected width.

Final values: value = 5; count = 1; eax = 10; signed reading = 10; eax bits = 0x0000000A; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; packed = 10.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0x00000005 (5 as signed). The illustrative previous CF and OF are both one.

    value = 5; count = 1; eax = 5; signed reading = 5; eax bits = 0x00000005; ZF = 0; SF = 0; CF = 1; OF = 1; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  2. Step 2, line 2. shl shifts 1 place left, filling new positions with zero. The last bit lost becomes CF=0; OF=0.

    value = 5; count = 1; eax = 10; signed reading = 10; eax bits = 0x0000000A; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: bit 3 = 1 (low four bits only); bit 2 = 0 (low four bits only); bit 1 = 1 (low four bits only); bit 0 = 0 (low four bits only).

  3. Step 3, line 3. Store 0x0000000A. Its signed reading is 10. Left shift keeps only the bits that fit in the selected width.

    value = 5; count = 1; eax = 10; signed reading = 10; eax bits = 0x0000000A; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; packed = 10.

    Memory: +0 = 0A (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: left shifting resembles multiplication by a power of two only while the needed bits are retained.

Meaning: shift bits right, filling the newly opened high positions with zeros.

Common forms: shr r/m32, imm8 or shr r/m32, cl. Effects: write the operand and use the shift flag/count rules above; for a one-bit shift, OF receives the old highest bit. An unsigned value is divided by a power of two with the discarded fractional part lost.

mov eax, [packed_value]
shr eax, 1
mov [packed_value], eax

Shift a bit pattern right with zero fill. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, -12
shr eax, 1
mov [packed], eax

Store 0x7FFFFFFA. Its signed reading is 2147483642. Zero-fill right shift treats the original bit pattern as unsigned.

Final values: value = -12; count = 1; eax = 2147483642; signed reading = 2147483642; eax bits = 0x7FFFFFFA; ZF = 0; SF = 0; CF = 0; OF = 1; PF = 1; packed = 2147483642.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0xFFFFFFF4 (-12 as signed). The illustrative previous CF and OF are both one.

    value = -12; count = 1; eax = 4294967284; signed reading = -12; eax bits = 0xFFFFFFF4; ZF = 0; SF = 0; CF = 1; OF = 1; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 0 (low four bits only).

  2. Step 2, line 2. shr shifts 1 place right, filling new positions with zero. The last bit lost becomes CF=0; OF=1.

    value = -12; count = 1; eax = 2147483642; signed reading = 2147483642; eax bits = 0x7FFFFFFA; ZF = 0; SF = 0; CF = 0; OF = 1; PF = 1.

    Memory: bit 3 = 1 (low four bits only); bit 2 = 0 (low four bits only); bit 1 = 1 (low four bits only); bit 0 = 0 (low four bits only).

  3. Step 3, line 3. Store 0x7FFFFFFA. Its signed reading is 2147483642. Zero-fill right shift treats the original bit pattern as unsigned.

    value = -12; count = 1; eax = 2147483642; signed reading = 2147483642; eax bits = 0x7FFFFFFA; ZF = 0; SF = 0; CF = 0; OF = 1; PF = 1; packed = 2147483642.

    Memory: +0 = FA (little-endian byte); +1 = FF (little-endian byte); +2 = FF (little-endian byte); +3 = 7F (little-endian byte).

Takeaway: zero-fill right shift does not preserve a negative signed value’s sign.

Meaning: shift bits right while repeating the original sign bit in the new high positions.

Common forms: sar r/m32, imm8 or sar r/m32, cl. Effects: write the operand and follow the shift flag/count rules; for a one-bit shift, OF is cleared. Signed negative values round toward minus infinity when low bits are discarded. That differs from idiv truncation toward zero: −3 shifted right once becomes −2, but −3 divided by 2 becomes −1.

mov eax, [signed_delta]
sar eax, 1
mov [signed_delta], eax

Shift a signed movement delta right. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, -12
sar eax, 1
mov [packed], eax

Store 0xFFFFFFFA. Its signed reading is -6. For a nonzero count, signed right shift rounds negative values downward when discarded bits are nonzero.

Final values: value = -12; count = 1; eax = 4294967290; signed reading = -6; eax bits = 0xFFFFFFFA; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 1; packed = 4294967290.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0xFFFFFFF4 (-12 as signed). The illustrative previous CF and OF are both one.

    value = -12; count = 1; eax = 4294967284; signed reading = -12; eax bits = 0xFFFFFFF4; ZF = 0; SF = 0; CF = 1; OF = 1; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 0 (low four bits only).

  2. Step 2, line 2. sar shifts 1 place right, repeating the sign bit. The last bit lost becomes CF=0; OF=0.

    value = -12; count = 1; eax = 4294967290; signed reading = -6; eax bits = 0xFFFFFFFA; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 1.

    Memory: bit 3 = 1 (low four bits only); bit 2 = 0 (low four bits only); bit 1 = 1 (low four bits only); bit 0 = 0 (low four bits only).

  3. Step 3, line 3. Store 0xFFFFFFFA. Its signed reading is -6. For a nonzero count, signed right shift rounds negative values downward when discarded bits are nonzero.

    value = -12; count = 1; eax = 4294967290; signed reading = -6; eax bits = 0xFFFFFFFA; ZF = 0; SF = 1; CF = 0; OF = 0; PF = 1; packed = 4294967290.

    Memory: +0 = FA (little-endian byte); +1 = FF (little-endian byte); +2 = FF (little-endian byte); +3 = FF (little-endian byte).

Takeaway: sign preservation and rounding are both part of a signed shift’s meaning.

Meaning: keep a one bit only where both input bits are one.

Common forms: and reg, r/m/imm, and memory, reg/imm. Effects: write the destination; clear CF/OF, set ZF/SF/PF from the result, and leave AF undefined.

mov eax, [status]
and eax, 3 ; keep only bits 0 and 1
mov [status], eax

Keep only the selected status bits. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
and eax, 3
mov [status], eax

Store 0x00000001 in status. ZF describes whether the complete result is zero, not whether one chosen status bit is clear.

Final values: value = 5; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; status = 1.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0x00000005. Previous CF and ZF are deliberately one to make flag preservation visible.

    value = 5; eax = 5; eax bits = 0x00000005; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  2. Step 2, line 2. A bit survives only where both inputs have one.

    value = 5; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 0 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  3. Step 3, line 3. Store 0x00000001 in status. ZF describes whether the complete result is zero, not whether one chosen status bit is clear.

    value = 5; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; status = 1.

    Memory: +0 = 01 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: and writes the masked word; test only inspects a temporary masked result.

Meaning: set a result bit when either input bit is one.

Common forms: or reg, r/m/imm, or memory, reg/imm. Effects: write the destination; clear CF/OF, set ZF/SF/PF from the result, and leave AF undefined.

mov eax, [status]
or eax, 4 ; ensure bit 2 is set
mov [status], eax

Set a status bit without clearing others. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
or eax, 4
mov [status], eax

Store 0x00000005 in status. ZF describes whether the complete result is zero, not whether one chosen status bit is clear.

Final values: value = 5; eax = 5; eax bits = 0x00000005; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; status = 5.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0x00000005. Previous CF and ZF are deliberately one to make flag preservation visible.

    value = 5; eax = 5; eax bits = 0x00000005; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  2. Step 2, line 2. A bit becomes one if either input has one.

    value = 5; eax = 5; eax bits = 0x00000005; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  3. Step 3, line 3. Store 0x00000005 in status. ZF describes whether the complete result is zero, not whether one chosen status bit is clear.

    value = 5; eax = 5; eax bits = 0x00000005; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; status = 5.

    Memory: +0 = 05 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: setting a bit twice with or leaves it set both times.

Meaning: set a result bit when its two input bits differ.

Common forms: xor reg, r/m/imm, xor memory, reg/imm. Effects: write the destination; clear CF/OF, set ZF/SF/PF from the result, and leave AF undefined. xor eax, eax produces zero and changes flags; it is not interchangeable with every use of mov eax, 0.

mov eax, [status]
xor eax, 4 ; toggle bit 2
mov [status], eax

Toggle one status bit. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
xor eax, 4
mov [status], eax

Store 0x00000001 in status. ZF describes whether the complete result is zero, not whether one chosen status bit is clear.

Final values: value = 5; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; status = 1.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0x00000005. Previous CF and ZF are deliberately one to make flag preservation visible.

    value = 5; eax = 5; eax bits = 0x00000005; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  2. Step 2, line 2. A bit becomes one when the two input bits differ.

    value = 5; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 0 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  3. Step 3, line 3. Store 0x00000001 in status. ZF describes whether the complete result is zero, not whether one chosen status bit is clear.

    value = 5; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; status = 1.

    Memory: +0 = 01 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: toggling a bit twice returns it to its original state.

Meaning: invert every bit of the operand.

Common form: not r/m. Effects: write the register or memory operand and preserve all arithmetic flags. This is bitwise complement, not a Boolean comparison.

mov eax, [status]
not eax ; invert all 32 bits here
mov [status], eax

Invert every bit in the word. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 5
not eax
mov [status], eax

Store 0xFFFFFFFA in status. The prior comparison flags still survive.

Final values: value = 5; eax = 4294967290; eax bits = 0xFFFFFFFA; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1; status = 4294967290.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with 0x00000005. Previous CF and ZF are deliberately one to make flag preservation visible.

    value = 5; eax = 5; eax bits = 0x00000005; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1.

    Memory: bit 3 = 0 (low four bits only); bit 2 = 1 (low four bits only); bit 1 = 0 (low four bits only); bit 0 = 1 (low four bits only).

  2. Step 2, line 2. Every bit flips, including the 28 upper bits absent from these boxes. Unlike and/or/xor, not leaves the flags alone.

    value = 5; eax = 4294967290; eax bits = 0xFFFFFFFA; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1.

    Memory: bit 3 = 1 (low four bits only); bit 2 = 0 (low four bits only); bit 1 = 1 (low four bits only); bit 0 = 0 (low four bits only).

  3. Step 3, line 3. Store 0xFFFFFFFA in status. The prior comparison flags still survive.

    value = 5; eax = 4294967290; eax bits = 0xFFFFFFFA; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1; status = 4294967290.

    Memory: +0 = FA (little-endian byte); +1 = FF (little-endian byte); +2 = FF (little-endian byte); +3 = FF (little-endian byte).

Takeaway: not flips the entire selected width, including bits a small diagram may not show.

Meaning: advance to the next instruction without performing a game-data operation.

Common form: nop; multi-byte no-op encodings also exist. Effects: leave general registers, arithmetic flags, and game memory unchanged; execution still advances and takes time. Replacing code with no-ops must preserve whole instruction boundaries and the surrounding program’s assumptions.

mov eax, [ammo]
nop
sub eax, 1 ; this later instruction still runs
mov [ammo], eax

Do nothing for one instruction, then continue. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 6
nop
sub eax, 1
mov [ammo], eax

The final ammo is 5. A nop is not a pause, a return, or a guarantee that later code will be safe.

Final values: ammo = 5; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

Read every step of the lesson’s example
  1. Step 1, line 1. Load ammo. The illustrative prior CF is one.

    ammo = 6; eax = 6; ZF = 0; SF = 0; CF = 1; OF = 0; PF = 1.

  2. Step 2, line 2. nop advances to the next instruction without changing general registers, comparison flags, or game memory. Time still passes.

    ammo = 6; eax = 6; ZF = 0; SF = 0; CF = 1; OF = 0; PF = 1.

  3. Step 3, line 3. The following sub, not nop, spends one ammo and produces fresh arithmetic flags.

    ammo = 6; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  4. Step 4, line 4. The final ammo is 5. A nop is not a pause, a return, or a guarantee that later code will be safe.

    ammo = 5; eax = 5; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: +0 = 05 (little-endian byte); +1 = 00 (little-endian byte); +2 = 00 (little-endian byte); +3 = 00 (little-endian byte).

Takeaway: doing nothing for one instruction does not stop the following instructions.

Meaning: raise the breakpoint exception used by a debugger.

Form here: int3, the one-byte CC instruction. Effects: transfer control through exception handling; the saved instruction pointer is after that byte. Hardware and the operating system manage exception-entry state, rather than producing an ordinary arithmetic comparison result. A debugger may pause and inspect the saved context; an unhandled exception can end the program.

mov eax, [ammo]
int3 ; owned debug build, intentional stop
sub eax, 1
mov [ammo], eax

Raise a breakpoint exception before the next action. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 6
int3
sub eax, 1
mov [ammo], eax

Inspection shows 6 ammo, not 5. Exception-entry state and the operating-system handler are outside this small model.

Final values: ammo = 6; action = pause; eax = 6; state = paused for inspection; saved next instruction = sub eax, 1.

Read every step of the lesson’s example
  1. Step 1, line 1. The toy program loads ammo before reaching its one-byte CC breakpoint instruction.

    ammo = 6; action = pause; eax = 6; state = running.

  2. Step 2, line 2. int3 transfers control through breakpoint-exception handling. The saved instruction pointer is after int3; the later sub has not run.

    ammo = 6; action = pause; eax = 6; state = breakpoint exception; saved next instruction = sub eax, 1.

  3. Step 3, line 2. Inspection shows 6 ammo, not 5. Exception-entry state and the operating-system handler are outside this small model.

    ammo = 6; action = pause; eax = 6; state = paused for inspection; saved next instruction = sub eax, 1.

Takeaway: a breakpoint is an exception with a continuation decision, not an automatic no-op.

Question Useful operation or rule
Equal or affordable? cmp sets subtraction flags; pick signed or unsigned jcc deliberately.
Is this bit present? test value, mask keeps the original value.
Where does execution go? jmp always redirects; jcc reads a condition; neither creates a return address.
Where does the helper return? Near call saves the continuation; ret consumes it.
How is a temporary value preserved? Balance push/pop and the convention’s other stack work.
How do bits move? shl fills low bits with zero; shr fills high bits with zero; sar repeats the sign.
Which bit operation is needed? and keeps, or sets, xor toggles, not inverts.
Why did execution stop? nop continues; int3 enters breakpoint-exception handling.

Next, read the scalar floating-point reference. For debugger handling rather than intentional breakpoint instructions, see Debug Events and Software Breakpoints.

Knowledge check

Compare before changing

A shop checks a gold amount against a cost using a comparison. What changes during that comparison alone?

Finished Lesson 5.5?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 5: 11 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.