We have already discussed registers, the type of fast temporary storage that can be used to store values or addresses to be used during execution of instructions. There is also the slower storage, memory, where the processor can store values for a longer time. Then there is the stack, a contiguous array of memory locations.
Understanding the Stack
As discussed in Chapter 8, the stack segment starts in high memory, and when it grows, it grows in the downward direction, like an icicle grows downward when it grows larger. Items are placed on the stack with the push instruction and removed from the stack with the pop instruction . Every time you push, the stack grows; every time you pop, the stack shrinks. You can verify this stack behavior by monitoring rsp, the stack pointer, which points to the top (thus actually the bottom, because it grows downward) of the stack.
The stack can be used as temporary storage to save values in registers and call them back later or, more importantly, to transfer values to functions. Functions or procedures will be treated in detail later.
stack.asm
First, note that to calculate the string length, we decreased the length of the string by 1, ignoring the terminating 0. Otherwise, the reversed string would start with a 0. Then the original string is displayed followed by a new line. We will use rax to push the characters, so let’s first initialize rax with zeros using xor. The address of the string goes into rbx, and we will use a loop instruction , so we set rcx to the string length. Then a loop is used to push character after character on the stack, starting with the first character. We move a character (byte) into al. Then we push rax onto the stack. Every time you use push, 8 bytes are moved to the stack. If we did not initialize rax before, it might well be that rax contains values in the upper bytes, and pushing these values to the stack may not be what we want. After that, the stack contains the pushed character plus the additional 0 bits in the bits above al.
When the loop is finished, the last character is at the “top” of the stack, which is in fact at the lowest address of the icicle because the stack grows in the downward direction. Another loop is started that pops character after character from the stack and stores them in memory in the original string, one after another. Note that we only want 1 byte, so we pop to rax and only use al.
In addition to registers, you can push memory and immediate values. You can pop to a register or a memory location but not to an immediate value, which is quite evident.
That’s good to know, but we will not use this here. If you want to push and pop the flag register to the stack, you can use the instructions pushf and use popf.
Keeping Track of the Stack
Select Data ➤ Status Displays in the menu, and scroll down until you find “Backtrace of the stack” and enable it. Set a breakpoint at, for example, main: and then click Run in the floating panel. Now start debugging and step through the program with the Next button (you do not want to step line per line through the printf function). See how the stack is displayed and updated in the upper window. Do not worry about the initial stuff that is displayed. When you arrive at the instruction after the push instruction, you will see that characters are pushed onto the stack in ASCII decimal representation (41, 42, etc.). Watch how the stack decreases during the second loop. That is an easy way to see what is on the stack and in what order.
As we said before, DDD is open source and outdated. There is no guarantee that it will continue working as expected in the future, but for now it is not very elegant, but it will do.
We referred to rsp as $rsp. We increase the stack address every time with 8 ($rsp + 8), because at every push, 8 bytes are sent to the stack. As Type, we specified Char, bytes, 8 bytes, and Address. We chose Characters because we are pushing a string and then it is easy to read for us, and we chose bytes, because we are interested in byte values (al contains 1 byte every time), so 8 bytes are pushed every time. And rsp contains an Address. Step through the program and see how the stack changes.
It works, but you have to detail every stack memory place manually, which can be a burden if you are using a large stack and/or have a lot of additional memory variables you want to keep track of.
Summary
The stack starts at an address in high memory and grows to lower addresses.
Push decreases the stack pointer (rsp).
Pop increases the stack pointer (rsp).
Push and pop work in reverse order.
How to use DDD to examine the stack.
How to use SASM to examine the stack.