文章目录
一、准备知识
1.1寄存器
32位CPU所含有的寄存器有:数据寄存器
,指针寄存器
,变址寄存器
,段寄存器
,指令指针寄存器
,标志寄存器
1.1.1数据数据寄存器
寄存器 | 作用 |
---|---|
EXM: 累加寄存器 | 累加器可用于乘、除、输入/输出等操作 |
EBX: 基址寄存器 | 它可作为存储器指针来使用 |
ECX: 计数寄存器 | 在循环和字符串操作时,要用它来控制循环次数 |
EDX: 数据寄存器 | 在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址 |
1.1.指针变址寄存器
寄存器 | 解释 |
---|---|
ESP: 堆栈指针寄存器 | 其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶 |
EBP: 扩展基址指针寄存器 | 其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部 |
ECX: 源变址寄存器 | |
EDX: 目的指针寄存器 |
1.2程序内存分区
段名 | 存储内容 | 分配方式 | 生长方向 | 读写特点 | 运行态 |
---|---|---|---|---|---|
代码段 | 程序指令、字符串常量、虚函数链表 | 静态分布 | 由低到高 | 只读 | 用户态 |
数据段 | 初始化的全局变量和静态变量 | 静态分布 | 由低到高 | 可读可写 | 用户态 |
BSS段 | 未初始化的全局变量和静态变量 | 静态分布 | 由低到高 | 可读可写 | 用户态 |
堆 | 动态申请的数据 | 静态分布 | 由低到高 | 可读可写 | 用户态 |
映射段 | 动态链接库、共享文件、匿名映射对象 | 静态分布 | 由低到高 | 可读可写 | 用户态 |
栈 | 局部变量、函数参数与返回值、函数返回地址、调用者环境信息 | 静态分布 | 由高到低 | 可读可写 | 用户态 |
内核空间 | 储操作系统、驱动系统 | 静态分布 | 都有 | 不能直接访问 | 内核态 |
而函数栈帧:就是指函数被调用时,系统会在栈区为该函数开辟一块栈空间,这个栈空间就是该函数的函数栈帧。
1.3简单汇编指令
1.3.1通用数据传送指令
pop:把字弹出堆栈
push:把字压入堆栈
move:传送字或字节
1.3.2算术运算指令
sub:减法
add:加法
cmp:比较(两操作数作减法,仅修改标志位,不回送结果)
1.3.3目的地址传送指令
lea:装入有效地址
1.3.4串指令
rep:当CX/ECX<>0时重复
stos:保存串
1.3.5程序转移指令
call:过程调用
注意:为了更好的观察函数栈帧的创建与销毁,最好不用最新版本的编译器,这里用VS2013
二、函数栈帧的创建与销毁
2.1宏观把握
代码例子:
#include<stdio.h>
int Add(int x,int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int c=Add(a, b);
printf("%d", c);
return 0;
}
打开调用堆栈
F10狂按直到
转到626行
观察代码可以发现,mian
是被调用的,往上翻阅可以得知是被__tmainCRTStartup
调用
转到466行
,__tmainCRTStartup
又被mainCRTStartup
调用
所以调用关系为:
mainCRTStartup
{…__tmainCRTStartup
{ …main
… }…}
2.2细节把控
接下来让我们康康汇编语言的世界来更深层次的理解函数栈帧的创建与销毁
F10+右键转到反汇编
int main()
{
00101400 push ebp
00101401 mov ebp,esp
00101403 sub esp,0E4h //为mian()预开辟的空间
00101409 push ebx
0010140A push esi
0010140B push edi
0010140C lea edi,[ebp-0E4h]
00101412 mov ecx,39h
00101417 mov eax,0CCCCCCCCh
0010141C rep stos dword ptr es:[edi] //从edi开始向下39h空间都改为0CCCCCCCCh
int a = 10;
0010141E mov dword ptr [a],0Ah //不显示符号名为dword ptr [ebp-8],0Ah
int b = 20;
00101425 mov dword ptr [b],14h //不显示符号名为dword ptr [ebp-14h],14h
int c=Add(a, b);
0010142C mov eax,dword ptr [b] //将ebp-14h的值给eax
0010142F push eax
00101430 mov ecx,dword ptr [a]
00101433 push ecx
00101434 call _Add (01010E1h) //call指令的一下条指令的指令调用
00101439 add esp,8
0010143C mov dword ptr [c],eax //dword ptr [ebp-20h],eax
printf("%d", c);
0010143F mov esi,esp
00101441 mov eax,dword ptr [c]
00101444 push eax
00101445 push 105858h
0010144A call dword ptr ds:[109114h]
00101450 add esp,8
00101453 cmp esi,esp
00101455 call __RTC_CheckEsp (010113Bh)
return 0;
0010145A xor eax,eax
}
0010145C pop edi
0010145D pop esi
0010145E pop ebx
0010145F add esp,0E4h
00101465 cmp ebp,esp
00101467 call __RTC_CheckEsp (010113Bh)
0010146C mov esp,ebp
0010146E pop ebp
0010146F ret
2.2.1汇编代码详解
所以有时候在定义变量但没有赋值的情况下输出的随机值可能为ccccc等等
a和b的位置取决于编译器
F11继续走时候跳转到Add()
函数
#include<stdio.h>
int Add(int x,int y)
{
001013C0 push ebp
001013C1 mov ebp,esp
001013C3 sub esp,0C0h
001013C9 push ebx
001013CA push esi
001013CB push edi
001013CC lea edi,[ebp-0C0h]
001013D2 mov ecx,30h
001013D7 mov eax,0CCCCCCCCh
001013DC rep stos dword ptr es:[edi]
return x + y;
001013DE mov eax,dword ptr [x] //eax,dword ptr [ebp+8]
001013E1 add eax,dword ptr [y] //eax,dword ptr [ebp+0Ch]
}
001013E4 pop edi
001013E5 pop esi
001013E6 pop ebx
001013E7 mov esp,ebp
001013E9 pop ebp
001013EA ret
可以发现Add()
与main()
有异曲同工之妙,故不赘述直接上图
将
return
返回的值暂时存在eax
中
call
指令的作用就是让Add
函数回到main
之后的过程以此类推…
由于作者水平有限,欢迎评论区批评指正:)