C语言函数栈帧是如何组织的?

2026-05-05 20:352阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1855个文字,预计阅读时间需要8分钟。

C语言函数栈帧是如何组织的?

在C语言中,非静态局部变量如何在栈上分配?函数是如何传递参数的?如何调用和返回值?

(1)在C语言中,非静态局部变量在函数被调用时,会在栈上为其分配空间。这些变量的生命周期与函数的调用周期相关,函数退出时,这些变量所占用的栈空间会被释放。

(2)函数通过参数传递数据。在C语言中,参数传递主要有以下几种方式:

- 值传递:将实参的值复制一份传递给形参,形参的值改变不会影响实参。- 地址传递(指针传递):将实参的地址传递给形参,形参通过该地址访问实参的数据,形参的改变会影响实参。

(3)调用函数时,可以传递参数,并在函数内部执行相应的操作。函数执行完成后,可以通过返回值将结果传递给调用者。

C语言函数栈帧是如何组织的?

以下是一个简单的示例代码:

c#include

int sum(int a, int b) { return a + b;}

int main() { int result=sum(1, 2); printf(The sum is: %d\n, result); return 0;}

编译并运行上述代码,会生成一个32位的可执行文件`sum01`。可以通过静态分析工具对`sum01`进行静态分析,了解其静态结构和行为。

非静态局部变量如何在栈上分配?c语言中的函数是如何传参数?如何调用?如何返回的?

(1)、sum01.c生成32位汇编程序,进行静态分析;

(2)、将sum01.c编译连接成32位的可执行文件sum01.exe,然后拖入OD软件,在main函数入口出设置断点,进行单步跟踪,动态分析。

参数传递:通过堆栈传递(c语言传参是从右向左传);

函数调用:call 函数名;

函数返回:ret指令(返回值一般在EAX寄存器中);

c语言中,函数名本质就是一个地址(该函数的第一条指令在内存中存储的偏移地址)。

函数名对应的地址:printf("add of sum is :%p,add of main :%p\n",sum,main);

c语言中每个函数调用:

(1)、传参数:从右到左,存放到堆栈栈顶;

(2)、发出call指令:call 被调用函数名;(将call指令的下一条指令的地址推入堆栈栈顶,然后将被调用的函数的第一条指令的地址自动赋值给EIP寄存器-------段内调用;

如果是段间调用,则会自动将call指令的下一条指令的地址(段地址CS:偏移地址EIP),推入堆栈栈顶,然后将被调用的函数的第一条指令的地址(段地址CS:偏移地址EIP)自动赋值给相应的CS和EIP寄存器)
以下面代码为例子:

int sum(int x, int y) { int z; z = x + y; return z; } int main(void) { int a = 10; int b = 20; printf("sum=%d\n", sum(a, b)); return 0; } /*汇编代码*/ _sum: push ebp mov ebp, esp sub esp, 4 mov eax, DWORD PTR [ebp+12] add eax, DWORD PTR [ebp+8] mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-4] leave ret .def ___main; .scl 2; .type 32; .endef LC0: .ascii "sum=%d\12\0" .globl _main .def _main; .scl 2; .type 32; .endef _main: push ebp mov ebp, esp sub esp, 24 and esp, -16 mov eax, 0 mov DWORD PTR [ebp-12], eax mov eax, DWORD PTR [ebp-12] call __alloca call ___main mov DWORD PTR [ebp-4], 10 mov DWORD PTR [ebp-8], 20 mov eax, DWORD PTR [ebp-8] mov DWORD PTR [esp+4], eax mov eax, DWORD PTR [ebp-4] mov DWORD PTR [esp], eax call _sum mov DWORD PTR [esp+4], eax mov DWORD PTR [esp], OFFSET FLAT:LC0 call _printf mov eax, 0 leave ret .def _printf; .scl 2; .type 32; .endef 被调用函数内部:

(1)建立自己的栈帧底部:

push ebp ;保存上一个栈帧的基地址(栈底地址) mov ebp,esp ;让ebp寄存器执行当前的栈帧的栈底

(2)为函数内部定义的非静态局部变量分配存储空间:

and esp ,-16 ;将栈顶指针esp进行对齐,保证能被16整除 sub esp ,32 ;为函数内部的非静态局部变量分配存储空间,分配的空间一般多余局部变量所需的,防止溢出。

(3)函数内部要调用别的函数:

A. 传参数(从右向左传,存放到堆栈栈顶) B. 发出call指令

(4)如何访问自己的局部变量呢?

mov DWORD PTR [esp+28], 10 (esp+28---->某个局部变量) mov DWORD PTR [esp+24], 20 (esp+24---->某个局部变量) 或者通过ebp来访问传递给自己的参数:ebp-4--->第一个参数,ebp-8--->第二个参数

一个函数内部完整的栈帧结构:(ebp指向当前函数栈帧的底部,而esp指向当前函数栈帧的顶部)

调用当前函数的那个函数的栈底指针ebp(也是当前函数的栈底)。 <-----ebp(栈底) 函数内部的局部变量。 当前函数调用别的函数传的参数。 当前函数调用别的函数的返回地址。 <----esp(栈顶)

(5)函数结束,返回返回值(一般通过eax寄存器返回),进行平衡堆栈操作:抛弃当前的栈帧,恢复ebp和esp原来的值。

leave (leave指令的功能:mov esp, ebp以及pop ebp,本质是抛弃当前函数栈帧,恢复上一个函数的栈帧) ret (当当前栈顶的返回地址弹出送到eip中,如果是段间返回,则弹出栈顶到cs和eip中)


堆栈代码分析:

EBP=0022FFB0 ESP=0022FF84 push ebp EBP=0022FFB0 ESP=0022FF80 --->(EBP)=0022FFB0 mov ebp, esp EBP=0022FF80 ESP=0022FF80 --->(EBP)=0022FFB0 sub esp, 18 EBP=0022FF80 ESP=0022FF68 and esp, fffffff0 (-16) //地址对齐操作,让32位地址的最右边4位为0,也就是该地址能被16整除。 EBP=0022FF80 ESP=0022FF68 & fffffff0 = 0022ff60 mov eax, 0 MOV DWORD PTR SS:[EBP-C],EAX ss:0022FF74--(00000000) MOV DWORD PTR SS:[EBP-4],0A EBP-4-->a MOV DWORD PTR SS:[EBP-8],14 EBP-8-->b MOV EAX,DWORD PTR SS:[EBP-8] MOV DWORD PTR SS:[ESP+4],EAX b--->栈顶ESP+4 MOV EAX,DWORD PTR SS:[EBP-4] a--->栈顶ESP MOV DWORD PTR SS:[ESP],EAX ESP = 0022ff60 CALL _sum ESP=0022FF5C 0022FF5C--->004012EA 函数调用的返回地址(call指令下一条指令的地址) 设置新的EIP为被调用的函数中第一条指令的地址:00401290。 00401290--->push ebp (sum函数中的第一条指令) ESP=0022FF58 执行了PUSH EBP EBP=0022FF80-->栈顶(保存main函数里的ebp,因为sum函数要建立自己的栈帧ebp) 栈帧:就是调用到某个函数时该函数使用的一段栈上的连续的存储空间,ebp指向栈帧底部,esp指向栈帧顶部。 EBP=0022FF58 ESP=0022FF58 执行了MOV EBP,ESP SUB ESP,4 实际上是为sum函数中的局部变量z分配存储空间。此时ESP=0022FF54 MOV EAX,DWORD PTR SS:[EBP+C] EBP+C--->参数y,存放的是b变量的值 ADD EAX,DWORD PTR SS:[EBP+8] EBP+8--->参数x,存放的是a变量的值 MOV DWORD PTR SS:[EBP-4],EAX 和--->z中 0022FF54(z)<--- 30(1E) MOV EAX,DWORD PTR SS:[EBP-4] 返回值(和30)----> EAX (返回值一般都通过EAX寄存器返回) LEAVE指令执行:恢复原来的栈帧(EBP、ESP):执行前EBP=0022FF58,ESP=0022FF54 执行后EBP=0022FF80,ESP=0022FF5C 此时的ESP栈顶里存放的是返回地址004012EA(main函数中call _sum的下一条指令的地址) 执行sum函数的最后一条指令:RETN 返回指令(从栈顶弹出返回地址,送入EIP)。 MOV DWORD PTR SS:[ESP+4],EAX将返回值EAX(和30)放入栈顶(传参数,准备执行printf函数) MOV DWORD PTR SS:[ESP], 004012A4(字符串常量"sum=%d\n"的偏移地址)放入栈顶 CALL _printf调用printf函数输出计算的和。 MOV EAX,0 main函数返回给操作系统的值 LEAVE RETN main函数返回 return 0;

本文共计1855个文字,预计阅读时间需要8分钟。

C语言函数栈帧是如何组织的?

在C语言中,非静态局部变量如何在栈上分配?函数是如何传递参数的?如何调用和返回值?

(1)在C语言中,非静态局部变量在函数被调用时,会在栈上为其分配空间。这些变量的生命周期与函数的调用周期相关,函数退出时,这些变量所占用的栈空间会被释放。

(2)函数通过参数传递数据。在C语言中,参数传递主要有以下几种方式:

- 值传递:将实参的值复制一份传递给形参,形参的值改变不会影响实参。- 地址传递(指针传递):将实参的地址传递给形参,形参通过该地址访问实参的数据,形参的改变会影响实参。

(3)调用函数时,可以传递参数,并在函数内部执行相应的操作。函数执行完成后,可以通过返回值将结果传递给调用者。

C语言函数栈帧是如何组织的?

以下是一个简单的示例代码:

c#include

int sum(int a, int b) { return a + b;}

int main() { int result=sum(1, 2); printf(The sum is: %d\n, result); return 0;}

编译并运行上述代码,会生成一个32位的可执行文件`sum01`。可以通过静态分析工具对`sum01`进行静态分析,了解其静态结构和行为。

非静态局部变量如何在栈上分配?c语言中的函数是如何传参数?如何调用?如何返回的?

(1)、sum01.c生成32位汇编程序,进行静态分析;

(2)、将sum01.c编译连接成32位的可执行文件sum01.exe,然后拖入OD软件,在main函数入口出设置断点,进行单步跟踪,动态分析。

参数传递:通过堆栈传递(c语言传参是从右向左传);

函数调用:call 函数名;

函数返回:ret指令(返回值一般在EAX寄存器中);

c语言中,函数名本质就是一个地址(该函数的第一条指令在内存中存储的偏移地址)。

函数名对应的地址:printf("add of sum is :%p,add of main :%p\n",sum,main);

c语言中每个函数调用:

(1)、传参数:从右到左,存放到堆栈栈顶;

(2)、发出call指令:call 被调用函数名;(将call指令的下一条指令的地址推入堆栈栈顶,然后将被调用的函数的第一条指令的地址自动赋值给EIP寄存器-------段内调用;

如果是段间调用,则会自动将call指令的下一条指令的地址(段地址CS:偏移地址EIP),推入堆栈栈顶,然后将被调用的函数的第一条指令的地址(段地址CS:偏移地址EIP)自动赋值给相应的CS和EIP寄存器)
以下面代码为例子:

int sum(int x, int y) { int z; z = x + y; return z; } int main(void) { int a = 10; int b = 20; printf("sum=%d\n", sum(a, b)); return 0; } /*汇编代码*/ _sum: push ebp mov ebp, esp sub esp, 4 mov eax, DWORD PTR [ebp+12] add eax, DWORD PTR [ebp+8] mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-4] leave ret .def ___main; .scl 2; .type 32; .endef LC0: .ascii "sum=%d\12\0" .globl _main .def _main; .scl 2; .type 32; .endef _main: push ebp mov ebp, esp sub esp, 24 and esp, -16 mov eax, 0 mov DWORD PTR [ebp-12], eax mov eax, DWORD PTR [ebp-12] call __alloca call ___main mov DWORD PTR [ebp-4], 10 mov DWORD PTR [ebp-8], 20 mov eax, DWORD PTR [ebp-8] mov DWORD PTR [esp+4], eax mov eax, DWORD PTR [ebp-4] mov DWORD PTR [esp], eax call _sum mov DWORD PTR [esp+4], eax mov DWORD PTR [esp], OFFSET FLAT:LC0 call _printf mov eax, 0 leave ret .def _printf; .scl 2; .type 32; .endef 被调用函数内部:

(1)建立自己的栈帧底部:

push ebp ;保存上一个栈帧的基地址(栈底地址) mov ebp,esp ;让ebp寄存器执行当前的栈帧的栈底

(2)为函数内部定义的非静态局部变量分配存储空间:

and esp ,-16 ;将栈顶指针esp进行对齐,保证能被16整除 sub esp ,32 ;为函数内部的非静态局部变量分配存储空间,分配的空间一般多余局部变量所需的,防止溢出。

(3)函数内部要调用别的函数:

A. 传参数(从右向左传,存放到堆栈栈顶) B. 发出call指令

(4)如何访问自己的局部变量呢?

mov DWORD PTR [esp+28], 10 (esp+28---->某个局部变量) mov DWORD PTR [esp+24], 20 (esp+24---->某个局部变量) 或者通过ebp来访问传递给自己的参数:ebp-4--->第一个参数,ebp-8--->第二个参数

一个函数内部完整的栈帧结构:(ebp指向当前函数栈帧的底部,而esp指向当前函数栈帧的顶部)

调用当前函数的那个函数的栈底指针ebp(也是当前函数的栈底)。 <-----ebp(栈底) 函数内部的局部变量。 当前函数调用别的函数传的参数。 当前函数调用别的函数的返回地址。 <----esp(栈顶)

(5)函数结束,返回返回值(一般通过eax寄存器返回),进行平衡堆栈操作:抛弃当前的栈帧,恢复ebp和esp原来的值。

leave (leave指令的功能:mov esp, ebp以及pop ebp,本质是抛弃当前函数栈帧,恢复上一个函数的栈帧) ret (当当前栈顶的返回地址弹出送到eip中,如果是段间返回,则弹出栈顶到cs和eip中)


堆栈代码分析:

EBP=0022FFB0 ESP=0022FF84 push ebp EBP=0022FFB0 ESP=0022FF80 --->(EBP)=0022FFB0 mov ebp, esp EBP=0022FF80 ESP=0022FF80 --->(EBP)=0022FFB0 sub esp, 18 EBP=0022FF80 ESP=0022FF68 and esp, fffffff0 (-16) //地址对齐操作,让32位地址的最右边4位为0,也就是该地址能被16整除。 EBP=0022FF80 ESP=0022FF68 & fffffff0 = 0022ff60 mov eax, 0 MOV DWORD PTR SS:[EBP-C],EAX ss:0022FF74--(00000000) MOV DWORD PTR SS:[EBP-4],0A EBP-4-->a MOV DWORD PTR SS:[EBP-8],14 EBP-8-->b MOV EAX,DWORD PTR SS:[EBP-8] MOV DWORD PTR SS:[ESP+4],EAX b--->栈顶ESP+4 MOV EAX,DWORD PTR SS:[EBP-4] a--->栈顶ESP MOV DWORD PTR SS:[ESP],EAX ESP = 0022ff60 CALL _sum ESP=0022FF5C 0022FF5C--->004012EA 函数调用的返回地址(call指令下一条指令的地址) 设置新的EIP为被调用的函数中第一条指令的地址:00401290。 00401290--->push ebp (sum函数中的第一条指令) ESP=0022FF58 执行了PUSH EBP EBP=0022FF80-->栈顶(保存main函数里的ebp,因为sum函数要建立自己的栈帧ebp) 栈帧:就是调用到某个函数时该函数使用的一段栈上的连续的存储空间,ebp指向栈帧底部,esp指向栈帧顶部。 EBP=0022FF58 ESP=0022FF58 执行了MOV EBP,ESP SUB ESP,4 实际上是为sum函数中的局部变量z分配存储空间。此时ESP=0022FF54 MOV EAX,DWORD PTR SS:[EBP+C] EBP+C--->参数y,存放的是b变量的值 ADD EAX,DWORD PTR SS:[EBP+8] EBP+8--->参数x,存放的是a变量的值 MOV DWORD PTR SS:[EBP-4],EAX 和--->z中 0022FF54(z)<--- 30(1E) MOV EAX,DWORD PTR SS:[EBP-4] 返回值(和30)----> EAX (返回值一般都通过EAX寄存器返回) LEAVE指令执行:恢复原来的栈帧(EBP、ESP):执行前EBP=0022FF58,ESP=0022FF54 执行后EBP=0022FF80,ESP=0022FF5C 此时的ESP栈顶里存放的是返回地址004012EA(main函数中call _sum的下一条指令的地址) 执行sum函数的最后一条指令:RETN 返回指令(从栈顶弹出返回地址,送入EIP)。 MOV DWORD PTR SS:[ESP+4],EAX将返回值EAX(和30)放入栈顶(传参数,准备执行printf函数) MOV DWORD PTR SS:[ESP], 004012A4(字符串常量"sum=%d\n"的偏移地址)放入栈顶 CALL _printf调用printf函数输出计算的和。 MOV EAX,0 main函数返回给操作系统的值 LEAVE RETN main函数返回 return 0;