Search code examples
assemblyx86-64masmcallstackstack-frame

What are the "extra" 32 bytes on the Windows stack?


I'm learning assembly on Windows and trying to figure out what the values on the stack are.
The Visual C++ documentation says that the values above RSP are:

  • Allocated space
  • Saved RBP
  • Return address
  • Registers home (RCX, RDX, R8, R9)
  • Function parameters

The problem is that there are 32 extra bytes in the stack not mentioned in the documentation.

In the memory snapshot RSP starts at 0x0000000000DAF5E0. The colored boxes are:

  • Yellow: two 64 bits variables with value 9
  • White: saved old RBP + return address
  • Blue: function parameters
  • Green: registers home
  • Red: ?

What could those bytes in red be?

MASM source code built with VS2019, MASM64 and running in x64 debug mode.

C++ flags: /JMC /permissive- /GS /W3 /Zc:wchar_t /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc142.pdb" /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\ConsoleApplication1.pch" /diagnostics:column

.code

; int64_t StackFrameDemo_(int8_t a, int16_t b, int32_t c, int64_t d, int8_t e, int16_t f, int32_t g, int64_t h)
StackFrameDemo_ proc frame

; prolog
push rbp
.pushreg rbp

; allocate 16 bytes on the stack
sub rsp, 16
.allocstack 16
.endprolog

; save registers to register home
mov qword ptr [rbp+8], rcx
mov qword ptr [rbp+16], rdx
mov qword ptr [rbp+24], r8
mov qword ptr [rbp+32], r9

; save the two variables
mov rax, 9
mov [rsp], rax
mov [rsp+type qword], rax

nop ; set the break point here to view memory

; epilog
add rsp, 16 ; release local stack space
pop rbp     ; restore caller's rbp register

ret

StackFrameDemo_ endp

end

Solution

  • You forgot to do mov rbp, rsp (after push rbp) to make RBP a frame pointer into your stack frame.

    Your "home space" aka shadow space is 32 bytes above your return address, you're just not using it. (And instead violating the calling convention by storing relative to some register that could have any value. In this case your caller was probably also using RBP for a legacy frame pointer, so you're probably just stepping on your caller's home space.)


    Note that 0xCC is the value MSVC debug mode uses to poison the stack, helping detect reads of uninitialized memory. (And if you accidentally execute memory with those contents, it's the x86 int3 debug breakpoint instruction.)


    And BTW, when you're using RBP as a traditional frame pointer, mov rsp, rbp / pop rbp / ret is more efficient than add rsp, 16 / pop rbp. Slightly smaller code-size, and some CPUs do mov-elimination to avoid needing an execution unit for mov. This would have broken more noisily for you, like maybe returning from your caller's return address, which you might have noticed when single-stepping!

    (leave = mov/pop, so you can use that for even smaller code-size. It's ok for performance, unlike enter; GCC uses leave in functions with a frame pointer that end with RSP not already pointing at the saved RBP. Some other compilers prefer mov/pop. But compilers usually only use add rsp, n when they weren't using a frame pointer at all.)

    Frame pointers are optional, and are not a required part of stack-frame layout. Directives like .allocstack 16 create the metadata that makes stack unwinding possible without the traditional linked list of frame pointers.