Research Context

If you are messing around with Assembly, the most direct way to talk to the processor is through the Syscall (System Call) mechanism. Opening a file, reading from a keyboard, or printing text to a terminal… it all starts with a formal request to the Kernel. Today, we are going behind the scenes to examine the open, read, and exit calls, how registers manage data flow, and why the .bss segment is your best friend for memory management.

1. The Register Dance: Who Goes Where?

In the Linux x64 architecture, before making a syscall, you must tell the Kernel exactly what you want. There is a strict protocol for this:

RAX: Holds the Syscall ID. (e.g., 0 for read, 2 for open, 60 for exit).

RDI: The 1st Parameter (Usually the File Descriptor).

RSI: The 2nd Parameter (Usually the memory address for a buffer).

RDX: The 3rd Parameter (Usually the size of the data in bytes).

Understanding File Descriptors (FD)

To the Kernel, there is no difference between a keyboard, a terminal, or a .txt file; they are all just numbers:

0 (stdin): Standard Input (Keyboard).

1 (stdout): Standard Output (Terminal screen).

2 (stderr): Standard Error.

2. Essential Syscalls: Open, Read, and Exit

To read data from a file and then shut down the program, we follow this logical sequence:

A. sys_open (RAX = 2)

Before you can touch a file, you must “open” it.

RDI: The address of the filename string (e.g., filename db “data.txt”, 0).

RSI: Flags. 0 = Read-only, 1 = Write-only.

RDX: Mode (File permissions, usually used when creating a file).

B. sys_read (RAX = 0)

Once the file is opened, the Kernel returns a File Descriptor (FD) inside the RAX register. Now we can read:

RDI: The FD number returned by the previous open call.

RSI: The address of where the data should be written (Buffer address).

RDX: How many bytes to read.

C. sys_write (RAX = 1)

Now that the data is safely parked in our .bss buffer, we want to actually see it. We ask the Kernel to take that data and push it to the terminal screen.

RDI: 1 (The File Descriptor for Standard Output / Terminal).

RSI: The address of the data we want to print (our .bss buffer).

RDX: How many bytes to print.

D. sys_close (RAX = 3)

A true systems programmer always cleans up after themselves. Leaving File Descriptors open causes memory/resource leaks. Since we overwrote our RDI register during the sys_write step, we need to make sure we saved our original file FD somewhere safe (like R8) so we can close it properly. RDI: The File Descriptor of the file we want to close (saved earlier).

E. sys_exit (RAX = 60)

When the task is done, you must tell the OS “I’m leaving.”

RDI: The status code (0 usually means “everything is fine”).

3. The .bss Segment: The Data “Parking Lot”

If your program receives external data (like a keyboard entry or file content), you need a place to store it. This is where the .bss (Block Started by Symbol) segment comes in.

Why use .bss instead of .data?

The .data section is for pre-defined (initialized) data (e.g., msg db “Hello”). This increases the actual size of your binary file.

The .bss section simply “reserves” space. It doesn’t increase the file size on disk; it only ensures that when the program runs, the RAM reserves that much space for you.

How Data is Written to .bss

When you execute a sys_read syscall, you provide the address of a label defined in .bss to the RSI register. The Kernel then takes the bytes it reads from the file/keyboard and lays them out in memory starting from that specific address.

4. Implementation: A Minimalist File Reader

The following code opens a file, reads its content into a .bss buffer, and exits gracefully.

section .data
    filename db "test.txt", 0   ; Null-terminated string for the filename

section .bss
    buffer resb 64              ; Reserve 64 bytes for our incoming data

section .text
    global _start

_start:
    ; 1. OPEN the file (sys_open)
    mov rax, 2                  ; syscall: sys_open
    mov rdi, filename           ; 1st param: address of the filename
    mov rsi, 0                  ; 2nd param: O_RDONLY flag (read only)
    xor rdx, rdx                ; 3rd param: mode (0, clear garbage data)
    syscall                     ; Execute. RAX now contains the FD
    
    push rax                    ; PRO TIP: Save the FD to the stack instead of a register!

    ; 2. READ the data (sys_read)
    mov rdi, rax                ; 1st param: Move FD from RAX to RDI
    mov rax, 0                  ; syscall: sys_read
    mov rsi, buffer             ; 2nd param: destination buffer in .bss
    mov rdx, 64                 ; 3rd param: number of bytes to read
    syscall                     ; Execute. The data is now in the buffer

    ; 3. WRITE the data to terminal (sys_write)
    mov rax, 1                  ; syscall: sys_write
    mov rdi, 1                  ; 1st param: File Descriptor 1 (stdout)
    mov rsi, buffer             ; 2nd param: source buffer in .bss
    mov rdx, 64                 ; 3rd param: number of bytes to write
    syscall                     ; Execute. Data prints to the screen

    ; 4. CLOSE the file (sys_close)
    pop rdi                     ; 1st param: Pop the saved FD directly into RDI!
    mov rax, 3                  ; syscall: sys_close
    syscall                     ; Execute. File is successfully closed!

    ; 5. EXIT the program (sys_exit)
    mov rax, 60                 ; syscall: sys_exit
    xor rdi, rdi                ; 1st param: exit code 0 (success)
    syscall                     ; Execute. Program terminates cleanly

Conclusion

In Assembly, everything is about addressing and register management. Whether data comes from the terminal (FD 0) or a file is just a parameter change for the Kernel. By mastering the .bss section and syscall registers, you gain total control over how your program interacts with the system memory and hardware.

In the low-level world, every byte is under your command. Keep coding!