Aprelius logo
uptime: 00:00:00
Assembly

Arithmetic Instructions (ADD/SUB/MUL/DIV)

ADD/SUB

Key insight: ADD/SUB produce the same bit pattern regardless of signed/unsigned interpretation.

asm
; Example: 11111111 + 00000001 = 00000000 (with carry)
; Unsigned: 255 + 1 = 0 (overflow)
; Signed:    -1 + 1 = 0 (correct)

FLAGS register (EFLAGS)

  • CF (Carry Flag): Unsigned overflow
  • OF (Overflow Flag): Signed overflow
  • ZF (Zero Flag): Result is zero
  • SF (Sign Flag): Most significant bit of result

MUL/DIV vs IMUL/IDIV

asm
mul ebx            ; Unsigned: result in EDX:EAX (destroys EDX)
imul ebx           ; Signed: result in EDX:EAX (destroys EDX)
imul eax, ebx, 3   ; Signed: eax = ebx * 3 (cleaner for simple cases)
div ebx            ; Unsigned: EAX = quotient, EDX = remainder
idiv ebx           ; Signed: EAX = quotient, EDX = remainder
                   ; Both can crash with divide-by-zero, #DE (divide error) exception

Remember: div/idiv take the 64-bit EDX:EAX pair as the dividend, not just EAX — so clear EDX first (e.g. xor edx, edx) or the result is garbage.

asm
mov eax, 100        ; low 32 bits of dividend
xor edx, edx        ; clear high 32 bits (so dividend = 100, not garbage)
mov ebx, 7          ; divisor
div ebx             ; EAX = 14 (quotient), EDX = 2 (remainder)

Why separate MUL/IMUL but not ADD/SUB? ADD/SUB share a bit pattern for signed and unsigned. MUL/DIV need different bit patterns for signed vs unsigned.

Working with memory (including the stack)

Only simple operations work directly on memory:

asm
# Simple operations
add dword [esp+4], 16
sub dword [esp], 5
inc dword [esp+8]

# More complex operations
mov eax, [esp+4]           ; Load from memory
imul eax, 3                ; Operate on register
mov [esp+4], eax           ; Store back to memory

Size specifiers

asm
# When size specifier is REQUIRED
add [esp+4], 16            ; Error: size unknown
add dword [esp+4], 16      ; OK: explicit 32-bit operation
mov [esp], 100              ; Error: size unknown
mov byte [esp], 100         ; OK: explicit 8-bit operation

# When size specifier is NOT needed
add eax, [esp+4]           ; OK: eax is 32-bit -> fetch 32 bits
mov [esp+4], eax           ; OK: eax is 32-bit -> store 32 bits
mov bl, [esp]               ; OK: bl is 8-bit -> fetch 8 bits
add esp, 12                 ; OK: esp is 32-bit register
push eax                    ; OK: push/pop always 32-bit in 32-bit mode