文章目录
CSAPP第三章知识点归纳(程序的机器级表示)(还未结合ppt,之后结合了补上)
基本概念
- 指令集体系结构(指令集架构):定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响
程序编码
-
机器代码可见的处理器状态:
- 程序计数器PC %rip (给出下一条要执行的指令的地址)
- 整数寄存器文件 16个 64位的
- 条件码寄存器
- 一组向量寄存器,存放一个或多个整数或者浮点数值
-
上面这一条中 -Og表示生成的代码符合原始程序的结构,-S表示生成汇编代码
-
汇编代码中所有以“.”开头的行都是指导汇编器和链接器工作的伪指令
.file "010-mstore.c" .intel_syntax noprefix .text .globl multstore .type multstore, @function multstore: .LFB0: .cfi_startproc push rbx .cfi_def_cfa_offset 16 .cfi_offset 3, -16 mov rbx, rdx call mult2 mov QWORD PTR [rbx], rax pop rbx .cfi_def_cfa_offset 8 ret .cfi_endproc .LFE0: .size multstore, .-multstore .ident "GCC: (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1" .section .note.GNU-stack,"",@progbits
数据格式
- 再次回顾一下字节大小
访问信息
- x86-64 CPU的16个存储64位值得通用目的寄存器
- 移动指令以寄存器为目标的时候得规则:生成1字节和2字节数字得指令会保持剩下得字节不变;生成4字节数字的指令会把高位4字节置为0。(后面这条规则是作为从IA32到x86-64的扩展的一部分而采用的)
操作数指示符
- 操作数分成3种类型:
- 立即数:以“$”后面跟一个整数表示 如 $0x34 或者 $-544
- 寄存器:表示寄存器的内容
- 内存引用:根据计算的有效地址访问某个内存位置
寻址模式(重点)
数据传送指令
-
源操作数指定的值是一个立即数,存储在寄存器种或内存中
-
目的操作数为内存位置or寄存器
-
x86-64的限制:传送指令的两个操作数不能都指向内存位置
-
movl 指令以寄存器作为目的时,会把该寄存器的高位4字节设置为0(x86-64惯例,任何为寄存器生成32位值的指令都会把该寄存器的高位部分置成零)
-
常规的movq指令只能表示为32位补码数字的立即数作为源操作数,然后把符号扩展到64位的值,放到目标位置。movabsq指令能够以任意**64位立即数作为源操作数,并且只能以寄存器**作为目的
-
下面的是零扩展指令(其中由四字节到八字节扩展用movl指令)
- 下面的是符号扩展指令(其中的cltq指令与movslq %eax ,%rax效果一样,但是编码更紧凑)
压入和弹出栈数据
-
惯例:栈是倒过来画得,栈顶在图的底部,
-
栈指针**%rsp**保存着栈顶元素的地址(注意这里的顺序是push先栈指针减,后入栈)
算术和逻辑操作
-
leaq没有其他大小的变种,因为是设计来对对地址进行操作的(其他的例如add有addb、addl、addw、addq)
-
整数算术操作如下
- 其中第二组中的是一元操作数,既是源又是目的
- 第三组中的是二源操作数,左边的是源,右边的既是源也是目的
- 最后一组中的是移位操作,移位量可以是一个立即数,或者是放在单字节寄存器%cl中(这些指令只允许以这个寄存器作为操作数)(移位操作对w位长的数据值进行操作,位移量是由%cl寄存器的低m位决定的,其中$ 2^m=w $,高位会被忽略)
-
加载有效地址指令
- 不从指定的位置读入数据,没有引用内存,而是将有效地址写入到目的操作数
- 下面图中计算z*12要分两步是因为比例因子只能是1、2、4、8
特殊的算术操作
- clto,无操作数,隐含读出%rax的符号位,并将它复制到%rdx的所有位
- imulq和mulq指令要求一个参数必须在寄存器%rax中,另一个作为指令的源操作数给出,结果高64位放在%rdx中,低64位放在%rax中(汇编器通过计算操作数的数目来区分用哪条指令)
控制
条件码
- 最常用的条件码如下:
-
leaq指令不改变任何条件码,因为它是用来进行地址计算的(除此之外,其他的运算指令都会设置条件码)
-
对于逻辑操作,进位标志和溢出标志会设置成0
-
对于移位操作,进位标志将设置为最后一个被移除的位,而溢出标志被设置成0
-
INC和DEC指令会设置溢出和零标志,但是不会改变进位标志
-
下面的指令只设置条件码而不改变其他的任何寄存器
这里注意的是cmpq的比较是$ S_2 和 和 和 S_1 $比较
比如
cmpq %rsi,%rdi
setl %al
movzbl %al,%eax
ret
这里面setl判断的是%rdi中的值是否是小于%rsi中值
-
条件码不能直接读取,一般通过以下的方法:
-
根据条件码的某种组合,将一个字节设置为0或1(注意区分表中有符号和无符号的指令后缀区别)
-
条件跳转到某个其他部分
-
条件传送指令
跳转指令
-
直接跳转和间接跳转:直接跳转的跳转目标是作为指令的一部分编码的(e.g. jmp .L1);间接跳转跳转目标是从寄存器或内存位置中读出的( jmp *(%rax))
跳转指令:
注意:条件跳转只能是直接跳转
- 执行pc相对寻址时,程序计数器的值是跳转指令后面的那条指令的地址。而不是跳转指令本身的地址
用条件控制实现条件分支
- C语言中的if-else语句的通用形式模板如下:
汇编通常会使用的相应的控制流如下:
用条件传送来实现条件分支
- 条件传送指令:
(同条件跳转不同,处理器无需预测测试的结果就可以执行条件传送。处理器只是读源值,检查条件码,然后可能更新目的寄存器,可能保持不变)
要注意的是条件传送只有在一定的条件下才可以使用,因为他是计算了两个分支的值,如果分支中有别的作用,就会导致错误,这时候只能使用条件控制分支,例如下面这种:(分支中分别有lt_cnt的增加一)
long absdiff_se(long x, long y)
{
long result;
if (x < y) {
lt_cnt++;
result = y - x;
}
else {
ge_cnt++;
result = x - y;
}
return result;
}
//下面这种也是不可以使用条件传送的,因为如果xp不存在,它会间接引用空指针
long cread(long *xp)
{
return (xp?*xp:0);
}
循环
do-while循环
- 相应的goto语句:
- 例子:
long fact_do(long n)
{
long result = 1;
do {
result *= n;
n = n-1;
} while (n > 1);
return result;
}
- 相应的汇编代码:
fact_do:
movl $1, %eax
.L2:
imulq %rdi, %rax
subq $1, %rdi
cmpq $1, %rdi
jg .L2
rep; ret
while循环
-
通用形式:
-
有两种形式:
- 跳转到中间:
- guarded-do:
-
例子:
long fact_while(<