深入理解操作系统(6)第三章:程序的机器级表示(2)控制(包括:条件码CF,ZF,SF,OF/set指令/判断条件的典型指令/跳转指令jump/直接跳转和间接/循环do-while,while,for说明
- 1. 控制
- 1.1 条件码
- 1.1.1 最有用的条件码是:
- 1.1.2 设置条件码的指令(lea不改变条件码)
- 1.1.3 cmp test只设置条件码,不改变寄存器
- 1.2 访问条件码
- 1.2.1 set 指令操作
- 1.2.2 c判断条件的典型指令序列
- 1.2.3 指令的同义名(一条机器指令有多个名字)
- 1.2.4 a-b 例子说明
- 1.3 跳转指令 jump
- 1.3.1 jump 例子:
- 1.3.2 jump 指令大全
- 1.3.3 直接跳转和间接跳转
- 1.3.4 silly.c 汇编例子
- 1.4 翻译条件分支 goto
- 1.5 循环
- 1.5.1 do while
- 1.5.1.1 例子:
- 1.5.1.2 特点(至少执行一次)
- 1.5.1.3 do-while 的实现:
- 1.5.1.4 Fibonacci
- 1.5.2 while
- 1.5.2.1 大多数编译器将while转成do-while
- 1.5.3 for
- 1.6 switch 语句
1. 控制
到目前为止,我们考虑了访问数据和操作数据的方法。程序执行的另一个很重要的部分就是控制被执行操作的顺序。
对C和汇编代码中的语句,默认的方式是顺序的控制流,按照语句或指令在程序中出现的顺序来执行。C中的某些程序结构,比如条件语句、循环语句和分支语句,允许控制按照非顺序方式进行,即根据程序数据的值来确定顺序。
汇编代码提供了实现非顺序控制流的较低层次的机制。
基本操作是跳转到程序的另一部分,可能会视某些测试结果而定。
编译器产生的指令序列是依赖于这些低层机制来实现C的控制结构在我们的讲述中,会先谈到机器级机制,然后会给出如何用它们来实现C的各种控制结构
1.1 条件码
除了整数寄存器,CPU还包含一组单个位的条件码( condition code)寄存器,它们描述了最近的算术或逻辑操作的属性。
它们描述了最近的算术或逻辑操作的属性。
对这些寄存器的检测,将有助于执行条件分支指令。
1.1.1 最有用的条件码是:
CF:进位标志。
最近的操作使最高位产生了进位,它可用来检查无符号操作数的溢出
如: (unsigned t) < (unsigned t2)
ZF: 零标志。
最近的操作得出的结果为0
如:if(0 == bIsFlag)
SF: 符号标志。
最近的操作得到的结果为负数。
如: (t < 0)
OF: 溢出标志。
最近的操作导致一个二进制补码溢出——正溢出或负溢出
(a < 0 == b < 0) && (t < 0 != a < 0)
1.1.2 设置条件码的指令(lea不改变条件码)
lea指令不改变任何条件码,它是取地址操作的。
图 3.7 中的除lea其他指令都会设置条件码
1.1.3 cmp test只设置条件码,不改变寄存器
图3.6.1
如: testl %eax %eax //检查%eax是负数,0还是正数
1.2 访问条件码
两个最常用的访问条件码的方法不是直接读取他们,而是根据条件码的某个组合,设置一个整数寄存器或执行一条件分支指令。
1.2.1 set 指令操作
图3.10
各种set指令根据条件码的某个组合,将一个字节设置为0或者1.
1.2.2 c判断条件的典型指令序列
a = %edx b = %eax
cmpl %eax,%edx 比较a b
setl %al 设置eax的低位为0或1
解释:setl %al 效果是 %al< SF^|OF
也就是将符号标志位和溢出标志位异或的结果给al寄存器
movzbl %al,%eax 设置eax的高位为0,(其中movsbl是置0或1 就这不同)
movzbl:
movb,movsbl 和 movzbl 的区别
movb 不改变,只传送一个字节
movsbl 指令的源操作数是单字节的,它执行符号扩展到32位(也就是,将高24位设置为源字节的最高位),
然后拷贝到双字的目的中。
movzbl 指令的源操作数是单字节的,在前面加24个0扩展到32位,并将结果拷贝到双字的目的中。
例子:
初始假设%df=8D,%eax=98765432
movb %df, %al %eax=9876548D //movb 不改变其他字节
movsbl %df, %eax %eax=FFFFFF8D //movsbl 将其他高三个字节全设置为全1或0
movzbl %df, %eax %eax=0000008D //movzbl 将其他高三个字节全设置为全0
参考:
https://blog.csdn.net/lqy971966/article/details/121307845
1.2.3 指令的同义名(一条机器指令有多个名字)
某些底层的机器指令可能有多个名字,我们称之为“同义名( synonym)”。
比如说,“setg”(表设置大于”)和“ settle”(表示“设置不小于等于”)指的就是同一条机器指令。
编译器和反汇编器会随意决定使用哪个名字。
虽然所有的算术操作都会设置条件码,但是各个set命令的描述都适用于这样一种情况:
执行比较指令,根据计算t=a-b设置条件码。
例如,就sete来说,即“当相等时设置( Set when equal)”指令。
当a=b时,会得到t=0,因此零标志置位就表示相等。
1.2.4 a-b 例子说明
类似地,考虑用setl,即“当小于时设置( Set when less”指令,测试一个有符号比较。
setl %al 效果是 %al< SF^|OF
也就是将符号标志位和溢出标志位异或的结果给al寄存器
当a和b是用二进制补码表示时,对于a<b,计算两者之差时,我们会有a-b<0。
当没有溢出发生时,符号标志置位就表明a<b。
当因为a-b是一个很大的正数,出现正溢出时,我们会得到t<0。
当因为a-b是一个很小的负数,出现负溢出时,我们会得到t>0。
无论是这两种情况中的哪一种,符号标志都表示的是真正的差的反。
因此,溢出和符号位的异或测试的就是a<b。
其他的有符号比较测试是基于 SF&&OF和ZF的其他组合。
对于无符号比较的测试,当无符号参数a和b的整数差是负数时,也就是当
( unsigned)a < ( unsigned)b时,cmpl指令会设置进位标志。
因此,这些测试使用的是进位标志和零标志的组合
1.3 跳转指令 jump
在正常执行的情况下,指令按照它们出现的顺序一条一条地执行。跳转(jump)指令会导致执行切换到程序中一个全新的位置。
这些跳转的目的地通常用一个标号( label)指明。
1.3.1 jump 例子:
xorl %eax,%eax //设置eax为0
//解释:xorl 按位异或,相同的位置为0,不同的位置为1,
eax和eax的每一位都相同,所以相当于清零。
jmp .Ll //jmp .Ll 会跳过下面的movl指令,从popl开始执行
movl (%eax),%edx //Null pointer dereference
.Ll:
popl %edx
说明:
jmp .Ll 会跳过下面的movl指令,从popl开始执行
1.3.2 jump 指令大全
图3.11
1.3.3 直接跳转和间接跳转
1.直接跳转是绐出一个标号作为跳转目标的
如:jmp .Ll
2.间接跳转的写法是“*”后面跟一个操作数指示符,语法与movl指令使用的一样。
如:jmp *%eax 用寄存器eax中的值作为跳转目标
jmp *(%eax) 以%eax中的值作为读地址,从存储器中读出跳转目标
条件跳转只能是直接跳转
1.3.4 silly.c 汇编例子
与PC相关的寻址例子,下面是汇编代码片段silly.c文件
jle .L4 //跳转到更高的地址
.p2aalign 4,,7 //针对汇编器的命令,使后面指令的地址从16的倍数处开始,最多浪费7个字节
.L5:
movl %edx,%eax
sarl $1,%eax
subl %eax,%edx
testl %edx,%edx
jg .L5
.L4:
movl %edx,%eax
其.o格式反汇编:
8:7e 11 jle 1b<silly+0x1b> //跳转到 0x10 其实是第二个字节 0x11 就是17
0x1b = 11 + 10
a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi //空指令,使下一条指令的起始地址是16的倍数
10: 89 d0 mov %edx,%eax
12: c1 f8 01 sar $0x1,%eax
15: 29 c2 sub %eax,%edx
17: 85 d2 test %edx,%edx
19: 7f f5 jg 10 <silly+0x10>
1b: 89 d0 mov %edx,%eax
1.4 翻译条件分支 goto
图3.12
1.5 循环
C提供了好几种循环结构,即 while、for和 do-while。汇编中没有相应的指令存在。
作为替代将条件测试和跳转组合起来实现循环的效果。
有趣的是,大多数汇编器根据一个循环的do- while形式来产生循环代码
其他的循环会首先转换成 do-while形式,然后编译成机器代码。
1.5.1 do while
1.5.1.1 例子:
格式: 例子:
do{ do{
body-statement a--;
}while(test-expr); }while(a > 0);
1.5.1.2 特点(至少执行一次)
至少执行一次
1.5.1.3 do-while 的实现:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
1.5.1.4 Fibonacci
int fib_dw(int n)
{
int i = 0;
int t = 0;
int val = 0;
int nval = 1;
do{
t = val + nval;
val = nval;
nval = t;
i++;
}while (i < n);
return val;
}
图3.13
1.5.2 while
while(test-expr)
body-statement
汇编代码:
loop:
t = test-expr
if (!t)
goto done
bodody-statement
goto loop
done
1.5.2.1 大多数编译器将while转成do-while
1.5.3 for
for循环的通用形式是这样的:
for (init-expr; test-expr; update-expr)
body-statement
C语言标准说明,这样一个循环的行为与下面这段使用 while循环的代码的行为一样:
init-expr;
while (test-expr){
body-statement
update-expr
}
1.6 switch 语句
switch(开关)语句提供了根据一个整数索引值进行多重分支( multiway branching)的能力。
应用:
在处理具有多种可能结果的测试时,这种语句特别有用。
跳转表:
通过使用一种称为跳转表( jump table)的数据结构使得实现更加高效。
跳转表是一个数组,表项i是个代码段的地址,这个代码段实现的是当开关索引值等于i时程序应该采取的动作。