Write Syscall in Assembly

In x86-64 assembly on Linux, the write system call is commonly used to write output to the console.

Write has a syscall number of one (1), and it takes in three (3) arguments: an integer file descriptor (usually ‘1’ for stdout), a const for an address pointer with the location of the string to be printed, and the length to be printed.

These arguments need to be set using the appropriate registers:

  • rax should contain the syscall number (in this case, one ‘1’).
  • rdi contains the first argument (typically one ‘1’ for stdout).
  • rsi contains the second argument – the address pointer.
  • rdx contains the third argument, which is the length.

Once we set these values, we can perform the system call using the syscall instruction.

The following example shows how this looks in practice:

mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, length
syscall

In this example, we use the mov instruction to first place the appropriate values into the designated registers. Then when we use the syscall instruction, it will print the contents of the message variable to the console.

Of course, this isn’t a complete example. Let’s look at the write syscall inside a working “Hello, World!” program to see it in action:

global _start

section .data
    message db "Hello, World!"

section .text
_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, 13
    syscall

    mov rax, 60
    mov rdi, 0
    syscall

Output:

$ ./helloworld
Hello, World!

Note that this program uses two system calls: the first is the write syscall, which prints “Hello, World!” to the standard output (stdout). The second is the exit syscall, which terminates the program.

Without the exit syscall, the program will still print the string, but it will also throw an error. Try it!

Using EQU to Dynamically Get Length

In the previous example, we manually set the value of rdx to 13, which is the length of the “Hello, World!” string. But what if we want to use a different string?

Let’s use “Hello, Assembly!” as an example. This string has a length of 15, which means that the last two characters would be cut off if rdx still holds a value of 13.

Of course, we could simply count the number of characters and manually update rdx. But this is tedious, prone to error, and more importantly, lacks flexibility that we may want.

To get our write syscall to work regardless of the string length, we can use equ to get and store the length of the message variable into a new variable called length. We do this within the .data section:

section .data
    message db "Hello, Assembly!"
    length equ $-message

Let’s see how this looks within the context of our program:

global _start

section .data
    message db "Hello, Assembly!"
    length equ $-message

section .text
_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, length
    syscall

    mov rax, 60
    mov rdi, 0
    syscall

This time instead of setting rdx to a numerical value, we instead use the length variable to perform this function dynamically.