Translating GAS to NASM, part 4

March 29, 2021

This chapter was a project. Not only was there far more code than any previous chapter, but I also put off learning make until I was nearly finished, so I spent a whooooole lot of time re-assembling and re-linking. For anyone similarly ignorant of the wonders of makefiles, you can grab mine here (or learn about them here). Store the makefile in the same directory as your ASM files and enter make in your terminal to handle all your assembling and linking. Run make clean to get rid of all your object files.

New NASM changes!!!!

  • We can now keep all our syscalls in a convenient external file! The only change here is that we use %include to use them, not .include.
  • Functions and data exported from other modules with the global keyword must be imported using the extern keyword.
  • To pad a data item, NASM uses times instead of .rept, and instead of counting word lengths to figure out how much padding you need, there’s some useful pointer math you can do using the symbol $, which stands for “current memory location”. These lines
    db "Fredrick"
    times record1+40-$ db 0
    mean “store the word Fredrick, then store 0s from the end of that word up to byte 40 of record1”.
  • This chapter uses push with a memory address as its operand. To do this in NASM, the size of the data being pushed has to be defined using one of the type keywords: byte, word, dword, qword, tword, oword, yword, or zword. These follow the pattern of NASM’s other size keywords.
  • These type keywords are used whenever dereferencing data whose size isn’t apparent. In the code below, you’ll see them when mov-ing a constant into memory, and when inc-rementing a value stored in memory.
  • Escape characters like newline and NUL only work if your strings are wrapped in backticks (`). You could also use the ASCII values of those characters: 10 for newline, and 0 for NUL.
  • In the book, read_record and write_record both preserve %rbx since it’s used in the syscalls. x64 syscalls don’t use %rbx so I didn’t bother.

Exercise 6 - Writing structured data

linux.asm

    record_firstname equ 0
    record_lastname equ 40
    record_address equ 80
    record_age equ 320
    record_size equ 328

record-defs.asm

    ;; Common Linux Definitions
    sys_read equ 0
    sys_write equ 1
    sys_open equ 2
    sys_close equ 3
    sys_exit equ 60
    sys_brk equ 12

    ;; Standard file descriptors
    stdin equ 0
    stdout equ 1
    stderr equ 2

    ;; Common Status Codes
    eof equ 0

write-record.asm

    %include "record-defs.asm"
    %include "linux.asm"

    st_write_buffer equ 16
    st_filedes equ 24

    section .text
    global write_record
write_record:
    push rbp
    mov rbp, rsp

    mov rax, sys_write
    mov rdi, [rbp + st_filedes]
    mov rsi, [rbp + st_write_buffer]
    mov rdx, record_size
    syscall

    mov rsp, rbp
    pop rbp
    ret

write-records.asm

    %include "record-defs.asm"
    %include "linux.asm"

    section .data

record1:
    db "Fredrick"
    times record1+40-$ db 0

    db "Bartlett"
    times record1+80-$ db 0

    db `4242 S Prairie\nTulsa, OK 55555`
    times record1+320-$ db 0

    dq 45

record2:
    db "Marilyn"
    times record2+40-$ d1 db 0

    db "Taylor"
    times record2+80-$ db 0

    db `2224 S Johannan St\nChicago, IL 12345`
    times record2+320-$ db 0

    dq 29

record3:
    db "Derrick"
    times record3+40-$ db 0

    db "McIntire"
    times record3+80-$ db 0

    db `500 W Oakland\nSan Diego, CA 54321`
    times record3+320-$ db 0

    dq 36

filename:
    db "test.dat", 0

    section .text

    st_file_descriptor equ -8
    extern write_record

    global _start
_start:
    mov rbp, rsp
    sub rsp, 8

    mov rax, sys_open
    mov rdi, filename
    mov rsi, 0o101
    mov rdx, 0o666
    syscall

    mov [rbp + st_file_descriptor], rax

    push qword [rbp + st_file_descriptor]
    push record1
    call write_record
    add rsp, 16

    push qword [rbp + st_file_descriptor]
    push record2
    call write_record
    add rsp, 16

    push qword [rbp + st_file_descriptor]
    push record3
    call write_record
    add rsp, 16

    mov rax, sys_close
    mov rdi, [rbp + st_file_descriptor]
    syscall

    mov rax, sys_exit
    mov rdi, 0
    syscall

Exercise 7 - Reading structured data

count-chars.asm

    global count_chars
    st_string_start_address equ 8

count_chars:
    push rbp
    mov rbp, rsp

    mov rcx, 0
    mov rdx, [rbp + st_string_start_address]

count_loop_begin:
    mov al, [rdx]
    cmp al, 0
    je count_loop_end
    inc rcx
    inc rdx
    jmp count_loop_begin

count_loop_end:
    mov rax, rcx
    pop rbp
    ret

write-newline.asm

    %include "linux.asm"
    global write_newline

    section .data
newline: db `\n` 

    section .text
    st_filedes equ 16
write_newline:
    push rbp
    mov rbp, rsp

    mov rax, sys_write
    mov rdi, [rbp + st_filedes]
    mov rsi, newline
    mov rdx, 2
    syscall

    mov rsp, rbp
    pop rbp
    ret

read-record.asm

    %include "linux.asm"
        %include "record-defs.asm"

    st_read_buffer equ 16
    st_filedes equ 24

    section .text
    global read_record
read_record:
    push rbp
    mov rbp, rsp

    mov rax, sys_read
    mov rdi, [rbp + st_filedes]
    mov rsi, [rbp + st_read_buffer]
    mov rdx, record_size
    syscall

    mov rsp, rbp
    pop rbp
    ret

read-records.asm

    %include "linux.asm"
    %include "record-defs.asm"

    extern count_chars, write_newline, read_record
    section .data
filename:
    db "test.dat", 0

    section .bss
record_buffer:  resb record_size

    section .text
    global _start
_start:
    st_input_descriptor equ -8
    st_output_descriptor equ -16

    mov rbp, rsp
    sub rsp, 16

    mov rax, sys_open
    mov rdi, filename
    mov rsi, 0
    mov rdx, 0o666
    syscall

    mov [rbp + st_input_descriptor], rax
    mov qword [rbp + st_output_descriptor], stdout

record_read_loop:
    push qword [rbp + st_input_descriptor]
    push record_buffer
    call read_record
    add rsp, 8

    cmp rax, record_size
    jne finished_reading

    push record_buffer + record_firstname
    call count_chars
    add rsp, 8

    mov rdx, rax
    mov rax, sys_write
    mov rdi, [rbp + st_output_descriptor]
    mov rsi, record_buffer + record_firstname
    syscall

    push qword [rbp + st_output_descriptor]
    call write_newline
    add rsp, 8

    jmp record_read_loop

finished_reading:
    mov rax, sys_exit
    mov rdi, 0
    syscall

Exercise 8 - Modifying structured data

modify-records.asm

    %include "linux.asm"
    %include "record-defs.asm"

    section .data
input_file_name:    db "test.dat", 0
output_file_name:   db "testout.dat", 0

    section .bss
record_buffer:   resb record_size

    st_input_descriptor equ -8
    st_output_descriptor equ -16

    section .text
    global _start
    extern read_record, write_record
_start:
    mov rbp, rsp
    sub rsp, 16

    mov rax, sys_open
    mov rdi, input_file_name
    mov rsi, 0
    mov rdx, 0o666
    syscall

    mov [rbp + st_input_descriptor], rax

    mov rax, sys_open
    mov rdi, output_file_name
    mov rsi, 0o101
    mov rdx, 0o666
    syscall

    mov [rbp + st_output_descriptor], rax

loop_begin:
    push qword [rbp + st_input_descriptor]
    push record_buffer
    call read_record
    add rsp, 16

    cmp rax, record_size
    jne loop_end

    inc qword [record_buffer + record_age]

    push qword [rbp + st_output_descriptor]
    push record_buffer
    call write_record
    add rsp, 16

    jmp loop_begin

loop_end:
    mov rdi, rax
    mov rax, sys_exit
    syscall

Previous Chapter