-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
This problem came up while I was attempting to implement debug.StackIterator
for UEFI, where I must use FP-based unwinding due to the lack of dwarf information.
Frame pointer address is stored incorrectly
Consider the following function preamble from a windows binary (UEFI exhibits the same behavior):
; x86_64-windows
push rbp ; save frame pointer
sub rsp, 0x130 ; allocate stack space
lea rbp, [rbp+0x80] ; attempt to find frame pointer?
rbp vs rsp
The first observation here is that rbp
is definitely the frame pointer, as is the case for non-windows systems. Not shown here are nearly all stack offsets being calculated from rbp
instead of rsp
.
- The first problem I encountered is that the
@frameAddress
builtin on both Windows and UEFI return the value ofrsp
instead ofrbp
.
rbp offset
The second observation here is that rbp
does not point to the frame pointer, but rather to 0x80
bytes below the top of the stack. This offset is never greater than 0x80
bytes, even if the stack space allocated is greater than 0x80
bytes.
If the stack space allocated is less than 0x80
bytes, rbp
generally points to the frame pointer as expected (there is an edge case I believe is related to other callee saved registers).
The following is an example of a preamble that leaves the frame pointer in rbp
as expected:
; x86_64-windows
push rbp ; save frame pointer
sub rsp, 0x50 ; allocate stack space
lea rbp, [rsp+0x50] ; work backwards to calculate frame pointer
What should be happening
The first thing that needs to be fixed is @frameAddress
needs to be updated to return the address of the frame pointer on Windows and UEFI. It currently does not.
There are two ways to fix the rbp offset problem:
-
Save the frame pointer immediately after pushing it on the stack. This is what all other systems do, it works for any stack allocation size, and doesn't require any weird calculations.
-
Continue backtracking from the top of the stack, but allow offsets greater than
0x80
bytes, so that the frame pointer is always inrbp
.
For example, the following preamble is the same program, but compiled for linux, and it works as expected:
; x86_64-linux
push rbp ; save frame pointer to stack
mov rbp, rsp ; save address of frame pointer to rbp
sub rsp, 0x30
Conclusion
On Windows and UEFI, the frame pointer cannot be reliably unwound, because all it takes is one function with the frame pointer stored incorrectly to break the rest of the stack trace.