【CSAPP】笔记-Cp3-1-程序编码、数据格式、访问信息和数据操作
世界Skill
CSAPP(深入理解计算机系统)第三章“程序的机器级表示”第一部分“程序编码、数据格式、访问信息和数据操作”的笔记和课后习题。
Github的同步工程在这
计算机执行的是机器码,即一条一条的01序列,这些序列经过CPU的控制译码器译码后以数字电路的形式操作CPU的其他部件进行工作。但机器码对人的可读性极其差,所以有了汇编代码。汇编代码可以看做是机器码的文本表现,十分接近于机器码的原本性质,但其抽象程度过低,难以构建大型工程,所以又有了C这样系统级语言,他可读性和抽象性更高,同时不会损失太多性能,但对于日渐膨胀的效率需求,系统及语言很多时候也无法更上迭代的速度,所以便有了C#这种编译型的高级语言和Python这种解释型的脚本语言,他们比C更上层,开发效率更高,门槛更低。但一般而言,开发效率和运行效率是不可兼得的。
对于C,需要一个编译器将其编译到汇编,汇编器再将汇编转换为机器码,而不同硬件平台的汇编代码往往有差异,所以高级语言还有一个重要的特点就是帮我们屏蔽了硬件底层,将兼容工作交给工具去做。
“精通细节是理解更深和更基本概念的先决条件。”
程序编码
在xinx系统中,可以使用以下代码编译一个C文件:
gcc -o1 -o p p1.c p2.c
编译两个C文件到p
中,使用第一级优化。优化随着等级上升性能提高,但和原始代码的对应会下降,并且会增加编译时间。
在编译过程中,编译器文件首先会将代码中的#include
和#define
扩展插入代码,然后编译成汇编文件.s
,随后汇编为机器码.o
文件,然后和预制库进行连接,生成最后的可执行文件。
程序中的代码段存储在程序存储器中,比如操作系统信息、运行时栈等,其使用虚拟地址来寻址,操作系统负责将虚拟地址转换成物理地址。
一个例子
如果我们有一段C代码code.c
,运行gcc -O1 -S code.c
将其编译为汇编文件后便是code.s
,其中是这段代码对应的汇编代码,这段代码一般会用push
指令保护现场,然后执行函数,最后pop
指令恢复现场。运行gcc -O1 -c code.s
会得到code.o
,这是二进制的机器码,其大小很大因为包含了执行开始等附加信息。可以通过反汇编器来从机器码中得到原始函数的字节长度并用gdb
来检查。
作为CISC,即复杂指令集的CPU,IA32和X86架构的机器指令长度均不一致,有的长有的短。
数据格式
机器中的数据格式和C中的数据类型有一定的对应关系。整形数据格式有8bits的字节,16bits的字,32bits的双字和64bits的四字等,浮点数据格式有32bits的单精度,64bits的双精度和60~72bits的扩展精度。这些格式在汇编中拥有各自专属的指令,会在一般指令后加上后缀,比如两个双字相加就是addl
等。
对于IA32处理器,由于四字不被直接支持,所以四字的运算和操作会被扩展。
访问信息
新时代的CPU对寄存器的专用性需求不再存在,所以CPU中的寄存器都是通用寄存器,但一般有一种约定,IA32中每一个CPU包含八个32bits的寄存器,有些指令会使用固定的源寄存器和目的寄存器,eax
、ecx
、edx
特殊,ebp
和esp
保存栈指针和帧指针,等等。
每一个指令都有其操作数,一些指令又分为源操作数和目的操作数,这些操作数可以是立即数、存储器地址和寄存器,对于存储器,可以用许多种方式进行寻址,比如绝对寻址、变址寻址等,详细请自行查找CPU寻址相关。
数据传送指令将数据从一个位置复制到另一个位置,mov
系列传送数据,movs
系列传送符号扩展字节,movz
系列传送零扩展字节,push
和pop
分别压栈和出栈。需要注意的是,传送指令的两个操作数不能都为存储器。
栈是一种特殊的数组,遵循先入后出原则,但由于其和一般数据的存储本质上并无不同之处,所以还是也可以使用一般的寻址方式访问其中的任意元素。
C中的间接引用运算符*
可以将一个指针指向地址的值取出放入一个局部变量之内,这和取地址操作符&
相反,后者返回一个变量的地址,而*p=x
这种形式则是指针间接引用形式,它把x
复制到x
指向的位置。
C中的局部变量一般保存在寄存器中,访问起来比在存储器中要快得多。
算术和操作逻辑
算术像是加减乘除移位这些操作大多都可以对应汇编里的一条单独指令,比如ADD S, D
就是将S+D
的结果放到D
中,INC D
就是将D
中的数据自加1等。
除此之外,还有leal
指令,即加载有效地址指令,是movl
指令的变形,它可以看做是普通计算,lea 7(%edx, %edx, 4), %eax
表示将%eax
中的值设定为5x+7
,括号中三个操作数记为x1、x2、x3则表示x1 + x2 * x3
。此指令最大的好处在于他不通过ALU,可以单周期进行计算,效率极高。
移位操作SAR
、SHR
等的源操作数是立即数或cl
寄存器中的数,并且范围在0~31
,功用基本和C中的一一对应。
对于乘除法,除了提供截断到32位的乘除法之外,IA32指令集同样提供mull
、imull
这样的存储64位结果的无符号或者有符号指令,分别有单操作数和双操作数。对于单操作数,其中一个参数在%eax
中,对于乘法,结果高位存储在%edx
、地位存储到%eax
;对于除法,商在%eax
,余数在%edx
。还有像是cltd
这种指令,设置除数,将%eax
符号扩展到%edx
。
习题
所有有代码的练习都以以题号为名字的单个文件内。
代码位于CSAPP-Chapter3内。
1
基础,略过。
2
- movl
- movw
- movb
- movb
- pushl
- movw
- popl
3
- %bl不能作为存储器地址
- 应该使用movw
- 源操作数和目的操作数不应该都为存储器地址
- 寄存器sh不存在
- 不能将立即数作为目的操作数
- 指令和目的寄存器的大小不符
- 应该是movw
4
基础,略过。
只要认清8bitsyongal
,符号扩展用movs
等即可。
5
这段汇编代码本质上是将xp
的内容复制到yp
,yp
的内容复制到zp
,zp
的内容复制到xp
。
void decode1(int *xp, int *yp, int *zp*){
int x, y, z;
*yp = x;
*zp = y;
*xp = z;
}
6
- x + 6
- x + y
- x + 4y
- 9x + 7
- 4y + 10
- x + 2y + 9
7
基本操作,略过。
8
movl 8(%ebp), %eax
sal $2, %eax
movl 12(%ebp), %ecx
sar %cl, %eax
9
int arith(int x, int y, int z){
int t1 = y;
int t2 = t1 ^ x;
int t3 = !(t2 >> 3);
int t4 = z - t3;
return t4;
}
10
A. 两个相同的数异或,结果恒为0,本质上是将%edx
寄存器置零
B. 更直接的操作是movl $0, %ebx
C. xorl
版本只需要2个字节,mov
版本需要5个
11
将cltd
指令替换为mov $0, %edx
,并且将idivl
替换为divl
即可。
12
A. 这显然是64位数的运算并且第四行使用的是无符号运算,所以num_t
是unsigned long long
类型。
B. 首先将x
和yh
相乘,结果记为s
,sl
存入%ecx
,而后将x
与yl
相乘得t
,th
存入%edx
,tl
存入%eax
,而后将s
和th
相加得到的r
存入%edx
,最后将$dest=2^{32}tl + r$
。