学习计时:共11.5小时
读书:5
代码:2
作业:2.5
博客:2
一、学习目标
二、学习资源
1. 教材:第三章《程序的机器级表示》:重点是3.7,3.11
2. 课程资料:https://www.shiyanlou.com/courses/413 实验四,课程邀请码:W7FQKW4Y
三、学习方法
1. 进度很重要。
四、学习任务
1. 阅读教材,完成课后练习(书中有参考答案)
3.1-3.7中练习,重点:3.1,3.3,3.5,3.6,3.9,3.14,3.15,3.16,3.22,3.23,3.27,3.29,3.30,3.33,3.34
2. 考核:练习题把数据变换一下
3. 实验:需要动手的到实验楼中练习一下
4. 深化、实践题目,额外加分
五、后续学习预告(可选):
第四章《处理器体系结构》
六、学习过程
一: 程序编码
P105 例子 :gcc -01 -o p p1.c p2.c
命令gcc 指的就是GCC C编译器。
-01 是告诉编译器使用第一级优化。
提高优化级别的优势和弊端:提高优化会使最终运行的更快,但是编译的时间可能会更长,用调试工具对代码进行调试会更困难。
-02 是更好的选择,从得到的程序性能方面考虑。
将源代码转化为可执行代码的过程:
1:C预处理器扩展源代码,插入用你#include命令指定的文件,并扩展所有用#define声明指定的宏。
2:编译器产生两个源代码的汇编代码,p1.o 和 p2.o
3:汇编器将汇编代码转化成二进制目标代码文件名。
4:链接器将两个目标代码文件与实现库函数的代码合并,产生最终的可执行代码文件p。
二、机器级代码
1.机器级编程的两种抽象
a:指令集结构ISA是机器级程序的格式和行为,定义了处理器状态、指令的格式,以及每条指令对状态的影响。
b:机器级程序使用的存储器地址是虚拟地址,看上去是一个非常大的字节数组,实际上是将多个硬件存储器和操作系统软件组合起来。
储存器系统的实际实现时将多个硬件储存器和操作系统软件组合起来
汇编代码有一个特点:用可读性更好的文本格式来表示。
2.一些对程序员可见的处理器状态:
- 程序计数器指示将要执行的下一条指令在储存器的地址。
- 整数寄存器文件包含8个命名的位置。分别储存32位的值。
- 条件码寄存器保存最近执行的算术或逻辑指令的状态信息。
- 浮点寄存器存放浮点数据。
机器代码只是简单地将存储器看成一个很大的、按字节寻址的数组。
程序储存器包含:程序的可执行机器代码,操作系统需要的一些信息,运行时栈,以及用户分配的储存器块。
一条机器指令只执行一个非常基本的操作。
三、代码示例
1:p107 int accum = 0;
int sum(int x, int y)
{
int t = x + y;
accum += t;
return t;
}
命令行上 -s 选项的使用,得到编译代码:Unix〉 gcc -01 -s code.c
要查看目标文件的内容,最有价值的是反汇编器。linux中 -d 表示 object dump。
读取地址时要注意是不是小端法,小端法的正确读法是两位两位的反方向去读存。
2:机器代码和它的反汇编表示的一些特性:
1:IA32指令长度从1到15个字节不等。
2:设计指令格式的方式是,从某个给定位置开始,可以将字节唯一的解码成机器指令
3:反汇编器只是基于机器代码文件中的字节序列来确定汇编代码,不需要访问程序的源代码或汇编代码
4:反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些差别。
四: 数据格式
1:关于格式的注解:
所有以"."开头的都是指导汇编器和链接器的命令。为了更清楚地说明汇编代码,用一种格式来表示汇编代码。
示例如下:
2:Linux和windows的汇编格式的区别:
Intel代码省略了指示大小的后缀,即'l'
Intel代码省略了寄存器名字前面的‘%’符号,用的是esp,而不是%esp
Intel代码用不同的方式来描述存储器中位置
在带有多个操作数的指令情况下,列出操作数的顺序相反。
3:Intel中:
字 表示 16位数据类型
双字 32位 四字 64位
遇到的大多数指令都是对字或者字节进行操作的。
4:c语言基本数据类型对应的IA32表示
char 字节 1字节 short 字 2字节 int 双字 4字节 long int 双字 4字节 long long int (不支持) 4字节 char * 双字 4字节 float 单精度 4字节 double 双精度 8字节 long double 扩展精度 10/12字节
5:数据传送指令的三个变种:movb 传送字节 和movw 传送字 和movl 传送双字。
6:注意不同数据的汇编代码后缀。
五:访问信息
1:一个 IA32中央处理单元CPU包含一组8个存储32位值的寄存器,用来存储整数数据和指针,名字都以%e开头/、。
2:操作数指示符:指示出执行一个操作中要引用的源数据值,以及放置结果的目标位置。
3:源数据值可以以常数的形式给出,或是从寄存器或储存器中读出。
不同的操作数分为三种类型
立即数:常数值。
寄存器:表示寄存器的内容。
存储器:根据计算出来的(有效)地址访问某个储存器位置。
4:结果存放的两种可能 寄存器和存储器。
5:寻址方式 a:立即数寻址方式 格式:$后加用标准c表示法表示的整数,如$0xAFF。
b:寄存器寻址方式 如%eax,与汇编中学过的AX寄存器类比
c:存储器寻址方式 等。
6:数据传送指令 1:mov指令(MOV DST,SRC):把一个字节(字)操作数从源SRC传送至目的地DST。
操作数:IA32的限制:两个操作数都不能指向存储器。
MOV reg/mem, imm ;立即数寄存器或存储器
MOV reg/mem/seg, reg ;寄存器的值寄存器/内存/段寄存器
MOV reg/seg, mem ;内存单元的值寄存器/段寄存器
MOV reg/mem, seg ;段寄存器的值寄存器/内存单元
movw 传送字
movl 传送双字
movs 符号位扩展
movz 零扩展
2.push&pop
a:堆栈 1.后进后出来 2.栈指针指向栈顶元素 3.栈朝低地址方向增长。
b:压栈push
指令功能:第一步:SP←SP-2 ;堆栈指针SP上移
第二步:(SS):(SP)←r16/m16/seg ;字操作数存入堆栈顶部。
c:出栈pop
第一步:r16/m16/seg← (SS):(SP) ;栈顶的一个字传送到指定的目的操作数
第二步:SP←SP+2 ;堆栈指针SP下移,指向新的栈顶,
栈顶指针变化同压栈。
7、数据传送示例
1.c操作符*执行指针的间接引用。
2.c语言中的指针其实就是地址,间接引用指针就是将该指针放在一个寄存器中,然后在存储器引用中使用这个寄存器。
3.局部变量通常保存在寄存器中,而不是存储器
六: 算术和逻辑操作
1、加载有效地址
加载有效地址指令:leal,是movl指令的变形,对比汇编中的LEA指令学习。
指令形式:从存储器读取数据到寄存器。
实际:将有效地址写入到目的操作数,而目的操作数必须是寄存器;并不真实引用存储器。
2、按目的操作数分类:
第一类:加载有效地址。实际是将有效地址写入目的操作数,目的操作数必须是寄存器。
第二类:一元操作。操作数既是源又是目的。可以是寄存器也可以是存储器。
第三类:二元操作。第二个操作数既是源又是目的。但两个操作数不能同时是存储器。
第四类:移位操作。位移量是一个立即数或放在单字节寄存器%cl中。移位操作的目的操作数可以是一个寄存器或是一个存储器位置。
SAL 算术左移
SHL 逻辑左移
SAR 算术右移(补符号位)
SHR 逻辑右移(补0)
源操作数:移位量——立即数或CL
目的操作数:要移位的数值——寄存器或存储器
3、特殊操作
七:控制
一、条件码
CF:进位标志
ZF:零标志
SF:符号标志
OF:溢出标志
二、访问条件码
这个指的是SET指令,通过set与不同的条件码的组合,达到不同的跳转条件。
三、跳转指令及其编码
JUMP指令,同样是汇编中常用的指令,根据不同的条件和符号位进行不同的跳转动作,具体见书128页。
跳转指令有几种不同的编码,最常用的是PC(程序计数器)相关的。
需要注意的是,jump分为直接跳转和间接跳转:直接跳转:后面跟标号作为跳转目标
间接跳转:*后面跟一个操作数指示符。
四、翻译条件分支
将条件表达式和语句从c语言翻译成机器语言,最常用的方式就是结合有条件和无条件跳转。
无条件跳转:例如 goto。书上的例子就是把if-else语句翻译成了goto形式,然后再由这个形式翻译成汇编语言。
五、循环
汇编中可以用条件测试和跳转组合起来实现循环的效果,但是大多数汇编器中都要先将其他形式的循环转换成do-while格式。
- 条件分支——if-else结构:在两个分支语句中选择执行一个,汇编实现通过goto,就是汇编器为两个分支产生各自的代码块,它会插入条件和无条件分支,以保证能执行正确的代码块。
- 循环结构——do-while、while、for:用条件测试和跳转组合实现循环的效果。大多数汇编器根据do-while形式来产生循环代码,其他的循环会首先转换成do-while形式,然后再编译成机器代码。
- switch语句:根据一个整数索引值进行多重分支。通过使用跳转表这种数据结构实现更加高效。跳转表是一个数组,表项i是一个代码段的地址,这个代码段实现当开关索引值为i时程序该做的。此时跳转可以用goto/jmp。
六、Switch语句
Switch语句是多重分支的典型,而且使用的是跳转表这种数据类型,是的搜索的更快更高效。
所以这里的关键就是要领会使用跳转表是一种非常有效的实现多重分支的方法。
八:过程
一、栈帧结构
栈用来传递参数、存储返回信息、保存寄存器,以及本地存储。
1.栈帧
定义:为单个过程分配的那部分栈称为栈帧(本质上栈帧还是栈)。
2.最顶端的栈帧以两个指针界定:
寄存器%ebp-帧指针;寄存器%esp-栈指针。
栈指针可移动,所以信息访问多相对于帧指针。
3.调用的过程
P150
调用者的帧应该在被调用者的下面,并且调用者返回地址是它的栈帧末尾,这样可以保证被调用者执行完毕全都出栈后,程序能够继续向下执行。
- 被调用者Q用栈的用处:
1.保存不能存放在寄存器中的局部变量。
当要对一个局部变量使用地址操作符&的时候,就必须要为它生成一个地址,所以要入栈。这个用法!以前没见过!
2.存放它调用的其他过程的参数。
二、转移控制
1.call
call指令和转移指令相似,同样分直接和间接,直接调用的目标是标号,间接调用的目标是*后面跟一个操作数指示符,和JMP一样。
CALL指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。返回地址是还在程序中紧跟在call后面的那条指令的地址。
然后就会用到ret了。
2.ret
ret指从栈中弹出地址,并跳转到这个位置。
3.leave
这个指令可以使栈做好返回的准备
三、寄存器使用惯例
程序寄存器组是唯一能被所有过程共享的资源。虽然在给定时刻只能有一个过程是活动的,但是我们必须保证当一个过程调用另一个过程时,被调用者不会覆盖某个调用者稍后会使用的寄存器的值。
编译器根据一组很简单的惯例来产生管理栈结构的代码。参数在栈上传递给函数。
为了程序能够正确执行,让所有过程都遵循一组建立和恢复栈的一致惯例是很重要的。
七、遇到的问题及解决
写博客的速度特别的慢,尤其太注意细节问题。参考了闫佳歆同学的博客后,发现人家把内容学的很深。觉得自己有很多东西还不透彻,需要再好好理解理解。
八、其他
1:参考教科书
2:参考闫佳歆同学的博客
3:百度一些不太懂得知识点。