aarch64-pg
4.4 · Printing Integer and Other Data Types in ARMv8 AArch64 Assembly

Printing Integer and Other Data Types in ARMv8 AArch64 Assembly

note

The Assembly language discussed in this lesson refers specifically to ARMv8 AArch64 Assembly.

prerequisite

Before starting this lesson, you should have a basic understanding of the following prerequisite lesson:

Printing integers and other data types in assembly is slightly more involved than printing a string; however, the process remains straightforward once the underlying steps are understood. In this lesson, we will examine how to print integers and other data types in the low-level Assembly programming language.

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. A similar approach is used when printing data types in Assembly, where the programmer must provide the correct format specifier and pass the corresponding value accordingly to the printf function.

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    ..    // Loading the format string (format1) into x0 for printing an integer.    ldr x0, =format1    // passing the integer value to be printed to next argument register.    // calling printf to print the integer value.    ..    // Loading the format string (format2) into x0 for printing a character.    ldr x0, =format2    // passing the character value to be printed to next argument register.    // calling printf to print the character value.    ..    // 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. This explains why the code contains two separate ldr instructions.

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 instead. For example, printing a float or double value requires passing the value through the appropriate s or d register. Make sure that values are stored and passed using the correct register type that matches the corresponding format specifier, as using the wrong register can cause unintended behaviour.

Passing the Corresponding 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    ..    // Loading the format string (format1) into x0 for printing an integer.    ldr x0, =format1    // passing the integer value to be printed to next argument register.    mov x1, 67    // calling printf to print the integer value.    ..    // Loading the format string (format2) into x0 for printing a character.    ldr x0, =format2    // passing the character value to be printed to next argument register.    mov x1, 'L'    // calling printf to print the character value.    ..    // 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. This ensures that the correct values are passed to the corresponding format specifiers in the format strings.

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    ..    // Loading the format string (format1) into x0 for printing an integer.    ldr x0, =format1    // passing the integer value to be printed to next argument register.    mov x1, 67    // calling printf to print the integer value.    bl printf    // Loading the format string (format2) into x0 for printing a character.    ldr x0, =format2    // passing the character value to be printed to next argument register.    mov x1, 'L'    // calling printf to print the character value.    bl printf    ..    // program epilogue
Open in playground

The code above calls the printf function to print the format string along with the corresponding values passed through the argument registers.

note

In Assembly, you can call any function using the bl instruction. To call a function, you can use the bl instruction followed by the function name.

Putting it All Together

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...
figure 4.4.1runnable — step it and watch the registersOpen in playground