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:
- Define a format string containing the appropriate format specifier.
- Load the address of the format string into
x0. - Place the value to be printed into the appropriate argument register.
- 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"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 epilogueIn 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 epilogueIn 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 epilogueThe 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.