Assembly Language - Arithmetic Instructions

Arithmetic instructions are the foundation of CPU mathematical operations. This chapter details addition, subtraction, multiplication, division, and related carry-operation instructions in the x86 architecture.


ADD - Addition Instruction

ADDAdds the source operand to the destination operand and stores the result in the destination operand.

Example

; File path: add_demo.asm
; ADD instruction example: calculate 100 + 200 + 300

section .data
    a dd 100
    b dd 200
    c dd 300
    sum dd 0

section .text
    global _start

_start:
    ; Method 1: register += immediate
    mov eax, [a]                ; eax = 100
    add eax, 200                ; eax = 100 + 200 = 300

    ; Method 2: register += register
    mov ebx, [b]                ; ebx = 200
    add eax, ebx                ; eax = 300 + 200 = 500

    ; Method 3: register += memory
    add eax, [c]                ; eax = 500 + 300 = 800

    ; Store the result
    mov [sum], eax              ; sum = 800

    ; Effect of addition on flags
    mov eax, 0xFFFFFFFF         ; eax = largest 32-bit unsigned number
    add eax, 1                  ; eax = 0 (overflow wraps around)
    ; CF = 1 (carry generated)
    ; ZF = 1 (result is 0)
    ; OF = 0 (no overflow from signed perspective)

    mov eax, 1
    mov ebx, 0
    int 0x80

SUB - Subtraction Instruction

SUBSubtracts the source operand from the destination operand and stores the result in the destination operand.

Example

; File path: sub_demo.asm
; SUB instruction example

section .data
    x dd 1000
    y dd 300

section .text
    global _start

_start:
    ; Basic subtraction
    mov eax, [x]                ; eax = 1000
    sub eax, [y]                ; eax = 1000 - 300 = 700

    ; Effect of subtraction on flags
    mov eax, 10
    sub eax, 20                 ; eax = -10 (i.e., 0xFFFFFFF6)
    ; CF = 1 (borrow generated: 10 < 20)
    ; SF = 1 (result is negative)
    ; ZF = 0 (result is non-zero)
    ; OF = 0 (no signed overflow)

    ; Subtract itself: often used to zero out
    mov eax, 12345
    sub eax, eax                ; eax = 0
    ; ZF = 1, CF = 0
    ; This is a classic way to zero a register (more efficient than mov eax, 0)

    mov eax, 1
    mov ebx, 0
    int 0x80

sub eax, eaxis a classic technique to zero a register; it occupies only 2 bytes, whilemov eax, 0occupies 5 bytes. It is commonly used in scenarios requiring extreme size optimization (such as shellcode).


INC / DEC - Increment/Decrement Instructions

Add-1 and subtract-1 instructions more concise than ADD/SUB:

Example

; INC and DEC example

section .data
    counter dd 0

section .text
    global _start

_start:
    mov dword [counter], 0      ; counter = 0
    inc dword [counter]         ; counter = 1 (memory operand)
    inc dword [counter]         ; counter = 2

    mov ecx, 10
    dec ecx                     ; ecx = 9
    dec ecx                     ; ecx = 8

    ; INC/DEC do not affect the CF flag (this is an important difference from ADD/SUB)
    ; But they affect ZF, SF, OF, PF

    ; Using INC in a loop
    mov ecx, 5
    mov eax, 0
loop_inc:
    inc eax                     ; eax increments by 1 each time
    loop loop_inc               ; Repeat 5 times, eax finally = 5

    mov ebx, eax                ; Return value = 5
    mov eax, 1
    int 0x80

MUL - Unsigned Multiplication

MULPerforms unsigned multiplication. The rules for multiplication are somewhat special:

Operand sizeMultiplierMultiplicand (implicit)Result storage
1 byteAny 8-bit register or memoryALAX = AL × operand
2 bytesAny 16-bit register or memoryAXDX:AX = AX × operand
4 bytesAny 32-bit register or memoryEAXEDX:EAX = EAX × operand

Example

; File path: mul_demo.asm
; MUL unsigned multiplication example

section .data
    val1 dd 1000
    val2 dd 2000
    result_low dd 0
    result_high dd 0

section .text
    global _start

_start:
    ; 32-bit multiplication: EDX:EAX = EAX × operand
    mov eax, [val1]             ; eax = 1000
    mul dword [val2]            ; edx:eax = 1000 × 2000 = 2,000,000
    ; Result = 2,000,000 = 0x001E8480
    ; EAX = 0x001E8480 (low 32 bits)
    ; EDX = 0x00000000 (high 32 bits, because the result did not exceed 32 bits)

    ; Large number multiplication demo (result exceeds 32 bits)
    mov eax, 0xFFFFFFFF         ; eax = 4,294,967,295
    mov ebx, 2                  ; ebx = 2
    mul ebx                     ; edx:eax = 0xFFFFFFFF × 2
    ; EAX = 0xFFFFFFFE
    ; EDX = 0x00000001 (high 32 bits)
    ; CF = 1 (result exceeds 32 bits)

    ; Save result
    mov [result_low], eax
    mov [result_high], edx

    mov eax, 1
    mov ebx, 0
    int 0x80

IMUL - Signed Multiplication

IMULUsed for multiplication of signed numbers, with three forms:

Example

; File path: imul_demo.asm
; IMUL signed multiplication example

section .data
    a dd -100
    b dd 3

section .text
    global _start

_start:
    ; Single-operand form: same as MUL
    mov eax, [a]                ; eax = -100
    imul dword [b]              ; edx:eax = -100 × 3 = -300
    ; EAX = -300(0xFFFFFED4)
    ; EDX = 0xFFFFFFFF (sign extension)

    ; Two-operand form: reg = reg × operand
    mov ebx, [a]                ; ebx = -100
    imul ebx, [b]               ; ebx = -100 × 3 = -300
    ; The result must fit in 32 bits

    ; Three-operand form: reg = operand1 × immediate
    imul ecx, [a], 5            ; ecx = -100 × 5 = -500

    mov eax, 1
    mov ebx, 0
    int 0x80

MULandIMULThe main difference is the handling of signs:MULInterpreted as unsigned numbers,IMULInterpreted as signed numbers. For example, 0xFFFFFFFF is 4294967295 in MUL, and -1 in IMUL.


DIV - Unsigned Division

DIVPerforms unsigned division, rules are symmetric to MUL:

Divisor sizeDividend (implicit)ShangRemainder
1 byteAXALAH
2 bytesDX:AXAXDX
4 bytesEDX:EAXEAXEDX

Example

; File path: div_demo.asm
; DIV unsigned division example

section .data
    dividend dd 1000
    divisor dd 7
    quotient dd 0
    remainder dd 0

section .text
    global _start

_start:
    ; 32-bit division: edx:eax / divisor
    ; The dividend must first be extended to EDX:EAX
    mov eax, [dividend]         ; eax = 1000
    mov edx, 0                  ; edx = 0 (clear high 32 bits)
    div dword [divisor]         ; edx:eax / 7
    ; EAX = 142 (quotient)
    ; EDX = 6 (remainder: 1000 = 142×7 + 6)

    mov [quotient], eax         ; quotient = 142
    mov [remainder], edx        ; remainder = 6

    ; Byte division example: 55 / 4
    mov ax, 55                  ; Dividend
    mov bl, 4                   ; Divisor
    div bl                      ; al = 13 (quotient), ah = 3 (remainder)

    mov eax, 1
    mov ebx, 0
    int 0x80

Before doing division, be sure to usemov edx, 0orxor edx, edxClear EDX! If EDX has stale data, the dividend will be wrong. This is the most common division bug for beginners.


IDIV - Signed Division

IDIVUsed for signed division. Before doing division, you need to useCDQinstruction to sign-extend EAX into EDX:EAX:

Example

; IDIV signed division example

    ; Calculate -100 / 3
    mov eax, -100               ; eax = -100
    cdq                         ; Sign extension: edx:eax = -100
                                ; cdq copies the sign bit of eax to all bits of edx
    mov ebx, 3                  ; Divisor
    idiv ebx                    ; eax = -33 (quotient), edx = -1 (remainder)
    ; Verify: -100 = -33 × 3 + (-1)

ADC / SBB - Operations with Carry/Borrow

Used forlarge-number arithmetic(data exceeding 32 bits):

Example

; File path: adc_sbb_demo.asm
; 64-bit addition: adding two 64-bit numbers

section .data
    ; 64-bit number a = 0x00000001 FFFFFFFF
    a_low dd 0xFFFFFFFF
    a_high dd 0x00000001

    ; 64-bit number b = 0x00000000 00000005
    b_low dd 0x00000005
    b_high dd 0x00000000

    ; Result
    result_low dd 0
    result_high dd 0

section .text
    global _start

_start:
    ; Add low 32 bits
    mov eax, [a_low]
    add eax, [b_low]            ; eax = 0xFFFFFFFF + 5 = 0x00000004
    ; CF = 1 (carry occurred!)
    mov [result_low], eax       ; result_low = 0x00000004

    ; Add high 32 bits with carry
    mov eax, [a_high]
    adc eax, [b_high]           ; adc = add + CF
    ; eax = 1 + 0 + 1(carry) = 2
    mov [result_high], eax      ; result_high = 2

    ; Final 64-bit result: 0x00000002 00000004
    ; Verify: 0x1FFFFFFF + 5 = 0x200000004

    ; SBB subtraction is similar (with borrow)
    mov eax, [a_low]
    sub eax, [b_low]            ; Subtract the low 32 bits
    mov [result_low], eax

    mov eax, [a_high]
    sbb eax, [b_high]           ; sbb = sub - CF

    mov eax, 1
    mov ebx, 0
    int 0x80

Arithmetic Instruction Quick Reference Table

InstructionFormatFunctionAffected flags
ADDadd dest, srcdest = dest + srcCF, ZF, SF, OF, PF
SUBsub dest, srcdest = dest - srcCF, ZF, SF, OF, PF
INCinc destdest = dest + 1ZF, SF, OF, PF (does not affect CF)
DECdec destdest = dest - 1ZF, SF, OF, PF (does not affect CF)
MULmul srcUnsigned multiplicationCF, OF
IMULimul srcSigned multiplicationCF, OF
DIVdiv srcUnsigned divisionUndefined (indeterminate)
IDIVidiv srcSigned divisionUndefined (indeterminate)
ADCadc dest, srcdest = dest + src + CFCF, ZF, SF, OF, PF
SBBsbb dest, srcdest = dest - src - CFCF, ZF, SF, OF, PF
NEGneg destdest = -dest (negate)CF, ZF, SF, OF, PF
Other extensions