Printing integers and characters
prerequisite
Read these first:
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:
- 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 .. ldr x0, =format1 .. ldr x0, =format2 .. // 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.
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 epilogueIn 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 epiloguenote
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.