Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
479 views
in Technique[技术] by (71.8m points)

c - Finding stack frame size

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...