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
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
; 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
; 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
; 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
| Instruction | Function | Registers used |
|---|---|---|
| MOVSB | Copy byte: [EDI] = [ESI] | ESI=source, EDI=destination, ECX=count, DF=direction |
| MOVSW | Copy word (2 bytes) | Same as above |
| MOVSD | Copy doubleword (4 bytes) | Same as above |
| STOSB | Store byte: [EDI] = AL | EDI=destination, AL=value, ECX=count |
| LODSB | Load byte: AL = [ESI] | ESI=source |
| CMPSB | Compare byte: [ESI] - [EDI] | ESI=source, EDI=destination, ECX=count |
| SCASB | Scan byte: AL - [EDI] | EDI=destination, AL=search value, ECX=count |
Case Conversion Example
Example
; 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.
Other extensionsThe direction flag DF determines the direction of string operations: when DF=0, ESI/EDI increment (forward), when DF=1, they decrement (reverse). Use
CLDto clear DF, useSTDto set DF. Before calling C functions or system calls, ensure DF=0 (cdecl requirement).