aarch64-pg
4.4 · Printing integers and characters

Printing integers and characters

prerequisite

Printing an integer takes one step more than printing a string: the format string names a type, and the value goes in the next argument register.

In the C programming language, printing different data types is accomplished by providing the appropriate format specifier to the printf function along with the corresponding value. For example, printing an integer requires the integer format specifier (%d), while printing a character requires the character format specifier (%c). This concept can be demonstrated using the following C code:

#include <stdio.h>int main() {    int myInteger = 42;    char myCharacter = 'A';    printf("Integer: %d\n", myInteger);    // Output:    // Integer: 42    printf("Character: %c\n", myCharacter);    // Output:    // Character: A    return 0;}

The format specifier informs printf how the provided data should be interpreted and displayed. Assembly works the same way: give printf the right format specifier and put the value in the right register.

The big picture

Just like printing strings, printing other data types in AArch64 Assembly uses the printf function. The general process is:

  1. Define a format string containing the appropriate format specifier.
  2. Load the address of the format string into x0.
  3. Place the value to be printed into the appropriate argument register.
  4. Call printf.

The example below demonstrates how to use printf in Assembly to print both an integer and a character. In this example, the program prints the integer 67 and the character L.

Defining the format string

The first step in printing an integer and a character is to define a format string containing the appropriate format specifiers. Since we are printing both an integer and a character, we need a string that includes the %d format specifier for the integer and the %c format specifier for the character.

This can be achieved by declaring a string variable in the .data section. The following code demonstrates this process:

    .data    // Format string for printing an integer    format1:   .string  "Integer: %d\n"    // Format string for printing a character    format2:   .string  "Character: %c\n"
Open in playground

In the above code, the variable format1 serves as the format string for printing an integer using the %d format specifier. Similarly, the variable format2 serves as the format string for printing a character using the %c format specifier. Both strings are defined using the .string directive.

note

You can print any data types in Assembly by defining the appropriate format string with the corresponding format specifier. The format specifiers for different data types are similar to those used in C programming.

Loading the format string address

Loading the format string address is done in the same way as loading the address of a string variable when printing a string. The ldr instruction can be used to load the address of the format string into the appropriate register.

The following code demonstrates the process of loading the format string address:

// Pseudo-ops and directivesmain:    // Program prologue    ..    ldr x0, =format1    ..    ldr x0, =format2    ..    // program epilogue
Open in playground

In the code above, the address of the format strings for the integer (format1) and character (format2) are loaded into the register x0. Each format string is loaded into x0 before its corresponding printf call because the integer and character are printed using different format strings.

note

The format string is always loaded into the first argument register (x0) before calling printf, regardless of the data type being printed. This is because printf expects the format string to be passed as its first argument.

The corresponding values are then passed to the subsequent argument registers (x1, x2, etc.). If the format string contains multiple format specifiers, the corresponding values must be passed in the order they appear in the format string.

However, not all data types are passed through the general-purpose x or w registers. Some data types, such as floating-point values, use dedicated floating-point registers instead. For example, printing a float or double value requires passing the value through the appropriate s or d register. Match the register class to the format specifier: the wrong class prints garbage.

Passing the values to print

The corresponding values can be passed to the format string by placing them in the next available argument register. Since the next available argument register is x1, the integer value 67 and the character L are placed in x1 before their respective printf calls.

The following code demonstrates how values are passed to the format string:

// Pseudo-ops and directivesmain:    // Program prologue    ..    ldr x0, =format1    mov x1, 67    ..    ldr x0, =format2    mov x1, 'L'    ..    // program epilogue
Open in playground

In the code above, the integer value 67 and the character L are placed in x1 before their respective printf call.

Calling printf

Once all the required arguments have been set up correctly, the final step is to call the printf function. This can be achieved by using the bl instruction. The following code demonstrates how to call the printf function:

// Pseudo-ops and directivesmain:    // Program prologue    ..    ldr x0, =format1    mov x1, 67    bl printf    ldr x0, =format2    mov x1, 'L'    bl printf    ..    // program epilogue
Open in playground

note

In Assembly you call any function with bl followed by the function name.

The whole program

The following code demonstrates how to print an integer and a character in ARMv8 AArch64 Assembly using the printf function. The program prints the integer 67 and the character L.

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.

Step with F10, run with F5, or feed stdin from the box below.

figure 4.4.1runnable: step it and watch the registersOpen in playground