[windows_internals]Stack Frame

本文详细解析了栈帧在函数调用中的作用及其构造过程,包括栈的特性、栈帧的内容、函数调用时栈帧的变化等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:http://blog.chinaunix.net/uid-9104650-id-2009601.html

[windows_internals]Stack Frame

Stack is one important segment of the process's memory layout. It is a dynamic memory buffer portion used to store data implicitly normally during the run time. 

栈是进程内存布局的很重要的一个片段,它是一个动态内存缓存部分,通常用于在程序运行时隐式的存储数据。


The stack segment is where local (automatic) variables are allocated.  In C program, local variables are all variables declared inside the opening left curly brace of a function body including the main() or other left curly brace that aren’t defined as static.  The data is popped up or pushed into the stack following the Last In First Out (LIFO) rule.  The stack holds local variables, temporary information/data, function parameters, return address and the like.  When a function is called, a stack frame (or a procedure activation record) is created and PUSHed onto the top of the stack. This stack frame contains information such as the address from which the function was called and where to jump back to when the function is finished (return address), parameters, local variables, and any other information needed by the invoked function. The order of the information may vary by system and compiler.  When a function returns, the stack frame is POPped from the stack.  Typically the stack grows downward, meaning that items deeper in the call chain are at numerically lower addresses and toward the heap.

 

Stack frame constructed during the function call for memory allocation implicitly.

A typical layout of a stack frame is shown below although it may be organized differently in different operating systems:
    *Function parameters.
    *Function’s return address.
    *Frame pointer.
    *Exception Handler frame.
    *Locally declared variables.
    *Buffer
    *Callee save registers

As an example in Windows/Intel, typically, when the function call takes place, data elements are stored on the stack in the following way:
1. The function parameters are pushed on the stack before the function is called.  The parameters are pushed from right to left.
2. The function return address is placed on the stack by the x86 CALL instruction, which stores the current value of the EIP register.
3. Then, the frame pointer that is the previous value of the EBP register is placed on the stack.
4. If a function includes try/catch or any other exception handling construct such as SEH (Structured Exception Handling - Microsoft implementation), the compiler will include exception handling information on the stack.
5. Next, the locally declared variables.
6. Then the buffers are allocated for temporary data storage.
7. Finally, the callee save registers such as ESI, EDI, and EBX are stored if they are used at any point during the functions execution.  For Linux/Intel, this step comes after step no. 4.

 

There are two CPU registers that are important for the functioning of the stack which hold information that is necessary when calling data residing in the memory. Their names are ESP and EBP in 32 bits system.
The ESP (Extended Stack Pointer) holds the top stack addressThe EBP (Extended Base Pointer) points to the bottom of the current stack frame.

ESP points to the top of the stack (lower numerical address); it is often convenient to have a stack frame pointer (FP) which holds an address that point to a fixed location within a frame.  Looking at the stack frame, local variables could be referenced by giving their offsets from ESP.  However , as data are pushed onto the stack and popped off the stack, these offsets change, so the reference of the local variables is not consistent.  Consequently, many compilers use another register, generally called Frame Pointer (FP), for referencing both local variables and parameters because their distances from FP do not change with PUSHes and POPs.  On Intel CPUs, EBP (Extended Base Pointer) is used for this purpose.
Because the way stack grows, actual parameters have positive offsets and local variables have negative offsets from FP as shown below.  Let examine the following simple C program.

[c-sharp]  view plain copy
  1. #include <stdio.h>  
  2. int MyFunc(int parameter1, char parameter2)  
  3. {  
  4.     int local1 = 9;  
  5.     char local2 = 'Z';  
  6.     return 0;  
  7. }  
  8. int main(int argc, char *argv[])  
  9. {  
  10.     MyFunc(7, '8');  
  11.     return 0;  
  12. }  

And the memory layout will look something like this:

Each time a new function is called, the old value of EBP is the first to be pushed onto the stack and then the new value of ESP is moved to EBP. This new value of ESP held by EBP becomes the reference base to local variables that are needed to retrieve the stack section allocated for the new function call. As mentioned before, a stack grows downward to lower memory address. The stack pointer (ESP) points to the last address on the stack not the next free available address after the top of the stack.

每次在一个新的函数被调用时, 首先把EBP(SP)原来的值压入栈,然后新的ESP的值将会给EBP(SP). 由EBP保存的ESP的新值就变成了对本地变量的的参考基准, 这些本地变量需要在为新的函数调用而分配的栈段上被检索。 自如前面所讲的,一个栈指针朝着低内存地址向下增长。 指向栈的最后一个地址的栈指针并不是在栈顶之后的下一个可用地址。

其实就说栈指针并不一定是一个长得开始地址,只是当前的地址,他会随着出栈入栈而相应的变化,所以为了能够取到参数,需要一个固定的指针来作为基地址,这样读取参数时只要通过偏移量就可以取到了, 这个固定的指针就称为FP. 但是需要注意的是在函数被调用之前,编译器会在新的栈上放入参数等,所以只有在函数被调用时,SP的值才会给到FP, 今天总算是搞明白了。


The first thing a function must do when called is to save the previous EBP (so it can be restored by copying into the EIP at function exit later).  Then it copies ESP into EBP to create the new stack frame pointer, and advances ESP to reserve space for the local variables.  This code is called the procedure prolog .  Upon function exit, the stack must be cleaned up again, something called the procedure epilog .

 

在函数真正的被调用之前。函数必须:

1. 首先保存先前的EBP, 用于在函数退出时把EBP的值给EIP,

2. 拷贝ESP的值给EBP,目的是创建新的栈frame指针,向前增加ESP来为本地变量预留空间

这样的代码叫做程序的prolog 在函数退出时栈必须被清除干净,称为程序的epilog


Using a very simple C program skeleton, the following tries to figure out function calls and stack frames construction/destruction.

[c-sharp]  view plain copy
  1. #include <stdio.h>  
  2. int a();  
  3. int b();  
  4. int c();  
  5. int a()  
  6. {  
  7.   b();  
  8.   c();  
  9.   return 0;  
  10. }  
  11. int b()  
  12. {   
  13.    return 0;   
  14. }  
  15. int c()  
  16. {   
  17.    return 0;   
  18. }  
  19. int main()  
  20. {  
  21.    a();  
  22.    return 0;  
  23. }  

By taking the stack area only, the following is what happen when the above program is run.


By referring the previous program example and above figure, when a program begins execution in the function main(), stack frame is created, space is allocated on the stack for all variables declared within main().  Then, when main()  calls a function, a(), new stack frame is created for the variables in a() at the top of the main() stack.  Any parameters passed by main() to a() are stored on the stack.  If a() were to call any additional functions such as b() and c(), new stack frames would be allocated at the new top of the stack.  Notice that the order of the execution happened in the sequence.  When c(), b() and a() return, storage for their local variables are de-allocated, the stack frames are destroyed and the top of the stack returns to the previous condition.  The order of the execution is in the reverse.  As can be seen, the memory allocated in the stack area is used and reused during program execution.  It should be clear that memory allocated in this area will contain garbage values left over from previous usage.

 

Ref:

http://www.tenouk.com/Bufferoverflowc/Bufferoverflow1c.html

http://www.tenouk.com/Bufferoverflowc/Bufferoverflow2.html

http://www.tenouk.com/Bufferoverflowc/Bufferoverflow2a.html

jetauto@jetauto-desktop:~/jetauto_ws/src/jetauto_example/scripts/yolov5_detect$ python3 xwc.py [10/29/2024-18:41:42] [TRT] [I] [MemUsageChange] Init CUDA: CPU +224, GPU +0, now: CPU 265, GPU 3416 (MiB) [10/29/2024-18:41:42] [TRT] [I] Loaded engine size: 22 MiB [10/29/2024-18:41:47] [TRT] [I] [MemUsageChange] Init cuBLAS/cuBLASLt: CPU +158, GPU +36, now: CPU 452, GPU 3479 (MiB) [10/29/2024-18:41:53] [TRT] [I] [MemUsageChange] Init cuDNN: CPU +240, GPU -12, now: CPU 692, GPU 3467 (MiB) [10/29/2024-18:41:53] [TRT] [I] [MemUsageChange] TensorRT-managed allocation in engine deserialization: CPU +0, GPU +21, now: CPU 0, GPU 21 (MiB) [10/29/2024-18:41:53] [TRT] [I] [MemUsageChange] Init cuBLAS/cuBLASLt: CPU +0, GPU +0, now: CPU 670, GPU 3445 (MiB) [10/29/2024-18:41:53] [TRT] [I] [MemUsageChange] Init cuDNN: CPU +0, GPU +0, now: CPU 670, GPU 3445 (MiB) [10/29/2024-18:41:53] [TRT] [I] [MemUsageChange] TensorRT-managed allocation in IExecutionContext creation: CPU +0, GPU +34, now: CPU 0, GPU 55 (MiB) bingding: data (3, 480, 640) bingding: prob (38001, 1, 1) (python3:31020): GStreamer-CRITICAL **: 18:41:55.346: Trying to dispose element pipeline0, but it is in READY instead of the NULL state. You need to explicitly set elements to the NULL state before dropping the final reference, to allow them to clean up. This problem may also be caused by a refcounting bug in the application or some element. [ WARN:0@21.714] global /home/jetauto/opencv/modules/videoio/src/cap_gstreamer.cpp (1356) open OpenCV | GStreamer warning: unable to start pipeline (python3:31020): GStreamer-CRITICAL **: 18:41:55.347: Trying to dispose element videoconvert0, but it is in PAUSED instead of the NULL state. You need to explicitly set elements to the NULL state before dropping the final reference, to allow them to clean up. This problem may also be caused by a refcounting bug in the application or some element. [ WARN:0@21.714] global /home/jetauto/opencv/modules/videoio/src/cap_gstreamer.cpp (862) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created (python3:31020): GStreamer-CRITICAL **: 18:41:55.347: Trying to dispose element appsink0, but it is in READY instead of the NULL state. You need to explicitly set elements to the NULL state before dropping the final reference, to allow them to clean up. This problem may also be caused by a refcounting bug in the application or some element. (python3:31020): GStreamer-CRITICAL **: 18:41:55.350: gst_element_post_message: assertion 'GST_IS_ELEMENT (element)' failed ^CTraceback (most recent call last): File "xwc.py", line 431, in <module> boxes, scores, classid = yolov5_wrapper.infer(frame) File "xwc.py", line 153, in infer input_image, image_raw, origin_h, origin_w = self.preprocess_image(raw_image_generator) File "xwc.py", line 264, in preprocess_image image = np.transpose(image, [2, 0, 1]) File "<__array_function__ internals>", line 6, in transpose File "/usr/local/lib/python3.6/dist-packages/numpy/core/fromnumeric.py", line 653, in transpose return _wrapfunc(a, 'transpose', axes) KeyboardInterrupt ------------------------------------------------------------------- PyCUDA ERROR: The context stack was not empty upon module cleanup. ------------------------------------------------------------------- A context was still active when the context stack was being cleaned up. At this point in our execution, CUDA may already have been deinitialized, so there is no way we can finish cleanly. The program will be aborted now. Use Context.pop() to avoid this problem. 解释
04-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值