Skip to content

Frame address is generally incorrect on windows and uefi #18662

@truemedian

Description

@truemedian

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 of rsp instead of rbp.

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:

  1. 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.

  2. Continue backtracking from the top of the stack, but allow offsets greater than 0x80 bytes, so that the frame pointer is always in rbp.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions