Now that you have a firm grasp of GDB and know what an assembly program looks like, let’s add some complexity. In this chapter, we will show how to obtain the length of a string variable. We will show how to print integer and floating-point values using printf. And we will expand your knowledge of GDB commands.
alive.asm
makefile for alive.asm
Save this file and quit the editor.
Analysis of the Alive Program
The $-msg1-1 part means this: take this memory location ($) and subtract the memory location of msg1. The result is the length of msg1. That length, -1 (minus the string-terminating zero), is stored in the constant msg1Len.
Note the use of a function prologue and function epilogue in the code. These are needed for GDB to function correctly, as pointed out in the previous chapter. The prologue and epilogue code will be explained in a later chapter.
So, you get the decimal and hexadecimal values stored at memory location radius.
(Notice the floating-point error?)
Look at lines 10 and 11, where on the left you can find the hexadecimal representation of radius and pi. Instead of 0165, you find 6501, and instead of 40091EB851EB851F, you find 1F85EB51B81E0940. So, the bytes (1 byte is two hex numbers) are in reverse order!
This characteristic is called endianness . The big-endian format stores numbers the way we are used to seeing them, with the most significant digits starting at the left. The little-endian format stores the least significant numbers starting at the left. Intel processors use little-endian, and that can be very confusing when looking at hexadecimal code.
Why do they have such strange names like big-endian and little-endian?
In 1726, Jonathan Swift wrote a famous novel, Gulliver’s Travels. In that novel appear two fictional islands, Lilliput and Blefuscu. Inhabitants of Lilliput are at war with the people of Blefuscu about how to break eggs: on the smaller end or on the bigger end. Lilliputs are little endians, preferring to break their eggs on the smaller end. Blefuscus are big endians. Now you see that modern computing has traditions rooted in the distant past!
Take the time to single-step through the program (break main, run, next, next, next…). You can see that GDB steps over the function prologue. Edit the source code, delete the function prologue and epilogue, and re-make the program. Single-step again with GDB. In our case, GDB does refuse to single-step and completely executes the program. When assembling with YASM, another assembler based on NASM, we can safely omit the prologue and epilogue code and step through the code with GDB . Sometimes it is necessary to experiment, tinker, and Google around!
Printing
However, there are two other variables that were not defined as strings: radius and pi. Printing these variables is a bit more complex than printing strings. To print these variables in a similar way as we did with msg1 and msg2, we would have to convert the values radius and pi into strings. It is perfectly doable to add code for this conversion into our program, but it would make our small program too complicated at this point in time, so we are going to cheat a little bit. We will borrow printf , a common function, from the program language C and include it in our program. If this is upsetting you, have patience. When you become a more advanced assembler programmer, you can write your own function for converting/printing numbers. Or you could conclude that writing you own printf function is too much waste of time....
hello4.asm
So, we start by telling the assembler (and the linker) that we are going to use an external function called printf. We created a string for formatting how printf will display msg. The syntax for the format string is similar to the syntax in C; if you have any experience with C, you will certainly recognize the format string. %s is a placeholder for the string to be printed.
Do not forget the function prologue and epilogue. Move the address of msg into rsi, and move the address of the fmtstr into rdi. Clear rax, which in this case means there are no floating-point numbers in the xmm registers to be printed. Floating-point numbers and xmm registers will be explained later in Chapter 11.
makefile for hello4.asm
Make sure the -no-pie flag is added in the makefile; otherwise, the use of printf will cause an error. Remember from Chapter 1 that the current gcc compiler generates position-independent executable (pie) code to make it more hacker-safe. One of the consequences is that we cannot simply use external functions anymore. To avoid this complication, we use the flag -no-pie.
Build and run the program. Google the C printf function to get an idea of the possible formats. As you will see, with printf we have the flexibility of formatting the output as print integers, floating-point values, strings, hexadecimal data, and so on. The printf function requires that a string is terminated with 0 (NULL). If you omit the 0, printf will display everything until it finds a 0. Terminating a string with a 0 is not a requirement in assembly, but it is necessary with printf, GDB, and also some SIMD instructions (SIMD will be covered in Chapter 26).
makefile for alive2.asm
We added three strings for formatting the printout. Put the format string in rdi, point rsi to the item to be printed, put 0 into rax to indicate that no floating-point numbers are involved, and then call printf. For printing a floating-point number, move the floating-point value to be displayed in xmm0, with the instruction movq . We use one xmm register, so we put 1 into rax. In later chapters, we will talk more about XMM registers for floating-point calculations and about SIMD instructions.
This means: take the content at address radius and put it in rsi. The function printf wants a memory address for strings, but for numbers it expects a value, not a memory address. Keep that in mind.
A warning about printf: printf takes a format string, and that format string can take different forms and can convert the nature of values printed (integer, double, float, etc.). Sometimes this conversion is unintentional and can be confusing. If you really want to know the value of a register or variable (memory location) in your program, use a debugger and examine the register or memory location.
Summary
Additional GDB functionality
Function prologue and epilogue
Big endian versus small endian
Using the C library function printf for printing strings, integers, and floating-point numbers