非静态局部变量如何在栈上分配?c语言中的函数是如何传参数?如何调用?如何返回的? (1)、sum01.c生成32位汇编程序,进行静态分析; (2)、将sum01.c编译连接成32位的可执行文件sum01.
(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;