C 语言,函数call,ret指令,栈变化等

  • 源代码
int fun(int a, int b)
{
    a += 1;
    b *= 2;
    int c = a + b;
    return c;
}

int main()
{   
    int a = 1;
    int b = 2;
    int c = fun(a, b);
    printf("%d\n", c);
    return 0;
}
  • 汇编
int main()
{   
00938270  push        ebp  
00938271  mov         ebp,esp  
00938273  sub         esp,0E4h  
00938279  push        ebx  
0093827A  push        esi  
0093827B  push        edi  
0093827C  lea         edi,[ebp-0E4h]  
00938282  mov         ecx,39h  
00938287  mov         eax,0CCCCCCCCh  
0093828C  rep stos    dword ptr es:[edi]  
    int a = 1;
0093828E  mov         dword ptr [a],1  
    int b = 2;
00938295  mov         dword ptr [b],2  
    int c = fun(a, b);
0093829C  mov         eax,dword ptr [b]  
0093829F  push        eax  
009382A0  mov         ecx,dword ptr [a]  
009382A3  push        ecx  
009382A4  call        fun (93710Dh)  
009382A9  add         esp,8  
009382AC  mov         dword ptr [c],eax  
    printf("%d\n", c);
009382AF  mov         eax,dword ptr [c]  
009382B2  push        eax  
009382B3  push        offset string "%d" (983EE8h)  
009382B8  call        @ILT+3880(_printf) (936F2Dh)  
009382BD  add         esp,8  
    return 0;
009382C0  xor         eax,eax  
}
int fun(int a, int b)
{
00938210  push        ebp  
00938211  mov         ebp,esp  
00938213  sub         esp,0CCh  
00938219  push        ebx  
0093821A  push        esi  
0093821B  push        edi  
0093821C  lea         edi,[ebp-0CCh]  
00938222  mov         ecx,33h  
00938227  mov         eax,0CCCCCCCCh  
0093822C  rep stos    dword ptr es:[edi]  
    a += 1;
0093822E  mov         eax,dword ptr [a]  
00938231  add         eax,1  
00938234  mov         dword ptr [a],eax  
    b *= 2;
00938237  mov         eax,dword ptr [b]  
0093823A  shl         eax,1  
0093823C  mov         dword ptr [b],eax  
    int c = a + b;
0093823F  mov         eax,dword ptr [a]  
00938242  add         eax,dword ptr [b]  
00938245  mov         dword ptr [c],eax  
    return c;
00938248  mov         eax,dword ptr [c]  
}
0093824B  pop         edi  
0093824C  pop         esi  
0093824D  pop         ebx  
0093824E  mov         esp,ebp  
00938250  pop         ebp  
00938251  ret  

内存栈基础
这里写图片描述

函数调用一定是 call指令

009382A4  call        fun (93710Dh)  

注意到call指令之前的几个指令

0093829C  mov         eax,dword ptr [b]  
0093829F  push        eax  
009382A0  mov         ecx,dword ptr [a]  
009382A3  push        ecx 

这里是参数入栈,而且是从右到左的顺序

CALL指令(“调用”指令)的功能,就是以下两点:
1. 将下一条指令的所在地址(即当时程序计数器PC的内容)入栈,
2. 并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序)。

这里写图片描述

call之后,vs2010下按F11,debug进入fun函数里面

push ebp

保存上一个函数栈的ebp, 也就是保存main函数栈,可以发现main函数开头也是这一句。(所以,每个函数都有自己的函数栈,但都是在整个内存栈中)

mov         ebp,esp  
sub         esp,0CCh 

上一句push指令后,esp是自动往下移动了的,这一个是把当前的esp赋值给ebp(所以,main函数的ebp,保存了的,这里ebp变化了,也就形成了fun函数自己的函数栈了)

sub应当是给当前函数分配栈大小的初始空间

接下来栈空间如下(灰色部分为fun函数的函数栈)

这里写图片描述


    return c;
011C8248  mov         eax,dword ptr [c]  
}
011C824B  pop         edi  
011C824C  pop         esi  
011C824D  pop         ebx  
011C824E  mov         esp,ebp  
011C8250  pop         ebp  
011C8251  ret 

return语句,可以看到最后的返回结果存到了eax中

fun函数最后的三条指令

011C824E  mov         esp,ebp  
011C8250  pop         ebp  
011C8251  ret 

mov esp, ebp 就是清除fun的函数栈,现在esp指向了ebp(ebp就是原来fun函数的栈底地址)

pop ebp,就是 弹出main函数的ebp

即如下的1,2操作,接着函数栈变成了,如下图红色框里面的内容

这里写图片描述


ret指令
ret指令用栈中的数据,修改IP的内容,从而实现近转移;
CPU执行ret指令时,进行下面的两步操作:

  1. (IP) = ((ss)*16 +(sp))
  2. (sp) = (sp)+2

经过上面的两条语句后,esp指向了 上图中[call指令下一个指令的所在地址] 那里, IP刚好取得地址,然后回到原来,main函数的地方。
接着 esp 增加,也就是出栈,所以 main函数栈如下

这里写图片描述

回到main函数中

011C82A4  call        fun (11C710Dh)  
011C82A9  add         esp,8  
011C82AC  mov         dword ptr [c],eax  
    printf("%d\n", c);
011C82AF  mov         eax,dword ptr [c]  
011C82B2  push        eax  
011C82B3  push        offset string "%d" (1213EE8h)  
011C82B8  call        @ILT+3880(_printf) (11C6F2Dh)  
011C82BD  add         esp,8 
call指令之后是 add esp 8

因为有两个参数,所以 add esp 8 ,弹出两个int

011C82AC  mov         dword ptr [c],eax  

即把fun函数返回的结果 存储到变量c中, 函数调用完后,一切恢复正常(fun函数栈,早就被清除掉了)

这里写图片描述


在虚拟机中,可以使用汇编语言来编写指令。下面是call指令、ret指令、push指令和pop指令的汇编代码示例: 1. call指令: ```assembly call function_label ``` 该指令将当前执行位置压入中,并跳转到函数的入口地址。 2. ret指令: ```assembly ret ``` 该指令将从中取出返回地址,并跳转回调用该函数的位置。 3. push指令: ```assembly push value ``` 该指令将给定的值压入中。 4. pop指令: ```assembly pop destination ``` 该指令顶的值弹出,并存储到目标位置。 在虚拟机中,可以使用空间来存储局部变量和函数调用的相关信息。空间的分配可以通过调整指针来实现。主函数和调用函数上的分配可以按照以下步骤进行: 1. 在主函数开始时,分配主函数所需的局部变量和其他数据的空间。可以通过将指针减少相应的大小来实现。 2. 当需要调用其他函数时,将要调用的函数的返回地址(即下一条指令的地址)压入中,然后跳转到目标函数的入口。 3. 在目标函数中,分配该函数所需的局部变量和其他数据的空间。 4. 当目标函数执行完毕时,使用ret指令返回到调用函数。ret指令将从中取出返回地址,并跳转回调用函数的位置。 5. 在调用函数中,可以通过push和pop指令来保存和恢复寄存器的值。例如,可以使用push指令将寄存器的值保存到中,然后在需要时使用pop指令中恢复寄存器的值。 以下是一个简单的C语言代码示例: ```c void called_function() { int a = 10; int b = 20; int result = a + b; } void main() { int x = 5; int y = 7; called_function(); int z = x + y; } ``` 对应的汇编代码示例: ```assembly section .text global _start _start: ; main函数开始 push ebp ; 保存旧的帧指针 mov ebp, esp ; 设置新的帧指针 sub esp, 8 ; 分配局部变量x和y的空间 mov dword [ebp-8], 5 ; x = 5 mov dword [ebp-4], 7 ; y = 7 call called_function ; 调用called_function函数 mov eax, dword [ebp-8] ; eax = x add eax, dword [ebp-4] ; eax += y mov dword [ebp-12], eax ; z = eax mov esp, ebp ; 恢复指针 pop ebp ; 恢复旧的帧指针 mov eax, 1 ; exit系统调用号 xor ebx, ebx ; 返回码为0 int 0x80 ; 调用内核 ret called_function: push ebp ; 保存旧的帧指针 mov ebp, esp ; 设置新的帧指针 sub esp, 8 ; 分配局部变量a和b的空间 mov dword [ebp-8], 10 ; a = 10 mov dword [ebp-4], 20 ; b = 20 mov eax, dword [ebp-8] ; eax = a add eax, dword [ebp-4] ; eax += b mov dword [ebp-12], eax ; result = eax mov esp, ebp ; 恢复指针 pop ebp ; 恢复旧的帧指针 ret ``` 这个示例代码展示了如何使用call、ret、push和pop指令来实现函数的调用和空间的分配。注意,这只是一个简单示例,实际的汇编代码可能会更加复杂,取决于具体的虚拟机或操作系统。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值