如分析有误,请在评论区中,指出 谢谢合作
主要是分析C的函数调用在汇编中的执行流程
本章主要是说一下函数调用时堆栈的变化,重点理解部分
C代码
#include"stdafx.h"
int plus(int x,int y){
return x+y;
}
int main()
{
plus(0,1);
return 0;
}
汇编代码 VC6.0++ 反编译所得 (如分析有误,请在评论区提出,谢谢)
//函数部分
0040100A jmp plus (00401010) //跳转到 00401010内存位置
0040100F int 3 //无视
00401010 push ebp //ebp压栈
00401011 mov ebp,esp // esp的值 赋给 ebp
00401013 sub esp,40h //esp = esp-40H //esp寄存器是栈首位置,相当于提升堆栈,开辟
00401016 push ebx //压栈
00401017 push esi //压栈
00401018 push edi //压栈
00401019 lea edi,[ebp-40h] //将ebp的内存地址 - 40H 后所得的内存地址 赋值给 edi寄存器
0040101C mov ecx,10h 将 10H (16) 赋给 ecx寄存器
00401021 mov eax,0CCCCCCCCh 将 ,0CCCCCCCCh 赋值给eax
00401026 rep stos dword ptr [edi] //循环 ecx的次数
00401028 mov eax,dword ptr [ebp+8] //将ebp+8的内存地址的值 赋给 eax
0040102B add eax,dword ptr [ebp+0Ch] // eax = eax + (ebp+0Ch)的值
0040102E pop edi //弹出栈
0040102F pop esi //弹出栈
00401030 pop ebx //弹出栈
00401031 mov esp,ebp //恢复堆栈
00401033 pop ebp //弹出栈底
00401034 ret //相当于 pop eip
//main函数部分
0040B76E push 1 //压栈 因为C函数的调用约定 '__stdcall', plus(0,1); 参数从右向左压入堆栈
0040B770 push 0 //压栈
0040B772 call @ILT+5(plus) (0040100a)
0040B777 add esp,8
一步一步来,这里的话 需要画图哈.别急
前置条件:
堆栈的特点是 前大后小(内存地址) 本章是 32位寄存器 所以一次性 4
main函数中, (栈首)esp的寄存器的值为 0019FEF4 , (栈尾)ebp的寄存器的值为 0019FF40
自绘图
汇编图
代码逻辑执行开始,主要是堆栈的变
------1
0040B76E push 1 //压栈 因为C函数的调用约定 '__stdcall', plus(0,1); 参数从右向左压入堆栈
0040B770 push 0 //压栈
0040B772 call @ILT+5(plus) (0040100a)0040B777
0040B777 add esp,8
push压栈时: 修改的 esp(栈顶的值) -4
执行前三句:结果如图
前两句应该很好理解
call指令,在指令执行的时候,会把call的内存地址 下一行的内存地址 压入堆栈中
--------2
0040100A jmp plus (00401010) //跳转到 00401010内存位置
0040100F int 3 //无视
00401010 push ebp //ebp压栈
第一行跳转,不用多说
第二行无视
第三行: push ebp//将堆栈的底,压入到内存地址
00401011 mov ebp,esp
00401013 sub esp,40h // 40h = 64(十进制)
第一句 ebp = esp,含义为 执行这个函数开辟内存
第二局 esp的内存地址 减去40H = 19FEA4 个人见解:提升堆栈 40H=64(十进制) 因为32寄存器 堆栈为4 所以就是提升 64/4= 16
00401016 push ebx //压栈
00401017 push esi //压栈
00401018 push edi //压栈
00401019 lea edi,[ebp-40h] //将ebp的内存地址 - 40H 后所得的内存地址 赋值给 edi寄存器
前三句没什么好说的
就是压栈
lea edi,[ebp-40h] //这句话就有点意思了。 ebp-40H就是刚才 esp-40H的地址一样的,将值赋值给EDI
高能从这里开始 就比较有意思了
0040101C mov ecx,10h //重复次数
00401021 mov eax,0CCCCCCCCh //eax = 0XCCCCCCCC
00401026 rep stos dword ptr [edi] //
第一句 ecx寄存器一般用来计数, 10H = 16(十进制) 跟我们刚才 ESP-40H 的内存地址也是16个
第二句 将0CCCCCCCCh 赋值给EAX寄存器
第三句 rep stos 【repeat(重复) store(保存) string】
指令解析
rep 重复前缀指令,英文缩写 repeat,每执行一次, ecx 减 1,直到 ecx 减至0,重复执行结束
stos 串存储指令,英文缩写 store string ,将 eax 中的数据传送到目的地址(目的地址默认edi寄存器),
以下两条指令相当于一条 stos 指令
mov [edi], eax
add edi, 4 ;或者 sub edi, 4
// 至于到底是加 4 还是减 4 ,是由方向标志 DF 来决定,可以由指令 cld 和 std 指令设置
cld: 从低地址往高地址传送
std: 从高地址往低地址传送
首次执行的汇编图
10H = 16(十进制) 所以rep就是执行16次,所得结果如图
汇编图:
自绘图
00401028 mov eax,dword ptr [ebp+8]
0040102B add eax,dword ptr [ebp+0Ch]
自绘图可以明显看到 ebp+8 与 ebp+0ch 也就是函数传参的两个值
也就是C代码 x+y;
0040102E pop edi //弹出栈
0040102F pop esi //弹出栈
00401030 pop ebx //弹出栈
这三句都是出栈, 注意出栈后esp的栈顶指针会+4
这三句的自绘图,这时esp的栈顶指针的内存地址为 19FEA4
这三句是本章重点
00401031 mov esp,ebp
00401033 pop ebp
00401034 ret
自绘图,这三句执行前的堆栈图
mov esp,ebp
当执行 的时候,堆栈的 栈顶与栈尾在同一个位置
00401033 pop ebp
因为执行pop 所以 esp的栈顶指针(0019FEE4)+4 = (0019FEE8)
因为 pop弹出的是堆栈 所以 ebp的值会回到 上一次的位置 也就是 0019FF40(有疑问请看第一张自绘图,这里比较难理解,多看几遍,建议手画一画图)
ret //pop eip
ret指令用栈中的数据,修改偏移地址,从而实现近转移 就相当于 pop eip
所以ESP栈顶指针(0019FEE8) +4 = (0019FEEC),这时的堆栈图
0040B777 add esp,8
//esp (0019FEEC)+8 = 0019 FEF4
这句就是函数的堆栈平衡,也就是操作数据时,开辟堆栈内存.用完了以后又恢复到 调用函数之前的位置:
最后的自绘图
总结:
1.调用函数的时候,堆栈会为函数执行开辟内存,
00401011 mov ebp,esp
00401013 sub esp,40h
2.调用函数时 用的是Call指令,并且会开辟空间,把函数的参数压栈,并且把后一个eip地址也压栈
特点:
1.会先将ebp栈底指针压栈,还有一些寄存器 首要特点push ebp ,mov ebp,esp
2.Call函数后面的那一句 一般是堆栈平衡的代码
//mai部分
0040B76E push 1
0040B770 push 0
0040B772 call @ILT+5(plus) (0040100a)
//函数部分
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,40h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-40h]
整理不易,花了好几个小时,有任何问题请在评论区指出,喜欢的话,点个关注或者喜欢哈