要理解指令和汇编部分,必须先掌握C语言内存分配和X86寄存器与栈帧知识
C语言内存分配
参考链接: C语言的内存分配
- stack-栈: 由编译器自动分配释放
- heap-堆: 一般由程序员分配释放,若程序员不释放 - 程序结束时可能由OS回收
- 全局区(静态区):
全局变量
和静态变量
的存储是放在一块的,初始化的全局变量和静态变量在一块区域``,``未初始化的全局变量和未初始化的静态变量在相邻的另一块区``域
- 程序结束释放。 - 另外还有一个专门放常量的地方 - 程序结束释放
X86寄存器和栈帧
Stack Frame
参考链接: X86-64寄存器和栈帧
| | C语言属于面向过程语言,他最大特点就是把一个程序分解成若干过程(函数),比如:入口函数是main,然后调用各个子函数。在对应机器语言中,GCC把过程转化成栈帧(frame),简单的说,每个栈帧对应一个过程
左图中:
- 自下到上地址变大
- 栈帧的生长方向自上到下
X86典型栈帧结构中
- %ebp指向栈帧开始,%esp指向栈顶**
esp是堆栈指针,无法暂借使用,所以一般使用ebp来存取堆栈 |
| :—-: | —- |
寄存器
| |
- x86-32CPU包含8个存储32bits的通用寄存器,从%eax到%esp(见左图👈,由于发展原因其命名历经变化),主要功能为管理栈、传递参数、保存返回值、存储**局部和临时**数据
- 当16bits使用时从%ax到%sp,高8位以H标识%ah,%bh…低8位以L标识%al,%bl…
- x86-64(x64)的CPU包含16个存储64bits的通用寄存器,都以%r开头(见CSAPP中文第三版3.4)
|
| :—-: | —- |
指令
参考链接: 16位汇编程序设计
指令格式
- 操作符:表示CPU支持的操作
- 操作数/操作数地址:访问操作数的模式
指令循环
每一条指令的执行都经过“取址-译码-执行”的指令循环
示例代码分析
#include <stdio.h>
int x;
void main()
{
int y;
int z = 0;
unsigned char* cptr;
x = 5;
}
VS下以x86-32调试(x64下采用%r开头的寄存器)反汇编结果与逐行分析
参考链接: 函数参数压栈,栈帧ebp,esp怎样移动的?
--- D:\ProjectSet\CProject\slp\slp.cpp -----------------------------------------
#include <stdio.h>
int x;
void main()
{
55 push ebp //保存当前栈帧
8B EC mov ebp,esp //改变栈帧,以后访问参数通过ebp([ebp±k])
,访问局部变量通过esp
81 EC E4 00 00 00 sub esp,0E4h //跳过0E4大小地址块用于当前栈帧保存局部变量
56 push esi
57 push edi //将ebx,esi,edi压入栈
8D BD 1C FF FF FF lea edi,[ebp+FFFFFF1Ch]
//加载有效地址,把[ebp+FFFFFF1Ch]
读取到edi作为参数1,根据补码运算即为[ebp-0E4]
即当前帧局部变量保存区域基址
B9 39 00 00 00 mov ecx,39h //令ecx等于0x39,作为下面rep指令重复次数
B8 CC CC CC CC mov eax,0CCCCCCCCh //令eax等于CCCCCCCC
F3 AB rep stos dword ptr es:[edi]
//es为辅助段寄存器,es:[edi]即辅助段基址+偏移
//循环使用eax中的值CCCCCCCC初始化es:[edi]指
向空间(该串表示未初始化),步长为一个双字指针大
小8字节(2*32b),恰好为CCCCCCCC长度,循环0x39
(57)次----总结:循环使用cc初始化辅助段
B9 03 C0 7A 00 mov ecx,7AC003h //将7AC003存入ecx
E8 E0 FA FF FF call 007A1208 //调用地址007A1208处子程序,这里为debugger
相关的程序,无需深究
int y;
int z = 0;
C7 45 EC 00 00 00 00 mov dword ptr [ebp-14h],0 //将0放入到双字指针ptr指向的位置,
即地址ebp-14的内存空间
unsigned char* cptr;
x = 5;
C7 05 38 A1 7A 00 05 00 00 00 mov dword ptr ds:[007AA138h],5
//x是全局变量,位于整个程序数据端,非栈内数据
区域,寄存器ds存放数据段基址,偏移量为007AA138,
把imm 5放入007AA138指向的内存空间
}
- ⭐movdword ptr ds:[007AA138h],5直接对应C7 0538 A1 7A 0005 00 00 00
- ⭐寄存器再认识:通过上述分析,可以看出来寄存器本身可存放一个32/64bits数,即一个地址,所以寄存器就是指针
- ⭐内存分配再认识:分析中可以看出
int z = 0对应movdword ptr [ebp-14h],0局部变量通过ebp±k访问,调试验证
x = 5对应movdword ptr ds:[007AA138h],5全局变量通过程序数据段基址+偏移访问
mov和lea类似,但[]之于二者作用恰好相反: 参考链接: 汇编中mov,lea指令的区别 | 对于变量num=2 | 对于寄存器ax,bx | | :—-: | :—-: | |
- mov ax,num表示把2放入ax
- mov ax,[num]表示把&num放入ax
- lea ax,num表示把%num放入ax
- lea ax,[num]表示把2放入ax
|
- mov bx,[ax]表示取地址:把as指向数据放入bx
- mov bx,ax表示取ax自身值放入bx
- lea bx,[ax]表示取ax自身值放入bx
- lea bx,ax表示取地址:把as指向数据放入bx
|rep stos: 参考链接 rep stos 指令(Intel汇编)
operand寻址模式
(Operand-addressing Mode)操作数寻址模式决定了指令循环中会被取到哪个数据项
| 寻址模式 | | 指令 | 地址生成 | 说明 | | :—-: | :—-: | :—-: | :—-: | —- | | 寄存器 | | movAXBX | AX存储BX的值 | 操作数都在通用reg | | 立即 | | movAX0464H | BX存储0464H | 直接给出操作数 | | 直接 | | movAX[0464H] | AX存储地址位0464H的空间中数据 | 给出操作数地址 | |
存
储
器 | 间接 | movAX[BX] | DS10H+BX作为地址,将对应空间数据存入AX | 给出一个操作数地址所在reg |
| | 基址
+
变址 | movAX[BX+SI] | DS10H+BS+SI作为地址,将对应空间数据存入AX | 给出操作数地址所在reg和存放变址的reg |
| | 寄存器相对 | movAX[BX+4H] | DS*10H+BX+4H作为地址,取其中数据放入AX | 给出操作数地址所在reg和一个偏移量 |
选择题知识点
- program counter程序计数器:存储下条指令地址
instruction register指令寄存器:存储下条指令
- 为CPU设计少量fast memory(cache)的目的①使得指令更短②使得指令更快,与compiler无关
- debugger停止的原因:不确定,可能由于断点,可能由于异常
- 指令集:sparc:RISC,x86: CISC
- 如何再断点调试时查看变量值①鼠标指针放在变量上②添加监视,不能使用printf
- 当程序执行完一个非分支\跳转语句后,PC指向下条指令
- CPU 寄存器:是1word大小的CPU memory,可显式地加载/卸载编译器生成的指令
- 当一组连续的内存位置包含整数0xC605CD623A8365000000时,该区域可能有
①整数0xC605CD623A8365000000②字符串③一条指令
- 分支语句:把PC设为两个可能值中一个
跳转语句:无条件地把PC设为其操作数
- 机器码并没有保存源码所有信息
- 经过compiler对优化后的机器码①更短,运行更快②更难以调试,但不能说更clearer
- 汇编程序不可能同时使用AX和AL:如图,AL会影响AX