3.2程序编码
unix> gcc -01 -o p p1.c p2.c
-o用于指定输出(out)文件名。
-01,-02 告诉编译器使用第一级或第二级优化
3.2.1机器级代码
机器级编程两种重要的抽象:
1.ISA(指令集体系结构):机器级程序的格式和行为,叙述成按顺序执行,一条指令结束后,下一条再开始。
2.机器级程序使用的储存器地址是虚拟地址,提供的储存器模型看上去是一个非常大的字节数组。
IA32:
1.程序计数器
2.整数寄存器文件
3.条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。
4.一组浮点寄存器存放浮点数据。
3.2.2代码示例
gcc -S -o code.s code.c产生一个汇编文件code.svim code.s
objdump -d xxx.xx使用反汇编器查看目标代码文件的内容。
机器代码和它的反汇编表示的一些特性:
1.IA32指令长度从1到15个字节不等
2.设计指令格式的方式是,从某个给定位置开始,可以将字节唯一的解码成机器指令
3.反汇编器只是基于机器代码文件中的字节序列来确定汇编代码,不需要访问程序的源代码或汇编代码
4.反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些差别
3.3数据格式
8 位:字节
16位:字
32位:双字
64位:四字
16位的机器体系结构,Intel用术语"字(word)"表示16位数据类型。
32位机器体系结构是从16位扩展过来,因此称32位数为“双字(double words)”,称64位数为“四字(quad words)”。
大部分指令都是针对字节和双字操作的。
C声明 Intel数据类型 GAS后缀 大小(字节)
char 字节(byte) b 1
short 字(word) w 2
int 双字(double words) l 4
unsigned 双字(double words) l 4
long int 双字(double words) l 4
unsigned long 双字(double words) l 4
char * 双字(double words) l 4
float 单精度(signal) s 4
double 双精度(double) l 8
long double 扩展精度 t 10/12
GAS中的每个操作指令都有一个字符后缀,用于表明操作数的大小。例如,mov有三种形式: movb(传送字节)、movw(传送字)、movl(传送双字)。其中float的后缀也是l,这不会与整数的混淆,因为浮点数使用的一组完全不同的指令和寄存器(浮点数寄存器)。
3.4访问信息
在IA32中央处理单元(CPU)中,包含了8个32位整数寄存器。在每个32位寄存器的名字前面都会有一个%e,在这里可以把e理解成extended(扩展的),因为早期的8086寄存器是16位,所以加e之后就变成32位的了。
3.4.1操作数指示符
操作数指示符包括立即数,寄存器,存储器。
类型 | 格式 | 操作数值 | 名称 |
立即数 | $Imm | Imm | 立即数寻址 |
寄存器 | Ea | R[Ea] | 寄存器寻址 |
存储器 | Imm | M[Imm] | 绝对寻址 |
存储器 | (Ea) | M[R[Ea]] | 间接寻址 |
存储器 | Imm(Eb) | M[Imm + R[Eb]] | (基址+偏移量)寻址 |
存储器 | (Eb, Ei) | M[R[Eb] + R[Ei]] | 变址寻址 |
存储器 | Imm(Eb, Ei) | M[Imm + R[Eb] + R[Ei]] | 变址寻址 |
存储器 | (, Ei, s) | M[R[Ei] * s] | 比例变址寻址 |
存储器 | Imm(, Ei, s) | M[Imm + R[Ei] * s] | 比例变址寻址 |
存储器 | (Eb, Ei, s) | M[R[Eb] + R[Ei]*s] | 比例变址寻址 |
寄存器 | Imm (Eb, Ei, s) | M[Imm + R[Eb] + R[Ei]*s] | 比例变址寻址 |
3.4.2数据传送指令
将数据从一个位置复制到另一个位置的指令是最频繁使用的指令
传送指令MOV
MOV reg/mem,imm ;立即数送寄存器或是存储器
MOV reg/mem/seg,reg ;寄存器送寄存器(包括段寄存器)或贮存
MOV reg/seg,mem ;主存送寄存器(包括段寄存器)
MOV reg/mem,seg ;段寄存器送主存或寄存器
特别说明:(1)立即数传送至通用寄存器(不包括段寄存器)或存储单元
(2)IA32的限制:两个操作数都不能指向存储器。
3.5算术和逻辑操作
3.5.1加载有效地址 3.5.2一元和二元操作 3.5.3移位操作
加载有效地址指令leal 实际上是movl指令的变形,它的指令形式是从储存器读数据到寄存器,但实际上它没用引用储存器。
一元操作
只有一个操作数,既是源又是目的,可以是一个寄存器,或者存储器位置。
二元操作
操作数既是源又是目的
·第一个操作数可以是立即数、寄存器或者存储器位置 第二个操作数可以是寄存器或者存储器位置 但是不能同时是存储器位置。
移位操作
SAL 算术左移
SHL 逻辑左移
SAR 算术右移(补符号位)
SHR 逻辑右移(补0)
3.5.5特殊的算术操作
imull
双操作数,从两个32位操作数产生一个32位的乘积。
mull
无符号数乘法
要求一个参数必须在寄存器%eax中,另一个作为指令的源操作数给出。乘积的高32位在%edx中,低32位在%eax中。
idivl
有符号除法
操作数
将DX:AX中的64位数作为被除数,操作数中为除数,结果商在AX中,余数在DX中。
divl
无符号除法
通常会事先设定寄存器%edx为0.
3.6控制
3.6.1条件码
条件语句、循环语句和分支语句,要求有条件的进行,根据数据测试的结果来决定操作执行的顺序。机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流
控制指令-条件码
CPU维护着一组单个位的条件码寄存器,他们描述了最近的算术或者逻辑操作的属性。可以检测这些寄存器来执行条件分支指令
最常用的条件码:
CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数的溢出
ZF:零标志。最近的操作得出的结果为0.
SF:符号标志。最经的操作得到的结果为负数。
OF:溢出标志。最近的操作导致一个补码溢出——正溢出或者负溢出
比较和测试指令
有两类指令只设置条件码而不改变任何其他寄存器。
CMP指令根据两个操作数之间的差来设置条件码。
TEST指令的行为同and一样。典型的用法是,testl %eax,%eax用来检查%eax是负数、零还是正数
![](https://i-blog.csdnimg.cn/blog_migrate/508db1c9f6f7a3c7ad70c9067588d323.jpeg)
3.6.2访问条件码
三种方法
1.根据条件码的某个组合,将一个字节设置为0或者1
2.可以条件跳转到程序的某个其他的部分
3.可以有条件地传送数据
3.6.3、跳转指令及其编码
jump分为直接跳转和间接跳转
直接跳转:后面跟标号作为跳转目标
间接跳转:*后面跟一个操作数指示符
![](https://i-blog.csdnimg.cn/blog_migrate/e01de3bae011b005c9c91cbdb1d54c1a.jpeg)
3.6.4翻译条件分支
将条件表达式和语句从c语言翻译成机器语言,最常用的方式就是结合有条件和无条件跳转。
无条件跳转:例如 goto。书上的例子就是把if-else语句翻译成了goto形式,然后再由这个形式翻译成汇编语言。
3.6.5循环
1.Do-while循环
源代码
do{
body-statement
}while(test-expr)
翻译成汇编的伪代码
loop:
body-statement;
t = test-expr;
if(t)
goto loop;
2.While循环
源代码
while(test-expr)
body-statement
翻译成汇编的伪代码
t = test-expr;
if(!t)
goto done;
loop:
body-statement;
t = test-expr;
if(t)
goto loop;
done:
For循环
源代码:
for(init-expr;
test-expr;
update-expr)
body-statement;
翻译成汇编的伪代码:
init-expr;
t = test-expr;
if(!t)
goto done;
loop:
body-statement;
update-expr;
t = test-expr;
if(t)
goto loop;
done:
switch语句
根据一个整数索引值进行多重分支,执行switch语句的关键步骤是通过跳转表来访问代码位置,使结构更加高效。
3.7过程
数据传递、局部变量的分配和释放通过操纵程序栈来实现。
3.7.1栈帧结构
· 为单个过程分配的栈叫做栈帧,寄存器%ebp为帧指针,而寄存器指针%esp为栈指针,程序执行时栈指针移动,大多数信息的访问都是相对于帧指针。
· 栈向低地址方向增长,而栈指针%esp指向栈顶元素。
3.7.2转移控制
call:目标是指明被调用过程起始的指令地址,效果是将返回地址入栈,并跳转到被调用过程的起始处。
ret:从栈中弹出地址,并跳转到这个位置。
函数返回值存在%eax中
3.7.3寄存器使用惯例
程序寄存器是唯一能被所有过程共享的资源,调用者保存寄存器 和 被调用者保存寄存器是分开的,对于哪一个寄存器保存函数调用过程中的返回值要有统一的约定。
3.11使用GDB调试器
命令 | 参数含义 | 说明 |
display |
| 设置自动显示列表,不带参数时显示列表中所有表达式 |
delete display dnums….. | 自动显示列表的ID号 | 删除自动显示列表 |
disable dislay dnums….. | 自动显示列表的ID号 | 禁用自动显示列表 |
enable display dnums….. | 自动显示列表的ID号 | 使用自动显示列表 |
info display |
| 显示自动显示列表中的表达式 |
show convenience |
| 显示快捷变量 |
macro expand expression | 包含宏定义的表达式 | 将表达式宏展示 |
info macro macroname | 宏定义的名字 | 查看宏定义的信息 |
generatre-core file |
| 产生转储文件 |
backtrace | n 最内层的n个函数栈帧 -n 最外层n个函数栈帧 | 栈帧回溯 |
frame | 栈帧号或者内存地址 | 选定栈帧,不带参数时显示栈帧简要信息 |
up | 栈帧号 | 选定栈帧上移 |
down | 栈帧号 | 选定栈帧下移 |
info frame |
| 显示栈帧详细信息 |
info agrs |
| 显示当前选定栈帧的函数参数 |
info locals |
| 显示当前选定栈帧中的所有局部变量 |
set variable expr | 赋值表达式 | 给变量赋值或者修改变量的值 |
| 赋值表达式 | 给变量赋值或者修改变量的值 |
jump |
| 使程序从另外的地址开始执行 |
signal sig | 信号值或者信号和名称 | 向被调试程序发送信号 |
call expr | 包含函数调用的表达式 | 调用函数 |
print expr | 包含函数调用的表达式 | 调用函数 |
list | 函数名 行号 地址 偏移等 | 列出程序源码 |
set listsize count |
| 设置 list默认显示的行数 |
disassemble |
| 反汇编被调试程序 |
set disassembly-flavor instruction-set |
| 设置反汇编格式 |
命令 | 参数含义 | 说明 |
display |
| 设置自动显示列表,不带参数时显示列表中所有表达式 |
delete display dnums….. | 自动显示列表的ID号 | 删除自动显示列表 |
disable dislay dnums….. | 自动显示列表的ID号 | 禁用自动显示列表 |
enable display dnums….. | 自动显示列表的ID号 | 使用自动显示列表 |
info display |
| 显示自动显示列表中的表达式 |
show convenience |
| 显示快捷变量 |
macro expand expression | 包含宏定义的表达式 | 将表达式宏展示 |
info macro macroname | 宏定义的名字 | 查看宏定义的信息 |
generatre-core file |
| 产生转储文件 |
backtrace | n 最内层的n个函数栈帧 -n 最外层n个函数栈帧 | 栈帧回溯 |
frame | 栈帧号或者内存地址 | 选定栈帧,不带参数时显示栈帧简要信息 |
up | 栈帧号 | 选定栈帧上移 |
down | 栈帧号 | 选定栈帧下移 |
info frame |
| 显示栈帧详细信息 |
info agrs |
| 显示当前选定栈帧的函数参数 |
info locals |
| 显示当前选定栈帧中的所有局部变量 |
set variable expr | 赋值表达式 | 给变量赋值或者修改变量的值 |
| 赋值表达式 | 给变量赋值或者修改变量的值 |
jump |
| 使程序从另外的地址开始执行 |
signal sig | 信号值或者信号和名称 | 向被调试程序发送信号 |
call expr | 包含函数调用的表达式 | 调用函数 |
print expr | 包含函数调用的表达式 | 调用函数 |
list | 函数名 行号 地址 偏移等 | 列出程序源码 |
set listsize count |
| 设置 list默认显示的行数 |
disassemble |
| 反汇编被调试程序 |
set disassembly-flavor instruction-set |
| 设置反汇编格式 |
实验楼实验五
要求
使用gcc –S –o main.s main.c -m32
命令编译成汇编代码,如下代码中的数字和函数名请自行修改以防与他人雷同
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
1.删除gcc产生代码中以"."开头的编译器指令,针对每条指令画出相应栈帧的情况
2.(选做)使用gdb的bt/frame/up/down 指令动态查看调用栈帧的情况
实验结果
2.
参考资料:
http://blog.csdn.net/21cnbao/article/details/7385161
http://blog.csdn.net/nabila/article/details/7786428