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

Instruction Reference: Values and Integer Arithmetic

Look up common x86 value operations, then explore each one with a worked trace.

Lesson 5.4
Read or listenPrint chapter or book
About 28 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.4Key ideas, reference and questions

A one-page summary of “Instruction Reference: Values and Integer Arithmetic” you can keep beside your notes. Plain Markdown.

Lesson 5.4 — Instruction Reference: Values and Integer Arithmetic: cheatsheet

  • Remember: Track which instruction most recently wrote the flag you need. A later copy may preserve a useful comparison; a later arithmetic operation may replace it.

Quick reference

  • Copy a value: mov writes a destination; source and flags survive.
  • Calculate a location: lea computes an address without reading the field.
  • Update a total: add/sub write a result and fresh arithmetic flags.
  • Count by one: inc/dec update most arithmetic flags but preserve CF.
  • Multiply signed values: imul forms choose implicit or explicit destinations; CF/OF report signed truncation.
  • Divide signed values: idiv r/m32 uses edx:eax, returns quotient/remainder, and may raise divide error.
  • Widen a field: movzx fills with zeros; movsx repeats the sign bit.
  • Interpret a boundary: CF is unsigned carry/borrow; OF is signed overflow.

Review

  • Q: A game copies an ammo count from one register into another. What happens to the original register? A: It keeps its value while the destination receives a copy
  • Q: Code calculates the address of a player field but performs no load there. What has it learned? A: A candidate location, without learning the field value yet
Download cheatsheet (.md)

Use this reference when an instruction in your own resettable lab is unfamiliar. Start with its plain-English meaning, then follow its worked trace. You can change the inputs; nothing needs to be memorized or completed before continuing.

These examples use 32-bit x86, Intel syntax: destination first. A label such as [gold] stands for a valid address in our fictional game. It is not an address to paste into another process. The forms below cover common general-purpose uses, rather than every encoding or special-register form.

The authoritative details are in Intel’s Software Developer’s Manuals, especially Volume 2. The links in this table open the relevant entry in revision 093 of the instruction reference. A mnemonic names an operation; it does not identify one fixed opcode or instruction length.

In the forms below, reg means a register, imm means a number encoded in the instruction, and r/m means either a register or a memory operand. Memory uses brackets. The operand widths must match the selected instruction form; byte ptr, word ptr, and dword ptr make one, two, or four bytes explicit.

Addressing: base + index × scale + displacement

Section titled “Addressing: base + index × scale + displacement”

An expression such as [esi+ecx*4+8] identifies a location. esi is the base, ecx is an index, 4 is the scale, and 8 is the displacement, a fixed extra distance in bytes. In these usual 32-bit addressing forms, the encoded scale is 1, 2, 4, or 8; some base/index combinations have encoding restrictions.

With base 0x1000, index 2, and a scale of 4, the address is 0x1000 + 2×4 + 8 = 0x1010. A mov with this memory source reads bytes there. A lea with the same expression only calculates the address.

One address calculation, two different instructionsBoth forms compute 0x1010. lea stops there; mov goes on to read the memory at that address.
  1. 1 Base plus index × scale: 0x1000 + 8

    Running address
    0x1000base
    + 8index 2 × scale 4
    0x1008

    The base register holds 0x1000. The index register holds 2 and the scale is 4, so 2 × 4 = 8 bytes are added.

  2. 2 Add the displacement: 0x1008 + 8

    Running address
    0x1008
    + 8displacement
    0x1010effective address

    The constant displacement adds another eight bytes, giving the effective address.

  3. 3 The path splits at 0x1010

    lea

    Register receives
    0x1010the address itself

    mov

    Register receives
    bytes at 0x1010the contents

    lea keeps the address as a number. mov uses it to read the bytes stored there.

Both forms compute 0x1010. lea stops there; mov goes on to read the memory at that address.

Addresses do not declare record sizes. If each player occupies 16 bytes, an index must advance by 16 bytes per player, perhaps by an earlier multiplication. Also distinguish operand size from address size: reading a four-byte field does not mean that four bytes make up the whole object.

Many integer operations update bits in eflags. The trace panels use these names:

Flag What it records for ordinary addition/subtraction
ZF, zero The stored-width result is zero.
SF, sign The result’s highest bit is one. That bit is the sign when the value is interpreted as signed.
CF, carry Addition carried out of the unsigned width, or subtraction needed an unsigned borrow.
OF, overflow The mathematical signed result cannot fit the selected signed width.

CF and OF answer different questions. Adding one to 0xFFFFFFFF wraps the 32-bit result to zero and sets CF, while signed −1 + 1 fits and does not set OF. Adding one to 0x7FFFFFFF sets OF because signed 2,147,483,648 does not fit, while the unsigned sum fits and does not set CF.

Some entries also mention PF: it reports even parity in the low result byte for these integer operations. AF records carry or borrow from bit 3 into bit 4. These two exist even when a game rule does not use them. An undefined flag is not an answer you may rely on; it is not a promise of zero. A preserved flag still describes an earlier operation.

Meaning: copy a value from the source to the destination, leaving the source available.

Common forms: mov reg, reg/imm/memory, mov memory, reg/imm. Ordinary mov does not copy memory directly to memory. Effects: write the destination register or memory at the selected width; leave arithmetic flags unchanged.

mov eax, [ammo] ; load the four-byte field
mov ecx, eax ; copy the register value
mov [display_ammo], ecx ; store the copy

Copy ammo through registers into memory. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, [ammo]
mov ecx, eax
mov [display_ammo], ecx

Copy ecx into the display field. Both registers and the original ammo still contain 12.

Final values: ammo = 12; eax = 12; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; ecx = 12; display_ammo = 12.

Read every step of the lesson’s example
  1. Step 1, line 1. Read four bytes from ammo and copy their value into eax. The source bytes stay in place.

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

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

  2. Step 2, line 2. Copy eax into ecx. eax keeps the same value, and the flags are not changed.

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

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

  3. Step 3, line 3. Copy ecx into the display field. Both registers and the original ammo still contain 12.

    ammo = 12; eax = 12; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; ecx = 12; display_ammo = 12.

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

Takeaway: a copy can change a destination without changing its source or the last comparison.

Meaning: calculate the effective address described by the source expression.

Common form: lea reg, [base+index*scale+displacement]. Effects: write a register; do not read the addressed game bytes or change arithmetic flags. It can also serve as arithmetic when the expression is useful as a number.

mov esi, 0x1000 ; toy player-array base
mov ecx, 2 ; third player's index
shl ecx, 2 ; pre-scale: 2 becomes 8
lea eax, [esi+ecx*4+8] ; base + 2*16 + field offset

Compute an address without reading the object. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov esi, 0x1000
mov ecx, 2
shl ecx, 2
lea eax, [esi+ecx*4+8]

lea adds base 0x00001000 + 8*4 + 8 = 0x00001028. It does not read health or change the flags left by shl.

Final values: index = 2; esi = 0x00001000; ZF = 0; SF = 0; CF = 0; OF = undefined; PF = 0; ecx = 8; eax = 0x00001028; memory read by lea = none.

Read every step of the lesson’s example
  1. Step 1, line 1. esi holds the base address of the toy collection. The health field is eight bytes into each 16-byte player record.

    index = 2; esi = 0x00001000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. ecx holds an index, not an address.

    index = 2; esi = 0x00001000; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; ecx = 2.

  3. Step 3, line 3. The encoding permits scales 1, 2, 4, or 8. Pre-multiply the index by four so the next scale of four produces a 16-byte stride.

    index = 2; esi = 0x00001000; ZF = 0; SF = 0; CF = 0; OF = undefined; PF = 0; ecx = 8.

  4. Step 4, line 4. lea adds base 0x00001000 + 8*4 + 8 = 0x00001028. It does not read health or change the flags left by shl.

    index = 2; esi = 0x00001000; ZF = 0; SF = 0; CF = 0; OF = undefined; PF = 0; ecx = 8; eax = 0x00001028; memory read by lea = none.

Takeaway: computing where a field would be does not prove that a live, valid object exists there.

Meaning: add the source to the destination and keep the selected-width result.

Common forms: add reg, r/m/imm, add memory, reg/imm. Effects: write the destination and set OF, SF, ZF, AF, CF, and PF from the addition. The CPU does not automatically reject an overflowing game reward.

mov eax, [gold]
add eax, 10 ; add a reward
mov [gold], eax

Add a reward and inspect overflow. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 0x00000064
add eax, 10
mov [gold], eax

Store 0x0000006E in gold. The mov does not repair overflow or change these flags.

Final values: case = ordinary; amount = 10; eax = 110; eax bits = 0x0000006E; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; signed reading = 110; gold = 110.

Read every step of the lesson’s example
  1. Step 1, line 1. Load 0x00000064 into eax. This copy leaves the comparison flags alone.

    case = ordinary; amount = 10; eax = 100; eax bits = 0x00000064; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

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

  2. Step 2, line 2. Add 10. The 32-bit result is 0x0000006E. CF=0 describes unsigned carry; OF=0 describes signed overflow.

    case = ordinary; amount = 10; eax = 110; eax bits = 0x0000006E; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; signed reading = 110.

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

  3. Step 3, line 3. Store 0x0000006E in gold. The mov does not repair overflow or change these flags.

    case = ordinary; amount = 10; eax = 110; eax bits = 0x0000006E; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 0; signed reading = 110; gold = 110.

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

Takeaway: the result bits and overflow flags are separate pieces of evidence.

Meaning: subtract the source from the destination and keep the selected-width result.

Common forms: sub reg, r/m/imm, sub memory, reg/imm. Effects: write the destination and set OF, SF, ZF, AF, CF, and PF. For subtraction, CF reports an unsigned borrow.

mov eax, [gold]
sub eax, 25 ; no affordability guard here
mov [gold], eax

Subtract a price and inspect a borrow. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 0x00000064
sub eax, 25
mov [gold], eax

Store 0x0000004B in gold. The mov does not repair overflow or change these flags.

Final values: case = ordinary; amount = 25; eax = 75; eax bits = 0x0000004B; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; signed reading = 75; gold = 75.

Read every step of the lesson’s example
  1. Step 1, line 1. Load 0x00000064 into eax. This copy leaves the comparison flags alone.

    case = ordinary; amount = 25; eax = 100; eax bits = 0x00000064; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

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

  2. Step 2, line 2. Subtract 25. The 32-bit result is 0x0000004B. CF=0 describes unsigned borrowing; OF=0 describes signed overflow.

    case = ordinary; amount = 25; eax = 75; eax bits = 0x0000004B; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; signed reading = 75.

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

  3. Step 3, line 3. Store 0x0000004B in gold. The mov does not repair overflow or change these flags.

    case = ordinary; amount = 25; eax = 75; eax bits = 0x0000004B; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; signed reading = 75; gold = 75.

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

Takeaway: subtraction alone does not enforce affordability; look for the check and branch that guard it.

Meaning: add one to the destination.

Common form: inc r/m. Effects: write the operand and update OF, SF, ZF, AF, and PF, while preserving CF. It differs from add ..., 1 when later code needs carry.

mov eax, [coins_collected]
inc eax
mov [coins_collected], eax

Count a collected coin while preserving carry. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 0x00000004
inc eax
mov [count], eax

The stored count is 0x00000005. Check the new ZF/SF/OF when needed; do not treat the preserved CF as this operation's overflow test.

Final values: case = ordinary; eax = 5; eax bits = 0x00000005; ZF = 0; SF = 0; CF = 1; OF = 0; PF = 1; count = 5.

Read every step of the lesson’s example
  1. Step 1, line 1. The starting comparison flags are illustrative. In particular, CF is already one.

    case = ordinary; eax = 4; eax bits = 0x00000004; ZF = 0; SF = 0; CF = 1; OF = 0; PF = 1.

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

  2. Step 2, line 2. inc changes the count by one. Its result is 0x00000005, but CF stays one; it is not a fresh carry or borrow answer.

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

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

  3. Step 3, line 3. The stored count is 0x00000005. Check the new ZF/SF/OF when needed; do not treat the preserved CF as this operation's overflow test.

    case = ordinary; eax = 5; eax bits = 0x00000005; ZF = 0; SF = 0; CF = 1; OF = 0; PF = 1; count = 5.

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

Takeaway: the carry flag after inc belongs to earlier code.

Meaning: subtract one from the destination.

Common form: dec r/m. Effects: write the operand and update OF, SF, ZF, AF, and PF, while preserving CF. Zero can become the unsigned maximum if no guard prevents it.

mov eax, [ammo]
dec eax
mov [ammo], eax

Use one ammo while preserving carry. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 0x00000001
dec eax
mov [count], eax

The stored count is 0x00000000. Check the new ZF/SF/OF when needed; do not treat the preserved CF as this operation's overflow test.

Final values: case = ordinary; eax = 0; eax bits = 0x00000000; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1; count = 0.

Read every step of the lesson’s example
  1. Step 1, line 1. The starting comparison flags are illustrative. In particular, CF is already one.

    case = ordinary; eax = 1; eax bits = 0x00000001; ZF = 0; SF = 0; CF = 1; OF = 0; PF = 1.

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

  2. Step 2, line 2. dec changes the count by one. Its result is 0x00000000, but CF stays one; it is not a fresh carry or borrow answer.

    case = ordinary; eax = 0; eax bits = 0x00000000; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1.

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

  3. Step 3, line 3. The stored count is 0x00000000. Check the new ZF/SF/OF when needed; do not treat the preserved CF as this operation's overflow test.

    case = ordinary; eax = 0; eax bits = 0x00000000; ZF = 1; SF = 0; CF = 1; OF = 0; PF = 1; count = 0.

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

Takeaway: a preserved CF is not a fresh underflow test for the decrement.

Meaning: multiply signed integers.

Common 32-bit forms: imul r/m32 writes the full product to edx:eax; imul reg32, r/m32 multiplies the destination by the source; imul reg32, r/m32, imm multiplies the source by the immediate. The latter two keep the low 32 bits in the named register. Effects: CF and OF tell whether that low result represents the full signed product; SF, ZF, AF, and PF are undefined. No source memory is written by these forms.

mov eax, [base_reward]
imul eax, eax, 3 ; triple the signed reward
mov [reward], eax

Multiply a signed reward and detect truncation. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 6
imul eax, eax, 3
mov [reward], eax

Store the low 32 bits, 0x00000012. The signed product fits the destination.

Final values: case = ordinary; factor = 3; eax = 18; ZF = undefined; SF = undefined; CF = 0; OF = 0; PF = undefined; eax bits = 0x00000012; exact product = 18; reward = 18.

Read every step of the lesson’s example
  1. Step 1, line 1. eax holds a signed reward. This is the three-operand form: destination, source, immediate multiplier.

    case = ordinary; factor = 3; eax = 6; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. The exact product is 18. The destination keeps 0x00000012, read as 18; CF and OF are 0. Other arithmetic-result flags are undefined for imul.

    case = ordinary; factor = 3; eax = 18; ZF = undefined; SF = undefined; CF = 0; OF = 0; PF = undefined; eax bits = 0x00000012; exact product = 18.

  3. Step 3, line 3. Store the low 32 bits, 0x00000012. The signed product fits the destination.

    case = ordinary; factor = 3; eax = 18; ZF = undefined; SF = undefined; CF = 0; OF = 0; PF = undefined; eax bits = 0x00000012; exact product = 18; reward = 18.

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

Takeaway: check the form before assuming which registers hold the product.

Meaning: divide a signed double-width dividend by the source, producing a quotient and a remainder.

Common 32-bit form: idiv r/m32 divides edx:eax by that register or memory value; the quotient goes to eax, the remainder to edx. There is no immediate-divisor form. cdq first sign-extends a signed eax into edx:eax. Effects: quotient truncates toward zero; arithmetic flags are undefined. A zero divisor or a quotient that does not fit raises a divide-error exception rather than an overflow flag. The trace models the zero-divisor case; signed-minimum divided by −1 is another failing boundary.

mov eax, [coin_total]
cdq ; prepare the signed dividend
mov ecx, [player_count]
idiv ecx ; requires a valid nonzero divisor
mov [share], eax

Share signed coins and keep the remainder. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov eax, 23
cdq
mov ecx, 4
idiv ecx
mov [share], eax

Each share is 5, with 3 left over. The divisor stayed 4; a later branch needs a new comparison.

Final values: coins = 23; players = 4; eax = 5; ZF = undefined; SF = undefined; CF = undefined; OF = undefined; PF = undefined; edx = 3; ecx = 4; share = 5.

Read every step of the lesson’s example
  1. Step 1, line 1. Load the signed total into eax. idiv ecx will divide the double-width EDX:EAX dividend, not eax alone.

    coins = 23; players = 4; eax = 23; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

  2. Step 2, line 2. cdq sign-extends eax into EDX:EAX; edx becomes 0. It does not change comparison flags.

    coins = 23; players = 4; eax = 23; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; edx = 0.

  3. Step 3, line 3. ecx holds the divisor; it is read, not overwritten by idiv.

    coins = 23; players = 4; eax = 23; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; edx = 0; ecx = 4.

  4. Step 4, line 4. Signed division truncates toward zero: eax=5, edx=3. The remainder has the dividend's sign when nonzero. The arithmetic flags are undefined.

    coins = 23; players = 4; eax = 5; ZF = undefined; SF = undefined; CF = undefined; OF = undefined; PF = undefined; edx = 3; ecx = 4.

  5. Step 5, line 5. Each share is 5, with 3 left over. The divisor stayed 4; a later branch needs a new comparison.

    coins = 23; players = 4; eax = 5; ZF = undefined; SF = undefined; CF = undefined; OF = undefined; PF = undefined; edx = 3; ecx = 4; share = 5.

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

Takeaway: identify the implicit dividend, validate the divisor, and make a new comparison before relying on flags.

Meaning: copy a smaller value into a wider register and fill the added high bits with zero.

Common forms here: movzx reg32, r/m8 or movzx reg32, r/m16. Effects: write only the wider destination; preserve flags and source memory. Byte FF becomes 000000FF, numeric 255.

mov bl, [ammo_byte]
movzx eax, bl
mov [ammo_dword], eax

Widen a byte without a sign. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov bl, 255
movzx eax, bl
mov [expanded], eax

Store the widened value 255. The original byte and the flags are unchanged.

Final values: value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eax = 255; eax bits = 0x000000FF; expanded = 255.

Read every step of the lesson’s example
  1. Step 1, line 1. bl contains the eight-bit pattern FF. The pattern itself does not declare a signed type.

    value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: byte = FF (8 bits).

  2. Step 2, line 2. Fill the new upper bits with zero. FF becomes 0x000000FF, read as 255.

    value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eax = 255; eax bits = 0x000000FF.

    Memory: byte = FF (8 bits).

  3. Step 3, line 3. Store the widened value 255. The original byte and the flags are unchanged.

    value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eax = 255; eax bits = 0x000000FF; expanded = 255.

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

Takeaway: zero extension preserves the value of a small unsigned field.

Meaning: copy a smaller signed value into a wider register while repeating its sign bit into the added high bits.

Common forms here: movsx reg32, r/m8 or movsx reg32, r/m16. Effects: write only the wider destination; preserve flags and source memory. Byte FF becomes FFFFFFFF, signed −1.

mov bl, [signed_step]
movsx eax, bl
mov [movement_delta], eax

Keep the sign when widening a byte. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

mov bl, 255
movsx eax, bl
mov [expanded], eax

Store the widened value -1. The original byte and the flags are unchanged.

Final values: value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eax = -1; eax bits = 0xFFFFFFFF; expanded = -1.

Read every step of the lesson’s example
  1. Step 1, line 1. bl contains the eight-bit pattern FF. The pattern itself does not declare a signed type.

    value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1.

    Memory: byte = FF (8 bits).

  2. Step 2, line 2. Repeat the source sign bit into the new upper bits. FF becomes 0xFFFFFFFF, read as -1.

    value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eax = -1; eax bits = 0xFFFFFFFF.

    Memory: byte = FF (8 bits).

  3. Step 3, line 3. Store the widened value -1. The original byte and the flags are unchanged.

    value = 255; bl = FF; ZF = 0; SF = 0; CF = 0; OF = 0; PF = 1; eax = -1; eax bits = 0xFFFFFFFF; expanded = -1.

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

Takeaway: widening the same source byte with a different rule can change its meaning.

Need Read the instruction this way
Copy a value mov writes a destination; source and flags survive.
Calculate a location lea computes an address without reading the field.
Update a total add/sub write a result and fresh arithmetic flags.
Count by one inc/dec update most arithmetic flags but preserve CF.
Multiply signed values imul forms choose implicit or explicit destinations; CF/OF report signed truncation.
Divide signed values idiv r/m32 uses edx:eax, returns quotient/remainder, and may raise divide error.
Widen a field movzx fills with zeros; movsx repeats the sign bit.
Interpret a boundary CF is unsigned carry/borrow; OF is signed overflow.

Next, follow decisions, stack operations, and bits. For numeric formats before these instructions, revisit How Bytes Become Numbers and Pointers.

Knowledge check

Copy without consuming

A game copies an ammo count from one register into another. What happens to the original register?

Finished Lesson 5.4?

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.