AsAssembly · Lesson 9 of 10
Mini Project: String Operations
Let's write a small library of string utilities in pure assembly — strlen, strcpy, strcmp, and a number-to-string converter. No libc. Just registers, memory, and loops.
x86-64 ASM
; strings.asm — string utility library + demo
; nasm -f elf64 strings.asm -o strings.o && ld strings.o -o strings && ./strings
section .data
str1 db "Hello, Assembly!", 0
str2 db "Hello, Assembly!", 0
str3 db "Different string", 0
newln db 10, 0
section .bss
outbuf resb 32 ; output buffer for number conversion
section .text
global _start
; strlen(rdi: *str) -> rax: length
strlen:
xor rax, rax ; rax = 0 (counter)
.loop:
cmp byte [rdi + rax], 0
je .done
inc rax
jmp .loop
.done:
ret
; puts(rdi: *str) — print string + newline
puts_asm:
push rbx
mov rbx, rdi
call strlen ; rax = length
mov rdx, rax ; count
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, rbx ; string pointer
syscall
; print newline
mov rax, 1
mov rdi, 1
lea rsi, [rel newln]
mov rdx, 1
syscall
pop rbx
ret
; strcmp(rdi: *s1, rsi: *s2) -> rax: 0 if equal, nonzero if not
strcmp_asm:
xor rax, rax
.loop:
mov al, byte [rdi] ; al = *s1
mov ah, byte [rsi] ; ah = *s2
cmp al, ah
jne .differ
test al, al ; end of string?
jz .equal
inc rdi
inc rsi
jmp .loop
.equal:
xor rax, rax
ret
.differ:
movzx rax, al
movzx rcx, ah
sub rax, rcx ; return difference (like C strcmp)
ret
; itoa(rdi: number, rsi: *buf) -> rax: *buf (null-terminated decimal string)
itoa:
push rbp
mov rbp, rsp
push rbx
push r12
push r13
mov r12, rsi ; save buf start
mov rbx, rdi ; save number
; Handle 0 specially
test rbx, rbx
jnz .convert
mov byte [r12], '0'
mov byte [r12+1], 0
lea rax, [r12]
jmp .done
.convert:
; Write digits in reverse, then flip
mov r13, rsi ; current position in buffer
mov rax, rbx
.digit_loop:
xor rdx, rdx
mov rcx, 10
div rcx ; rax = rax/10, rdx = rax%10
add dl, '0'
mov byte [r13], dl
inc r13
test rax, rax
jnz .digit_loop
mov byte [r13], 0 ; null terminate
; Reverse the string (r12..r13-1)
mov rdi, r12
lea rsi, [r13-1]
.reverse:
cmp rdi, rsi
jge .reversed
mov al, byte [rdi]
mov bl, byte [rsi]
mov byte [rdi], bl
mov byte [rsi], al
inc rdi
dec rsi
jmp .reverse
.reversed:
lea rax, [r12]
.done:
pop r13
pop r12
pop rbx
pop rbp
ret
_start:
; Test strlen
lea rdi, [rel str1]
call strlen
; rax = 16
; Print str1
lea rdi, [rel str1]
call puts_asm
; Test strcmp — equal strings
lea rdi, [rel str1]
lea rsi, [rel str2]
call strcmp_asm
; rax = 0 (equal)
; Test strcmp — different strings
lea rdi, [rel str1]
lea rsi, [rel str3]
call strcmp_asm
; rax != 0 (different)
; Convert 2024 to string and print
mov rdi, 2024
lea rsi, [rel outbuf]
call itoa
mov rdi, rax
call puts_asm
mov rax, 60
xor rdi, rdi
syscallBash
nasm -f elf64 strings.asm -o strings.o
ld strings.o -o strings
./strings
# Hello, Assembly!
# 2024
# Inspect generated machine code:
objdump -d strings | head -60
# Check binary size (should be tiny — no libc):
ls -lh strings✦ Tip
Use `gdb` to step through assembly line by line: `gdb ./strings`, then `layout asm` for the disassembly view, `layout regs` for register display, `si` to step one instruction. Or use `gdb -tui`. Seeing registers change as each instruction executes makes assembly click faster than anything else.
Use `gdb` to step through assembly line by line: `gdb ./strings`, then `layout asm` for the disassembly view, `layout regs` for register display, `si` to step one instruction. Or use `gdb -tui`. Seeing registers change as each instruction executes makes assembly click faster than anything else.