当前位置 : 主页 > 编程语言 > 其它开发 >

c语言函数的栈帧

来源:互联网 收集:自由互联 发布时间:2022-06-03
非静态局部变量如何在栈上分配?c语言中的函数是如何传参数?如何调用?如何返回的? (1)、sum01.c生成32位汇编程序,进行静态分析; (2)、将sum01.c编译连接成32位的可执行文件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;
网友评论