CSAPP阅读笔记-汇编语言初探(控制类指令)-来自第三章3.6的笔记-P135-P163
1.正溢出与负溢出:
首先,一个正数与一个负数相加,不可能溢出,因为结果的绝对值一定小于两个加数的绝对值,既然两个加数能合理表示出来,结果一定也能合理表示出来。
其次,正溢出是由于两个很大的正数相加,导致符号位变成1的情况如0110+0011=1001(假设最大只能运算4位)
负溢出则是两个很小的负数相加,导致符号位变成0的情况,如1011(-5)+1011(-5)=10110->0110溢出,如1111(-1)+1111(-1)=11110->1110则没溢出。
因此,正溢出的判断标准是符号位或最高位有进位。
负溢出的判断标准是符号位和最高位只有一个发生了进位。符号位和最高位同时发生进位则没溢出。
注意,这里的最高位指的是去掉符号位后的最高位,即符号位后面一位。
可以结合上面列举的负溢出的例子理解。
2.条件码寄存器:
CPU维护着一组条件码寄存器,它们只有一个位,它们会记录最近的算术或逻辑操作带来的变化,常用的有:
CF:进位标志,代表最近的操作使最高位产生了进位,用于检测无符号操作的溢出。
ZF:零标志,代表最近的操作结果为0。
SF:符号标志,代表最近的操作结果为负数。
OF:溢出标志,代表最近的操作导致了正溢出或负溢出。
那么,系统是怎么根据操作来设置条件码寄存器的呢?以什么为判断基准?
比如系统用一条ADD指令完成了等价于t=a+b的功能,这时候会用以下表达式为判断基准,来设置条件码寄存器:
CF: (unsigned)t < (unsigned)a 无符号溢出
ZF: (t==0) 零
SF: (t<0) 负数
OF: (a<0 == b<0) && (t<0 != a<0) 有符号溢出
解释下CF: CF可用于检测无符号操作的溢出,若t与a,b都无符号,则都>=0,若此时t<a说明溢出了,而无符号操作溢出时的表现就是最高位(此时因为是无符号操作,最高位已经对应符号位了)出现了进位,因此对应CF。
再解释下OF: OF代表发生了溢出,需要满足两个条件,一是两个加数符号相同,二是结果的符号要和任意一个加数相反。
leaq不是算术或逻辑指令,不会改变条件码。
逻辑操作中,XOR会使CF和OF标志被设置为0
移位操作中,CF为最后一个被移出的位,OF为0 为什么?
INC和DEC会设置OF和ZF,但不会改变CF
3.CMP和TEST指令
CMP和TEST指令都有b,w,l,q版本,分别对应字节,字,双字,四字
CMP指令等价于SUB,区别就是它不会把计算结果更新到目的寄存器。CMP S1,S2会计算S2-S1并根据结果设置条件码。
TEST指令等价于AND,区别就是它不会把计算结果更新到目的寄存器。TEST S1,S2会计算S1&S2并根据结果设置条件码。
TEST指令可用来判断某个操作数是正数,负数还是0,比如 testq %rax,%rax
4.访问条件码
一般不直接访问条件码,而是根据条件码的组合设置某个字节为0或1,对应的就是SET指令。如下图:
其后缀不是用来标志操作数大小的,只是用来代表不同的比较条件的。
一个例子:
比较a(位于%rdi)和b(位于%rsi)时,汇编如下:(两者都是64位long)
comp:
cmpq %rsi,%rdi
setl %al
movzbl %al,%eax
ret
此时比较的是%rdi(a)-%rsi(b),结果被设置到%al中,movzbl在设置%eax的高3个字节为0时,还会把%rax的高4个字节一起清0。
取其中的一条指令分析一下:
setl,代表有符号的<,是以SF^OF作判定条件的,当没有溢出(OF为0)且结果为负数(SF为1)时,显然代表a-b<0,即代表有符号<成立,设置为1。当发生溢出时(OF为1),若正溢出(OF为1)且结果为非负数(SF为0),显然代表a-b<0,同样,当负溢出(OF为1)且结果为非负数(SF为0)时,也代表a-b<0,综上,用异或可以作判定条件。
5.条件控制转移指令
如上图所示,跳转指令分有条件跳转和无条件跳转。
jmp是无条件跳转,在汇编代码中,它后面直接加标号,汇编器把它变为.o文件时,会将标号对应的目标地址编码为跳转指令的一部分。
它又分为直接和间接跳转,前者会直接把目标地址作为跳转指令的一部分,后者则从寄存器或内存中读出目标地址。
直接跳转的表现形式是直接在jmp后加标号,如jmp .L1。间接跳转的表现形式是*后面加操作数指示符,如jmp *%rax,又如jmp *(%rax)。
表中其它跳转指令都是条件跳转指令,条件跳转只能是直接跳转。
那么跳转指令在编码机器代码时是如何确定目标地址的呢?
如下:
左侧为一段汇编代码,右侧为对应的机器代码(.o)及反汇编代码。
跳转指令在变成机器代码时,最常用的编码方式是把目标地址和跳转指令后面那条指令对应的地址之差作为编码。当然,也有直接给出目标地址的编码方式。
右图中,.L2下面的第一条指令的地址为8,jmp的下一条指令地址是5(.L3是标号,不是指令),相差8-5=3,符合之前的规律。
这种表达方式的优点是表示的跳转目标都是相对值,因此当程序被重定位时(比如被链接后),改变的只是这段代码的绝对地址,但机器码仍可以不用变。
例子:
40042f: 74 f4 je xxxxxx
400431: 5d pop %rbq
请写出xxxxxx的地址:
0xf4 = -12 , 12 = 0xc , 400431-0xc = 400425
6.条件传送指令:
条件控制转移指令存在一种缺陷,处理器是通过流水线的方式处理指令的,在取一条指令的同时,可能同时在执行前一条指令的算术运算。因此需要预先确定好指令的执行序列。当出现条件跳转时,处理器会对分支进行预测,虽然准确率很高,但一旦预测失败,处理器需要丢掉它为此跳转指令后面所做的所有工作,重新填充流水线。这会导致程序性能下降。
而条件传送指令则是先把条件分支的多个值计算出来,比如说一个是a,一个是b,随后的操作是固定的,比如对a操作,++a什么的,此时若发现选的是b分支,则只需要b=a这么复制一下就行了,优势就在于无需为此丢掉跳转指令后面所做的工作,当然代价就是需要多做一次计算,因此条件传送指令的适用条件有限,编译器需要根据浪费的计算和分支预测错误导致的性能处罚中作权衡,然而实际上它无法很好地判断,因此,只有当两个表达式都十分容易计算时,编译器才会选用条件传送指令,有时候即使分支预测错误的开销更大,仍会选择条件控制转移指令。
以上是条件传送指令,和SET以及JMP一样,只有满足指定条件才能传送数据,源操作数可以是寄存器或内存地址,目的地是寄存器,它和MOV类指令类似,区别是指令名无需写上传送数据的长度(b,w,l,q),汇编器可从寄存器中推断出操作数长度(因为目的地不可能是内存地址,所以可以直接推断出)。
条件传送指令不支持单字节传送。
条件传送指令有使用限制,因为它必须计算所有的条件表达式,因此若任意一个有错误条件,会导致非法行为。
比如:
对于左侧这段C代码,汇编形式如右侧所示,因为即使xp计算为0,cmove仍会计算0(%rax)和*xp(%rax)的值,而此时xp为空指针,会发生引用空指针的错误。
7. do while和while的实现
首先看下do while的实现例子:
清晰易懂,无需解释。
while循环则有两种实现形式:
第一种,被称为跳转到中间的实现形式,如下:
第二种形式是guarded-do形式,如下:
第二种形式其实是一种更高级别的优化,它主要对初始值进行优化,大部分情况下,初始条件下的while的循环条件是满足的,此时对第一种情况,仍会做一个跳转(可以看到第一种形式的b图中的goto test必然会被执行),而第二种形式对此作了优化,它会先判断一下初始是否满足while循环条件,满足时继续执行不跳转,不满足时才跳转到结束段,这种手法相当于把while改造成了do while,比起do while,仅仅多了一层初始的判断。
8. for循环的实现:
for循环都能转换为while,转换思路如下图:
具体转换例子见书。
下面做一个练习:
将下面的for循环先转换为跳转到中间的while形式,再转换为guarded-do形式:
答案:
以第一种形式转换:
以第二种形式转换:
这里有个需要注意的地方,在由第一种向第二种转换时,开头的那个判断条件是n<=1,那是因为它只对初始条件判断就行,因为循环不退出条件为i<=n,而开始时i=2,所以开始时无法进入循环的条件是n<=1。
第二个练习:
答案:
A:直接翻译后如下图所示:
此时continue会直接跳过本次循环进入下次循环,相对的也会跳过i++,导致i一直不变,永远跳不出while循环。
改变方法:使用goto代替continue:
8.switch语句
switch通过跳转表实现,它是一个数组,里面每一项都是一个代码段的地址,GCC根据开关数量决定是否使用跳转表(如大于4个,且值跨度较小会用)
看一个switch的例子:
上面两张是switch语句的C语言使用及翻译,需要注意几点:首先&&在这里是一个C语言中的扩展运算符,用于创建一个指向某个代码位置的指针。
void *jt[7],jt是一个指针数组,这里unsigned long index=n-100配合if(index>6)可以把n的值限制在[100,106],因为n<100时n-100为负数,转换为unsigned后是个很大的正数。
下面左边那张是对应的汇编代码,注意jmp *.L4(,%rsi,8),这里是一个间接跳转,从.L4的地址为起始找到8*%rsi指代的索引对应的地址,乘8是因为一个内存地址占8个字节(64位)
右边那张是对应的跳转表,从中寻址到对应跳转目的地的地址,用间接跳转跳转过去。
CSAPP阅读笔记-汇编语言初探(控制类指令)-来自第三章3.6的笔记-P135-P163的更多相关文章
- CSAPP阅读笔记-汇编语言初探(算术和逻辑操作类指令)-来自第三章3.5的笔记-P128-P135
1.算术和逻辑操作类指令分四类:加载有效地址,一元操作,二元操作和移位,如下: 2. leaq指令,类似mov指令,它左侧的数看似是给出一个地址,在内存中从给定的地址取操作数,传给右边的目的地.但其实 ...
- CSAPP阅读笔记-汇编语言初探(数据传送类指令)-来自第三章3.2-3.3的笔记-P115-P128
1.如何由机器代码生成汇编代码? objdump -d再加上文件名即可直接在终端看到由反汇编器恢复的汇编代码.注意,文件名并不一定得是.o文件,任何可执行文件都可以. 结果如下: 仅列举了反汇编tes ...
- CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113
gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...
- CSAPP阅读笔记-变长栈帧,缓冲区溢出攻击-来自第三章3.10的笔记-P192-P204
一.几个关于指针的小知识点: 1. malloc是在堆上动态分配内存,返回的是void *,使用时会配合显式/隐式类型转换,用完后需要用free手动释放. alloca是标准库函数,可以在栈上分配任 ...
- CSAPP阅读笔记-栈帧-来自第三章3.7的笔记-P164-P176
1.基本结构: 如上图所示,是通用的栈帧结构.大致分两块,调用者函数P和被调用者函数Q. 对P来说,要做的工作是把传递参数中多于6个的部分压栈,随后把Q返回时要执行的下一条指令的地址压栈. 对Q来说, ...
- CSAPP阅读笔记-struct, union, 数据对齐-来自第三章3.9的笔记-P183-P191
1.数据对齐 为什么要对齐:通俗点解释就是CPU对数据访问时,每次都是取固定数量的字节数,假如一次取4个字节,若有个int存在0x01-0x04,则一次就能取出,若存在0x03-0x06,则需要分两次 ...
- CSAPP阅读笔记-数组分配与访问-来自第三章3.8的笔记-P176-P183
这一节比较简单,仅记录几个比较重要的点: 1.C语言允许对指针进行运算,计算出的值会根据该指针引用的数据类型大小进行伸缩. 例子: 其中,xE是数组的起始地址.注意,指针运算时,若最终结果为指针,则指 ...
- CSAPP阅读笔记-32位64位的区别--来自第三章引言的笔记--P110
仅从寻址上看,32位和64位机器能寻址的内存空间大小不同. 需要知道的是,计算机系统对存储器作了抽象,程序“认为”内存是一个很大的字节数组,然而实际上它是由多个硬件存储器和操作系统组合起来实现的. 程 ...
- 软件测试价值提升之路- 第三章"拦截缺陷 "读书笔记
作为一个测试团队,基本的职责是:测试产品,发现缺陷,报告结果,使每个版本的测试水准稳步提升.这些价值是作为一个测试所必须具备的,发挥这些价值能够让测试获得研发团队的基本信任.这类价值分为3部分: 1) ...
随机推荐
- hadoop理解
Hadoop的主核心有2部分: 1,HDFS 2, MapReduce 首先: HDFS HDFS(Hadoop Distributed File System,Hadoop分布式文件系统),它是一个 ...
- React Native开发环境的搭建
我只能说搭建开发环境还是不能相信网上纷乱的博客,还是中文网靠谱. http://reactnative.cn/docs/0.47/getting-started.html 纯粹只是为了记录一下.
- python中用ElementTree.iterparse()读取xml文件中的多层节点
我在使用Python解析比较大型的xml文件时,为了提高效率,决定使用iterparse()方法,但是发现根据网上的例子:每次if event == 'end':之后elem.clear()或者是每次 ...
- Delphi IOS开发环境安装
RAD Delphi XE/10 Seattle 安装IOS.OSX环境安装,IOS模拟器,MAC X 真机可以调试 http://community.embarcadero.com/blogs/en ...
- vector容器(一)
一. Vector简要描述 vector是C++标准模版库STL提出的一种顺序存储结构,之所以称之为“容器”,是因为vector是一个模板类,它允许我们重复利用已有的实现构造自己的特定类型下的数据结构 ...
- vs附加调试
已解决.项目-属性-调试-启用调试器选择启用本机代码调试,不知道怎么的选成启用Visual Studio承载进程了.
- 什么是C#?什么是.NET Framework?
1.什么是C#: 解1:C#就是一门开发语言,是由C及C++演变而来的,有朋友戏称之为"C四个+",这里的"#"号,不读"井",而读做&qu ...
- django 生命周期
客户端发送请求 客户端(浏览器) → 发送请求 → 服务器(wsgi) → 解析请求 → 服务器(Middleware) → process_request → 服务器(urls) → 通过路由寻vi ...
- sql 统计常用的sql
统计常用的sql 统计常用的sql语句: 今天的所有数据:select * from 表名 where DateDiff(dd,datetime类型字段,getdate())=0 昨天的所有数据:s ...
- 理解图像Garbor和HOG特征的提取方法及实例应用
前言:今天接触到了这两个特征,看了课本和博客后很蒙蔽,没有理解这两个特征,本篇博客的目的是只是参考其他的博客总结这两个特征,如果未来能研究和工作领域是这方面的话再回来自己研学,如有错误也欢迎指出. G ...