Assembly Language - String Processing

String processing is a common requirement in programming.

Handling strings in assembly means directly manipulating byte sequences in memory; it is lower-level but also more flexible.


Definition and Storage of Strings

In NASM, strings are essentially byte sequences and can be defined using the DB pseudo-instruction:

Example

; String definition methods

section .data
    ; Method 1: standard string
    str1 db 'Hello, example!', 0     ; C style: null-terminated

    ; Method 2: with newline
    str2 db 'Line1', 0xA, 'Line2', 0xA

    ; Method 3: backticks support escapes
    str3 db `hello\nworld\n`, 0     ; Automatically convert escape sequences

    ; Method 4: define character by character
    str4 db 'A', 'B', 'C', 'D', 0

    ; Method 5: use dup to generate repeated characters
    border db 40 dup('-')           ; 40 hyphens

    ; String length calculation (compile time)
    str1_len equ $ - str1           ; Including the trailing null

Calculating String Length

There are two ways to get string length: compile-time calculation (EQU) and runtime calculation (traverse to find null):

Example

; File path: strlen_demo.asm
; Two ways to calculate string length

section .data
    ; Method A: compile-time calculation (for constant strings)
    msg1 db 'Hello, EXAMPLE!', 0xA
    msg1_len equ $ - msg1          ; Automatically calculated at compile time

    ; Method B: null-terminated string (runtime calculation)
    msg2 db 'Find my length', 0    ; Ends with 0

section .text
    global _start

_start:
    ; Method A: directly use the compile-time calculated length
    mov eax, 4
    mov ebx, 1
    mov ecx, msg1
    mov edx, msg1_len              ; Use the constant directly
    int 0x80

    ; Method B: calculate the length of a null-terminated string at runtime
    mov esi, msg2                  ; esi points to the start of the string
    mov ecx, 0                     ; counter

strlen_loop:
    cmp byte [esi], 0              ; Is the current byte null?
    je  strlen_done                ; Yes, end
    inc ecx                        ; count + 1
    inc esi                        ; pointer + 1
    jmp strlen_loop

strlen_done:
    ; ecx now holds the string length (without null)
    mov eax, 4
    mov ebx, 1
    mov ecx, msg2
    mov edx, ecx                   ; Use the calculated length
    ; Here's a problem: ecx is overwritten, should save it first
    ; Correct approach: push ecx then pop to edx

    mov eax, 1
    mov ebx, 0
    int 0x80

String Copy

Use a loop to copy byte by byte, or use x86 string operation instructionsMOVSB:

Example

; File path: strcpy_demo.asm
; Two implementations of string copying

section .data
    src db 'example source string', 0
    src_len equ $ - src

section .bss
    dest_manual resb 64             ; Manual copy target
    dest_fast resb 64               ; Fast copy target

section .text
    global _start

_start:
    ; Method A: manual byte-by-byte copy
    mov esi, src                    ; source address
    mov edi, dest_manual            ; destination address
    mov ecx, src_len                ; byte count

copy_loop:
    mov al, [esi]                   ; read one byte
    mov [edi], al                   ; write one byte
    inc esi                         ; source pointer++
    inc edi                         ; destination pointer++
    loop copy_loop

    ; Method B: use string operation instructions (faster)
    cld                             ; Clear direction flag (DF=0, forward copy)
    mov esi, src                    ; source address (ESI)
    mov edi, dest_fast              ; destination address (EDI)
    mov ecx, src_len                ; byte count (ECX)
    rep movsb                       ; repeatedly execute movsb ecx times
    ; rep movsb:while(ecx>0) { [edi]=[esi]; esi++; edi++; ecx--; }

    ; Verify: output the two copy results
    mov eax, 4
    mov ebx, 1
    mov ecx, dest_manual
    mov edx, src_len
    int 0x80

    mov eax, 4
    mov ebx, 1
    mov ecx, dest_fast
    mov edx, src_len
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

REP MOVSBIt is the most classic string copy method on x86. However, on modern CPUs, a manual copy loop with loop unrolling may be faster than REP MOVSB, because modern CPUs have better pipeline optimization for simple operations.


String Comparison

UseCMPSBtogether withREPE(repeat until not equal) byte-by-byte comparison:

Example

; File path: strcmp_demo.asm
; Compare whether two strings are equal

section .data
    str_a db 'example', 0
    str_b db 'example', 0
    str_c db 'EXAMPLE', 0
    eq_msg db 'Strings are equal', 0xA
    eq_len equ $ - eq_msg
    ne_msg db 'Strings are NOT equal', 0xA
    ne_len equ $ - ne_msg

section .text
    global _start

_start:
    cld                             ; Forward comparison
    mov esi, str_a                  ; First string
    mov edi, str_b                  ; Second string
    mov ecx, 7                      ; Compare up to 7 bytes (including null)

    repe cmpsb                      ; Repeat comparison until not equal or ecx=0
    ; repe: continue if ZF=1 (equal) and ecx>0
    ; cmpsb: compare [esi] and [edi], then esi++, edi++

    je  strings_equal               ; If ZF=1, all bytes are equal

    ; Not equal
    mov eax, 4
    mov ebx, 1
    mov ecx, ne_msg
    mov edx, ne_len
    int 0x80
    jmp compare_next

strings_equal:
    mov eax, 4
    mov ebx, 1
    mov ecx, eq_msg
    mov edx, eq_len
    int 0x80

compare_next:
    ; Compare str_a and str_c (different case)
    cld
    mov esi, str_a
    mov edi, str_c
    mov ecx, 7
    repe cmpsb
    jne not_equal_2

    mov eax, 4
    mov ebx, 1
    mov ecx, eq_msg
    mov edx, eq_len
    int 0x80
    jmp exit

not_equal_2:
    mov eax, 4
    mov ebx, 1
    mov ecx, ne_msg
    mov edx, ne_len
    int 0x80

exit:
    mov eax, 1
    mov ebx, 0
    int 0x80

Summary of String Operation Instructions

InstructionFunctionRegisters used
MOVSBCopy byte: [EDI] = [ESI]ESI=source, EDI=destination, ECX=count, DF=direction
MOVSWCopy word (2 bytes)Same as above
MOVSDCopy doubleword (4 bytes)Same as above
STOSBStore byte: [EDI] = ALEDI=destination, AL=value, ECX=count
LODSBLoad byte: AL = [ESI]ESI=source
CMPSBCompare byte: [ESI] - [EDI]ESI=source, EDI=destination, ECX=count
SCASBScan byte: AL - [EDI]EDI=destination, AL=search value, ECX=count

Case Conversion Example

Example

; File path: case_convert.asm
; Convert lowercase letters in string to uppercase

section .data
    msg db 'Hello, example! Welcome to Assembly.', 0xA
    len equ $ - msg

section .text
    global _start

_start:
    ; Output original string
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80

    ; Conversion: iterate string, lowercase -> uppercase
    mov esi, msg                    ; Point to start of string
    mov ecx, len                    ; Loop count

convert_loop:
    mov al, [esi]                   ; Read character
    cmp al, 'a'                     ; Is it >= 'a'
    jb  next_char                   ; Less than 'a', skip
    cmp al, 'z'                     ; Is it <= 'z'
    ja  next_char                   ; Greater than 'z', skip

    ; Lowercase to uppercase: 'a'(97) - 'A'(65) = 32
    sub al, 32                      ; Convert to uppercase
    mov [esi], al                   ; Write back

next_char:
    inc esi
    loop convert_loop

    ; Output converted string
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

Running result:

$ nasm -f elf32 case_convert.asm -o case_convert.o
$ ld -m elf_i386 case_convert.o -o case_convert
$ ./case_convert
Hello, example! Welcome to Assembly.
HELLO, EXAMPLE! WELCOME TO ASSEMBLY.

The direction flag DF determines the direction of string operations: when DF=0, ESI/EDI increment (forward), when DF=1, they decrement (reverse). UseCLDto clear DF, useSTDto set DF. Before calling C functions or system calls, ensure DF=0 (cdecl requirement).

Other extensions