© Jo Van Hoey 2019
J. Van HoeyBeginning x64 Assembly Programminghttps://doi.org/10.1007/978-1-4842-5076-1_13

13. Stack Alignment and Stack Frame

Jo Van Hoey1 
(1)
Hamme, Belgium
 

When your main program calls a function, it will push an 8-byte return address on the stack. That 8-byte address is the address of the instruction to be executed after the function. So, when the function ends, the program execution will find the return address from the stack and continue operation after the function call. Inside the function, we can also use the stack for different purposes. Every time you push something on the stack, the stack pointer will decrease by 8 bytes, and every time you pop something from the stack, the stack pointer will increase by 8 bytes. So, we have to make sure to “restore” the stack to the appropriate value before we leave the function. Otherwise, the executing program would have a wrong address for the instruction to be executed after the function call.

Stack Alignment

In the Intel manuals, you will find mention of a requirement that the stack has to have a 16-byte alignment when you call a function. This may sound a bit weird, as the stack is built in 8-byte (or 64-bit) memory. The reason is that there are SIMD instructions that perform parallel operations on larger blocks of data, and these SIMD instructions may require that these data are located in memory on addresses that are multiples of 16 bytes. In previous examples, when we used printf with xmm registers, we aligned the stack on 16 bytes, without explicitly telling you. Go back to Chapter 11 on floating-point arithmetic, where we crashed the program by commenting out push rbp and pop rbp. The program crashed because deleting these instructions caused the stack to be not aligned. If you use printf without xmm registers, you can get away without stack alignment, but if you do that, bugs are going to bite you someday.

We will discuss SIMD and alignment in later chapters, so don’t worry if the previous explanation does not make sense to you. For now, keep in mind that when you call a function, you need to align the stack on an address that is a multiple of 16 bytes.

As far as the processor is concerned, main is just another function. Before your program starts execution, the stack is aligned. Just before main starts, an 8-byte return address is pushed onto the stack, which means the stack is not aligned upon the start of main. If the stack is not touched between the start of main and the call of a function, the stack pointer rsp is not 16-byte aligned. You can verify that by looking at rsp: if rsp ends with 0, it is 16-bit aligned. To make it zero, you push something onto the stack so that it becomes 16-bit aligned. Of course, do not forget the corresponding pop instruction later.

This alignment requirement is one of the reasons for using a prologue and an epilogue. The first instruction in main and in a function should push something onto the stack to align it. That’s the reason for the prologue instruction push rbp. The rbp register is also called the base pointer .

Why are we using rbp? In the prologue, when using stack frames (explained later), rbp is modified, so before rbp is used in a stack frame, it is pushed onto the stack to preserve it when returning. Even when not building a stack frame, rbp is the ideal candidate to align the stack because it is not used for argument passing to a function. Argument passing will be discussed later in the chapter. In the prologue, we also use the instruction mov rbp,rsp. This instruction preserves rsp, which is our stack pointer containing the return address. The prologue instructions are reversed in the epilogue; needless to say, it is best to not meddle with rbp! In future chapters, you will see a number of other methods to align the stack.

Listing 13-1 shows some source code to play with. Keep an eye on rsp when debugging and stepping through the program with SASM. Comment out push rbp and pop rbp and see what happens. If the program execution arrives at printf with an unaligned stack, the program will crash. That is because printf definitely requires alignment.

In this program, we do not use complete prologues and epilogues; that is, we do not build stack frames. We only use push and pop to illustrate alignment.
; aligned.asm
extern printf
section .data
      fmt  db     "2 times pi equals %.14f",10,0
      pi   dq     3.14159265358979
section .bss
section .text
;-----------------------------------------------
func3:
      push  rbp
            movsd       xmm0, [pi]
            addsd       xmm0, [pi]
            mov   rdi,fmt
            mov   rax,1
            call  printf     ; print a float
      pop   rbp
      ret
;-----------------------------------------------
func2:
      push  rbp
            call  func3 ; call the third function
      pop   rbp
      ret
;-----------------------------------------------
func1:
      push  rbp
            call  func2 ; call the second function
      pop   rbp
      ret
;-----------------------------------------------
      global main
main:
      push  rbp
            call  func1 ; call the first function
      pop   rbp
ret
Listing 13-1

aligned.asm

Note that if you do a certain number of calls (even or odd, depending how you start), the stack will be 16-byte aligned even if you do not push/pop to align, and the program will not crash. Pure luck!

More on Stack Frames

You can distinguish two types of functions: branch functions and leaf functions. Branch functions contain calls to other functions, while leaf functions execute some commands and then return to the parent function without calling any other function.

In principle, every time you call a function, you need to build a stack frame. This is done as follows: in the called function, you first align the stack on a 16-byte border, that is, push rbp. Then you save stack pointer rsp into rbp. When leaving the function, restore rsp and pop rbp to restore rbp. That is the role of the function prologue and epilogue. Inside the function, register rbp now serves as an anchor point to the original stack location. Every time a function calls another function, the new function should build its own stack frame.

Inside a leaf function, you can in general ignore stack frame and stack alignment; it is not necessary as long as you don’t mess with the stack. Note that when you call, for example, printf in your function, your function is not a leaf function. Similarly, if your function does not use SIMD instructions, you do not need to care about alignment.

Compilers have optimizing functionality, and sometimes when you look at code generated by compilers, you will find that there was no stack frame used. That happens when the compilers noticed during optimizing that a stack frame is not needed.

Anyway, it is a good habit to always include a stack frame and check the stack alignment; it can save you a lot of trouble later. A good reason to include a stack frame is the fact that GDB and GDB-based debuggers (such as DDD and SASM) expect to find a stack frame. If there is no stack frame in your code, the debugger will behave unpredictably, such as ignoring breakpoints or jumping over instructions. Take some code from a previous chapter (e.g., alife.asm), comment away the function prologue and epilogue, and then start GDB and see what happens.

As an additional exercise, look at the code from the previous chapter (function2.asm) with SASM or GDB and see how the stack remains aligned during the execution.

Here is an additional shortcut: you can substitute the function prologue for the instruction enter 0,0 and the function epilogue for the instruction leave. However, enter has poor performance, so you can just continue to use push rbp and mov rbp, rsp if you think performance is an issue. The instruction leave has no such performance problem.

Summary

In this chapter, you learned about the following:
  • Stack alignment

  • Using stack frames

  • Using SASM to check the stack pointer

  • Entering and leaving instructions

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.190.175