Instruction Reference: Values and Integer Arithmetic
Look up common x86 value operations, then explore each one with a worked trace.
Cheatsheet for Lesson 5.4
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:
movwrites a destination; source and flags survive. - Calculate a location:
leacomputes an address without reading the field. - Update a total:
add/subwrite a result and fresh arithmetic flags. - Count by one:
inc/decupdate most arithmetic flags but preserve CF. - Multiply signed values:
imulforms choose implicit or explicit destinations; CF/OF report signed truncation. - Divide signed values:
idiv r/m32usesedx:eax, returns quotient/remainder, and may raise divide error. - Widen a field:
movzxfills with zeros;movsxrepeats 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
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.
Find an operation
Section titled “Find an operation”| Instruction | Worked example | Intel entry |
|---|---|---|
mov |
Copy ammo | MOV |
lea |
Calculate a player’s field address | LEA |
add |
Add a gold reward | ADD |
sub |
Spend gold | SUB |
inc |
Count a coin | INC |
dec |
Use one ammo | DEC |
imul |
Multiply a signed reward | IMUL |
idiv |
Share a signed coin total | IDIV |
movzx |
Widen an unsigned byte | MOVZX |
movsx |
Widen a signed byte | MOVSX |
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.
1 Base plus index × scale: 0x1000 + 8
Running address0x1000base+ 8index 2 × scale 40x1008The base register holds 0x1000. The index register holds 2 and the scale is 4, so 2 × 4 = 8 bytes are added.
2 Add the displacement: 0x1008 + 8
Running address0x1008+ 8displacement0x1010effective addressThe constant displacement adds another eight bytes, giving the effective address.
3 The path splits at 0x1010
lea
Register receives0x1010the address itselfmov
Register receivesbytes at 0x1010the contentslea 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.
Flags: four different questions
Section titled “Flags: four different questions”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.
Copies and addresses
Section titled “Copies and addresses”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 fieldmov ecx, eax ; copy the register valuemov [display_ammo], ecx ; store the copyCopy 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], ecxCopy 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
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).
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).
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 basemov ecx, 2 ; third player's indexshl ecx, 2 ; pre-scale: 2 becomes 8lea eax, [esi+ecx*4+8] ; base + 2*16 + field offsetCompute 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
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.
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.
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.
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.
Arithmetic and boundaries
Section titled “Arithmetic and boundaries”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 rewardmov [gold], eaxAdd 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], eaxStore 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
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).
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).
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 heremov [gold], eaxSubtract 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], eaxStore 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
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).
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).
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 eaxmov [coins_collected], eaxCount 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], eaxThe 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
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).
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).
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 eaxmov [ammo], eaxUse 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], eaxThe 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
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).
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).
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 rewardmov [reward], eaxMultiply 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], eaxStore 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
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.
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.
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 dividendmov ecx, [player_count]idiv ecx ; requires a valid nonzero divisormov [share], eaxShare 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], eaxEach 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
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.
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.
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.
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.
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.
Widening a field
Section titled “Widening a field”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, blmov [ammo_dword], eaxWiden 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], eaxStore 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
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).
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).
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, blmov [movement_delta], eaxKeep 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], eaxStore 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
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).
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).
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.
Cheatsheet
Section titled “Cheatsheet”| 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.
Copy without consuming
A game copies an ammo count from one register into another. What happens to the original register?
An ordinary register copy writes the destination without consuming or clearing the source. The same value can remain in both registers.
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.
Plain Markdown, saved in this browser.
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.