22

The stack frame of a caller function can be easily obtained via __builtin_frame_address(1), but what about the stack frame size?

Is there a function that will let me know how big is the stack frame of the caller function?

alexandernst
  • 14,352
  • 22
  • 97
  • 197
  • 2
    `(char *)__builtin_frame_address(1) - (char *)__builtin_frame_address(0)`? –  Feb 01 '14 at 17:37
  • @H2CO3 Let me try it, but I'm not sure how the EIP (x86, RIP for x64) would be handled. – alexandernst Feb 01 '14 at 17:39
  • that's a good question and I'm not a low-level sort of guru, so don't take my word for it :) –  Feb 01 '14 at 17:39
  • @H2CO3 Nope, doesn't make much sense: http://pastebin.com/GUaSrY7c – alexandernst Feb 01 '14 at 17:44
  • What doesn't make sense in that? –  Feb 01 '14 at 17:46
  • @H2CO3 there are 7 ints. In my machine, ```sizeof(int)``` returns ```4```. So I assume that the stack frame should be 4*7=28. Maybe I'm wrong, if so, please explain why? – alexandernst Feb 01 '14 at 17:49
  • Even further, I just objdump-ed the binary, and I see ```48 83 ec 20 sub rsp,0x20``` in the main, which means that GCC reserved 32 bytes instead of the 28 I expect (I guess because of optimisations?). But either way, it's not 48 – alexandernst Feb 01 '14 at 17:51
  • 1
    "So I assume that the stack frame should be 4*7=28" - no, that's not how it works. The stack often needs to be aligned (just like the size of a struct is not the sum of the size of its members, a stack frame is often bigger than it would "seem" necessary). Also, since your variables are unused, they may very well be optimized away. Also, aren't there more operations involving "sp", by the way? (keep in mind that the return address must be stored somewhere too, so maybe that adds to the 32 bytes? etc.) –  Feb 01 '14 at 17:55
  • @H2CO3 this is the dump of main (exclusing the call to the test function): http://pastebin.com/4ZDPd7LL There is a push of 8 bytes (rbp), so if that makes the stack bigger that would be 28 + 8 = 36 bytes of stack, which is still not 48. I guess aligning the stack does sounds reasonable, but maybe not in this exact code (as the dump clearly shows the contrary) – alexandernst Feb 01 '14 at 18:01
  • 1
    Well, seeing the dump, this actually makes sense: `0x20` is 32, not 28. 32 + 8 is 40. If you add the space needed for the return address, you get 48. (I'm not sure this should indeed be done, but something tells me that this way of obtaining the frame size does actually make sense.) –  Feb 01 '14 at 18:04
  • @H2CO3 According this this image http://1.bp.blogspot.com/_n5BzA0M85tM/SdbJduaGHKI/AAAAAAAAARM/yWcZUvFURv0/s320/mem.png (or any other visual representation of a stack), the return address doesn't form part of the frame stack, so adding + 8 + 8 doesn't seem to be the *clean* solution. – alexandernst Feb 01 '14 at 18:19
  • Oh, no, I'm sorry, it does actually. – alexandernst Feb 01 '14 at 18:30
  • @H2CO3 Imho there is something buggy with this solution. http://pastebin.com/2ERgnXsf GCC reserves 32 bytes for the stack, I have 8 ints of 4 bytes each. There is no "empty space" in the stack because of alignment. I'd expect my code to show the values of each variable, but they seem to be misplaced by one. If I rest 4 to ```tmp```, it gets fixed, but I cant really see why? I'm on a x64 machine, so the return address is 8 bytes. – alexandernst Feb 01 '14 at 20:51
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46610/discussion-between-h2co3-and-alexandernst) –  Feb 01 '14 at 21:26

1 Answers1

25

My first reaction would have been, why would anybody want this? It should be considered bad practice for a C function to dynamically determine the size of the stack frame. The whole point of cdecl (the classic C calling convention) is that the function itself (the 'callee') has no knowledge of the size of the stack frame. Any diversion from that philosophy may cause your code to break when switching over to a different platform, a different address size (e.g. from 32-bit to 64-bit), a different compiler or even different compiler settings (in particular optimizations).

On the other hand, since gcc already offers this function __builtin_frame_address, it will be interesting to see how much information can be derived from there.

From the documentation:

The frame address is normally the address of the first word pushed on to the stack by the function.

On x86, a function typically starts with:

push ebp       ; bp for 16-bit, ebp for 32-bit, rbp for 64-bit

In other words, __builtin_frame_address returns the base pointer of the caller's stack frame. Unfortunately, the base pointer says little or nothing about where any stack frame starts or ends; the base pointer points to a location that is somewhere in the middle of the stack frame (between the parameters and the local variables).

If you are only interested in the part of the stack frame that holds the local variables, then the function itself has all the knowledge. The size of that part is the difference between the stack pointer and the base pointer.

register char * const basepointer  asm("ebp");
register char * const stackpointer asm("esp");

size_localvars = basepointer - stackpointer;

Please keep in mind that gcc seems to allocate space on the stack right from the beginning that is used to hold parameters for other functions called from inside the callee. Strictly speaking, that space belongs to the stack frames of those other functions, but the boundary is unclear. Whether this is a problem, depends on your purpose; what you are going to do with the calculated stack frame size?

As for the other part (the parameters), that depends. If your function has a fixed number of parameters, then you could simply measure the size of the (formal) parameters. It does not guarantee that the caller actually pushed the same amount of parameters on the stack, but assuming the caller compiled without warnings against callee's prototype, it should be OK.

void callee(int a, int b, int c, int d)
{
    size_params = sizeof d + (char *)&d - (char *)&a;
}

You can combine the two techniques to get the full stackframe (including return address and saved base pointer):

register char * const stackpointer asm("esp");

void callee(int a, int b, int c, int d)
{
    total_size = sizeof d + (char *)&d - stackpointer;
}

If however, your function has a variable number of parameter (an 'ellipsis', like printf has), then the size of the parameters is known only to the caller. Unless the callee has a way to derive the size and number of parameters (in case of a printf-style function, by analyzing the format string), you would have to let the caller pass that information on to the callee.

EDIT: Please note, this only works to let a function measure his own stack frame. A callee cannot calculate his caller's stack frame size; callee will have to ask caller for that information.

However, callee can make an educated guess about the size of caller's local variables. This block starts where callee's parameters end (sizeof d + (char *)&d), and ends at caller's base pointer (__builtin_frame_address(1)). The start address may be slightly inaccurate due to address alignment imposed by the compiler; the calculated size may include a piece of unused stack space.

void callee(int a, int b, int c, int d)
{
   size_localvars_of_caller = __builtin_frame_address(1) - sizeof d - (char *)&d;
}
Ruud Helderman
  • 10,563
  • 1
  • 26
  • 45
  • Thank you! This answer is is well detailed, but the edit disappointed me... I should think of another way to *do crazy stuff* with the stack :) – alexandernst Feb 01 '14 at 23:26
  • @alexandernst: I see one small opportunity... please see my latest edit (the 'however' paragraph at the bottom). – Ruud Helderman Feb 02 '14 at 00:04
  • I was thinking about yet another option (hold on, crazy things comming). Get the start of the caller and then use udis86 to actually see how much stack was reserved. – alexandernst Feb 02 '14 at 00:13
  • @alexandernst: So you mean, use a disassembler to spot e.g. `sub esp, 56` (typically the third instruction in a C function)? That's going in the direction of static analysis rather than dynamic analysis. Before going into a detailed discussion there (this could become a different answer in itself), I would very much like to know more about your motives; _why_ do you need the stack frame size? Is it a purely hypothetical situation, is it for debugging or logging purposes, or do you actually have a real-life use case where this would be useful? – Ruud Helderman Feb 02 '14 at 08:49
  • Those `void *`s should really be `char *`s. –  Feb 02 '14 at 09:13
  • 1
    @H2CO3: Thanks, you are right; I fixed it. For those interested: http://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c – Ruud Helderman Feb 02 '14 at 09:20
  • @Ruud Thanks. Nice, detailed write-up btw. –  Feb 02 '14 at 09:21
  • @Ruud The final purpose is being able to access the local variables of the caller, as in http://stackoverflow.com/questions/21498749/poking-the-stack – alexandernst Feb 02 '14 at 11:32
  • @alexandernst: Well, it was a nice experiment, but totally unsuitable for production code; it hurts maintainability. If callee needs locals from caller, use parameter passing. If the number of parameters troubles you, then combine them in one big `struct`. Effectively, you are defining your own sub-stackframe, with a well-defined address and size. Syntactic verbosity and lack of ad-hoc data exchange: in my opinion that's a small price to pay, compared to the simplicity and maintainability of straightforward parameter passing. – Ruud Helderman Feb 02 '14 at 16:01