你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

深入理解操作系统(6)第三章:程序的机器级表示(2)控制(包括:条件码CF,ZF,SF,OF/set指令/判断条件的典型指令/跳转指令jump/直接跳转和间接/循环do-while,while,for

2021/11/17 18:05:21

深入理解操作系统(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时程序应该采取的动作。