上一波代码:http://www.cnblogs.com/20145207lza/p/5954525.html
这周脱坑!!!!!
控制
(1)状态寄存器(条件码寄存器)
- 常用条件码:
- CF:进位标志。最近操作使最高位产生进位。用于检查无符号操作数的溢出。
- ZF:零标志。最近操作结果为0。
- SF:符号标志。最近操作得到的结果为负数。
- OF:溢出标志。最近操作导致一个补码溢出。
- leal 不改变条件码寄存器
- 比较指令cmp和减法指令sub有何不同?
- sub d,s 是d-s,结果送回d中,即送回目的操作数中。
- cmp d,s 也是d-s,但结果不送回目的操作数中,是利用减法进行两个数值的比较。
(2)访问条件码
- 常用的使用方法:
- 根据条件码的某个组合,将一个字节设置为0或1。
- 可以条件跳转到程序的某个其他部分
- 可以有条件的传送数据
(3)跳转指令
- 无条件跳转
- 直接跳转:跳转目标是作为指令的一部分编码的。
- 间接跳转:跳转目标是从寄存器或存储器位置中读出的。
例:
jmp *%eax 用寄存器%eax中的值作为跳转目标。
jmp *(%eax) 以%eax中的值作为读地址,从存储器中读出跳转目标。
- 其它跳转指令:
- 一些底层的机器指令有多个名字,条件跳转只能是直接跳转。
- 跳转指令编码
- 最常用的是PC相关的,它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。
- 第二种编码方法是给出“绝对”地址,用四个字节直接指定目标。
- 执行与PC相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。
(4)翻译条件分支
- 将条件表达式和语句从c语言翻译成机器语言,最常用的方式就是结合有条件和无条件跳转。
- if-else 的汇编结构
- 通用形式模板
if(test-expr)
then-statement
else
else-statement
(注:test-expr 整数表达式[假/真])
- 汇编实现形式
t = test-expr;
if (!t)
goto false;
then-statement
goto done;
false:
else-statement
done:
(5)循环
- do-while循环
- 通用形式:
do
body-statement
while(test-expr);
- 翻译成如下条件和goto语句:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
- while循环
- 通用形式:
while (test-expr)
body-statement
- 转换成 do-while 形式:
if(!test-expr)
goto done;
do
body-statement
while(test-expr);
done:
- 翻译成 goto 形式:
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
- 同 while:
init-expr;
while(test-expr)
{
body-statement
update-expr;
}
- 对应 do-while 形式:
init-expr;
if(!test-expr)
goto done;
do{
body-statement
update-expr;
}while(test-expr);
done;
- 转换成 goto 代码:
init-expr
t = test-expr;
if(!t)
goto done:
loop:
body-statement
t = test-expr;
if(t)
goto loop
done:
(6)条件传送指令
(7)switch语句
- switch语句可以根据一个整数索引值进行多重分支。处理具有多种可能结果的测试时,这种语句特别有用。
- 优点:提高了代码的可读性;使用跳转表这个数据结构使用实现更加高效。
- 跳转表:是一个数组,表项i是一个代码段的地址,这个代码段实现当switch索引值等于i时程序应该执行的动作。程序代码用于索引值来执行一个跳转表内的数组引用,确定跳转指令的目标。和使用一组很长的if-else相比,使用跳转表的优点是执行switch语句的时间与switch的case数量无关。GCC根据switch语句中case的数量和case中值的稀少程序来翻译开关语句。当case数据比较多(例如4个以上),并且值的范围跨度比较小时,就会使用跳转表。
- 执行switch语句的关键步骤是通过跳转表来访问代码位置。
过程
(1)概述
- 一个过程调用包括将数据和控制从代码的一部分传递到另一部分,需要在进入时为过程的局部变量分配空间,并在退出时释放这些空间。
- 数据传递、局部变量的分配和释放通过操纵程序栈来实现。
(2)栈帧结构
栈帧: 为单个过程分配的那部分栈
- 最顶端的栈帧以两个指针界定:
- 寄存器%ebp为帧指针
- 寄存器%esp为栈指针
- 程序执行时,栈指针可以移动,大多数信息的访问都是相对于帧指针的。
- 栈向低地址方向增长,栈指针%esp指向栈顶元素:
- 栈指针值适当减小可以分配没有指定初始值的数据的空间
- 类似的,可以通过增加栈指针来释放空间
(3)转移控制
- call指令
- 目标:指明被调用过程起始的指令地址。
- 效果:将返回地址入栈,并跳转到被调用过程的起始处。
- ret指令
- 从栈中弹出地址,并跳转到这个位置.
- 使用这个指令时,栈指针要指向前面call指令存储返回地址的位置。
- leave
- 这个指令可以使栈做好返回的准备
- 等价于:
movl %ebp,%esp
popl %ebp
(4)寄存器使用惯例
- 程序寄存器组是唯一能被所有过程共享的资源。
- 为什么必须遵守惯例:必须保证一个过程(调用者)在调用另一个过程(被调用者)时,被调用者不会覆盖某个调用者寄存器中的值。
- 惯例:
- %eax,%edx,%ecx 划分为调用者保存寄存器
- %ebx,%esi,%edi 划分为被调用者保存寄存器
- %ebp,%esp 保持寄存器
- %eax 保存函数返回值
(5)递归过程
- 当过程被调用时分配局部存储,返回时释放存储。
GDB调试器
- GDB命令:
#作业
- main.c:
- 汇编代码:
- 用vi查看编译器指令:
- 删除gcc产生代码中以"."开头的编译器指令后:
- 分析:
- main函数保存%ebp,并设置新的帧指针。
pushl %ebp
movl %esp,%ebp
- 分配4字节的栈空间
subl $4,%esp
- 设置 arg1=8
movl $8,(%esp)
- call调用fh
- fh被调用,初始化帧指针,分配栈空间。
- 将(%esp)中的8给 %eax,即存入栈中
movl %eax,(%esp)
- call调用gh
- gh被调用,初始化栈指针,分配栈空间
- 将 %eax 与立即数 3 相加
add $3,%eax
- 在gh结束前弹栈
popl %ebp
- ret返回fh中call的调用位置
- fh也结束,return返回main中call调用的位置
- main继续 %eax 加1的操作
addl $1,%eax
- leave为返回准备栈,相当于%ebp出栈,最后ret结束。
代码托管:https://git.oschina.net/24ktyrant/codes