AArch64 Playground
4.5 · Printing a string with printf

Printing a string with printf

prerequisite

Read this first:

This lesson prints a string to standard output in assembly, which takes more work than a print statement in a high-level language. Standard output is the stream of text a program writes as it runs; in the playground it appears in the console.

A high-level language has a built-in way to print. In C, it is the printf function:

#include <stdio.h>int main() {    printf("Hello, World!\n");    return 0;}

The string "Hello, World!" is passed to printf as an argument, and printf writes it to standard output.

In assembly, printing takes a few more steps.

The big picture

Assembly prints by calling the same C printf function. The first thing printf receives is its format string: the text to print, which can also hold placeholders for values (the next lesson covers those). To print a string:

  1. Store the string in a variable.
  2. Load the variable's address into the first argument register, x0.
  3. Call the printf function.

Storing the string

The first step in printing a string in assembly is to store the string in a variable. In assembly, a variable is a label, a name followed by a colon, that marks where a value sits in memory. The .data directive starts the data section, the part of the program that holds values rather than instructions, and the .string directive stores the string there. The following code does this:

.datagreeting:    .string "Hello, World!\n"

In the above code, the string "Hello, World!\n" is stored in a variable named greeting. The .string directive writes the characters followed by a zero byte, which is how printf knows where the text ends. The \n at the end is a single character, the newline, which moves any later output to a fresh line.

Loading the address

The next step in printing a string in assembly is to load the variable's address into the appropriate register. This is done using the ldr instruction with an = in front of the label. An address is 64 bits wide, too wide for any immediate, so the assembler stores it in a small table next to the code, called the literal pool, and ldr loads it from there. The following code does this:

        ldr     x0, =greeting                // x0 = address of greeting

The address goes in x0 because x0 is the first argument register, which is where printf looks for the format string. printf receives the address of the string, not the characters themselves, and reads the characters from memory up to the zero byte.

Calling printf

The final step in printing a string in assembly is to call the printf function. This is done using the bl instruction, short for branch with link: it jumps to printf and stores the address of the next instruction in lr (x30), so printf knows where to come back to. The following code does this:

        ldr     x0, =greeting        bl      printf

bl calls printf, which prints the string whose address is in x0.

The whole program

The following program puts the three steps together. The two lines at the start of main, the prologue, save fp and lr, because bl printf overwrites lr and main still needs it to return. The ldp before ret, the epilogue, puts them back. The stack lesson explains both line by line. mov w0, 0 sets the value main returns, which becomes the program's exit status: 0 means the run succeeded.

Run it and the console shows Hello, World!.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

note

Because the string is a format string, printf treats a % in it as the start of a placeholder. To print a percent sign itself, write %%: the string "100%% done\n" prints 100% done.

Two calls, one line

printf prints exactly the characters it is given and adds nothing of its own, not even a newline. The program below prints two strings with two calls. The first string has no \n, so the second call carries on along the same line, and the output is Two calls, one line.

Add \n to the end of the first string and run it again: the output now spans two lines. Notice too that x0 is loaded again before the second call. printf may change x0, so the first address is not still there.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

Check yourself

  1. What does .string add after the characters you write, and why does printf need it?
  2. Which register holds the string's address when printf is called?
  3. One call prints Loading... and the next prints done, and the console shows Loading...done. What is missing?

answers

show answers
  1. A zero byte; printf stops reading there.
  2. x0, the first argument register.
  3. A \n at the end of the first string.

Practice