Is reserving stack space necessary for functions less than four arguments?
Asked Answered
C

2

19

Just started learning x64 assembly and I have a question about functions, arguments, and the stack. As far as I understand it, the first four arguments in a function get passed to rcx, rdx, r8, and r9 registers (and xmm0-xmm3 for floats) in Windows. So a trivial addition function with four parameters would looks like this:

add:
   mov r10, rcx
   add r10, rdx
   add r10, r8
   add r10, r9
   mov rax, r10
   ret

However, I've come across documentation that mentions this:

At a minimum, each function must reserve 32 bytes (four 64-bit values) on the stack. This space allows registers passed into the function to be easily copied to a well-known stack location. The callee function isn't required to spill the input register params to the stack, but the stack space reservation ensures that it can if needed.

So, do I have to reserve stack space even if the functions I'm making take four parameters or less, or is it just a recommendation?

Cicatrix answered 12/9, 2011 at 18:39 Comment(4)
agner.org/optimize/optimizing_assembly.pdf chapter 4 has an example that seems to indicate that you have to always reserve space.Nica
Damn, too late for edit. oldnewthing blog entry on the amd64 calling convention.Nica
Another piece of the puzzle for you: you have a leaf function, which means it does not call other functions.Slattery
MS's calling convention docs (learn.microsoft.com/en-us/cpp/build/…) mention the shadow space / home space, but seem to assume you already know the meaning of the term "shadow space", and aren't 100% explicit about the fact that every function can freely use 32 bytes of space above its return address.Amphiaster
M
14

Your quote is from the "calling convention" part of the documentation. At the very least, you do not have to worry about this if you do not call other functions from your assembly code. If you do, then you must respect, among other things, "red zone" and stack alignment considerations, that the recommendation you quote is intended to ensure.

EDIT: this post clarifies the difference between "red zone" and "shadow space".

Mannos answered 12/9, 2011 at 18:55 Comment(8)
I have seen "red zone" used in System V documents, but Windows documents use "shadow space". Are these the same thing? If no-one knows, I'll ask in another question.Mannos
Partially answering myself: no, "red zone" and "shadow space" are not the same thing. "Shadow space" is the name of the 32-bytes reserved on the stack. It's reserved by the caller, so it's not optional if you want you function to be callable with the usual ABI, but you shouldn't have to worry about it.Mannos
So all I would have to do to make that above code sample "proper" is add 'sub rsp, 32' to the beginning and 'add rsp, 32' to the end, correct?Cicatrix
The red zone that the UNX x86_64 ABI talks about are 128 Bytes of stackspace immediately below the current value of the stackpointer (i.e. within what's traditionally considered the _unused_ part of the stack). Asynchronous events (UNX signals) must not overwrite these when redirecting execution to the signal handler. The effect is that the space between -128(%rsp) ... (%rsp) is assigned to the currently executing function, you get 128 Bytes of stack "automatically". For frequently-called small funcs, saving a few instructions to setup/tear down a stackframe gives a noticeable benefit.Warplane
Re previous comment, this "automatic stackspace reservation" the red zone gives you obviously only applies if your function is a so-called leaf function, i.e. it doesn't call any other non-inlined functions, and never uses push to write data to the stack (but does mov -...(%rsp) instead). Only then can it get away with not setting up its own stackframe (and use the red zone).Warplane
@Cicatrix This is what I understand (I don't have a 64-bit Windows to check this on). This space, reserved at the beginning of your function and freed at the end, is intended for your function's callees, if any.Mannos
Researching this more, if my function is a leaf function (which I'm pretty sure that example above is), I don't have to allocate stack space, right? Only if it's a frame function?Cicatrix
@Don: Correct, shadow space is something the caller reserves for the callee to use. A leaf function doesn't have any callees. If you want, you can use the 32 bytes above your return address as scratch space; your caller is guaranteed to have reserved that shadow space for you regardless of number of args.Amphiaster
F
2

After playing with this more and reading the docs, the 32 bytes need to be reserved for any function that you call. If your function is as simple as the example and you don't call other functions, you don't have to reserve this space. Any function you call however may use this 32 bytes so if you do not reserve them the function may

Also your function may rely on there being 32 bytes available on the stack from the function that called yours if it's following the ABI. Commonly this 32 byte area is used to save registers that will be changed in your function so you can restore their values before returning. I think is is for performance purposes, 32 bytes being chosen as enough to make it so most leaf functions (functions that don't call others) don't need to reserve any stack space, and have temporary room on the stack to save registers and restore them before returning. Take this example:

Calling Function:

CallingFunction:
  push rbp
  mov rbp, rsp
  sub rsp, 40  // $20 bytes we want to use at [rbp+30],
               // plus $20 bytes for calling other functions
               // according to windows ABI spec
  mov rcx, [rsi+10]     // parameter 1 (xmm0 if non-int)
  mov rdx, 10           // parameter 2 (xmm1 if non-int)
  movss xmm2, [rsi+28]  // parameter 3 (r8 if int)
  mov r9, [rsi+64]      // parameter 4 (xmm3 if non-int)
  call MyFunction
  // ... do other stuff
  add rsp, 40           // free space we reserved
  pop rbp
  xor rax,rax
  ret

Called Function

CalledFunction:
  push rbp      // standard
  mov rbp, rsp  // standard

  // should do 'sub rsp, 20' here if calling any functions
  // to give them a free scratch area

  // [rbp] is saved rbp
  // [rbp+8] is return address
  // [rbp+10] to [rbp+2f] are the 0x20 bytes we can
  //     safely modify in this function, this could
  //     be pushed higher if the function had more than 4
  //     parameters and some had to be passed on the stack
  //     or if returning a structure or something that needs
  //     more space.  In these cases the CALLER would have
  //     allocated more space for us

  // the main reason for the 0x20 is so that we can save 
  // registers we want to modify without having to allocate
  // stack space ourselves
  mov [rbp+10], rsi // save rsi in space allocated by caller
  mov [rbp+18], rdi // save rdi in space allocated by caller
  mov rsi, [rcx+20]
  mov rdi, [rsi+48]
  add rdi, [rsi+28]
  mov rax, rdi
  mov rdi, [rbp+18] // restore changed register
  mov rsi, [rbp+10] // restore changed register
  pop rbp
  ret

Original answer

I just ran into this not knowing and it seems to be the case. The first two instructions in GetAsyncKeyState for instance overwrite the stack above the return address in the 0x20 byte area you're supposed to reserve for the callee to use for parameters:

user32.GetAsyncKeyState  - mov [rsp+08],rbx
user32.GetAsyncKeyState+5- mov [rsp+10],rsi
user32.GetAsyncKeyState+A- push rdi
user32.GetAsyncKeyState+B- sub rsp,20
Fog answered 27/3, 2015 at 22:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.