- 浏览: 141080 次
文章分类
最新评论
正常执行的情况下,指令会按照顺序一条条地执行,使用跳转(jump)指令可以改变这种行为。在汇编代码中,这些跳转的目的地通常用一个标号(label)指明。在产生目标代码文件时,汇编器会确定所有带标号指令的地址,并将跳转目标(目的指令的地址)编码为跳转指令的一部分。下表列举了不同的跳转指令。
其中,jmp 指令是无条件跳转,它可以是直接跳转,即跳转目标是作为指令的一部分编码的;也可以是间接跳转,即跳转目标是从寄存器或内存位置中读出的。汇编语言中,直接跳转是给出一个标号(如“.L1”)作为跳转目标的。间接跳转的写法是“*”后面跟一个x86-64 中的寄存器与汇编操作数杂述一节中描述的内存操作数格式中的一种操作数指示符。比如,指令“jmp *%rax”用寄存器 %rax 中的值作为跳转目标,而“jmp *(%rax)”则以 %rax 中的值作为读地址,然后从内存中读出跳转目标。
表中的其他跳转指令都是有条件的——它们根据条件码的某种组合,或者跳转,或者继续执行代码序列中的下一条指令。这些指令的名字和跳转条件与 SET 指令(见汇编指令之条件码)的名字和设置条件是相匹配的,而且有些指令同样拥有同义名。注意,条件跳转只能是直接跳转。
如前所述,汇编器以及后来的链接器,可以根据标号产生跳转目标的适当编码。跳转指令有几种不同的编码,但是最常用的都是 PC 相对的(PC-relative),也就是,它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。这些地址偏移量可以编码为 1、2 或 4 个字节。第二种编码方式是给出“绝对”地址,即用 4 个字节直接指定目标。
下面代码片段是一个 PC 相对寻址的例子。它包含两个跳转:jmp 指令前向跳转到更高的地址,而 jg 指令后向跳转到较低的地址。
这里需要了解的是,使用 rep 后跟 ret 的指令组合主要是为了避免使 ret 指令成为条件跳转指令的目标。如果没有 rep 指令,当分支不跳转时,jg 指令会继续到 ret 指令。而根据 AMD 的说法,当 ret 指令通过跳转指令到达时,处理器不能正确预测 ret 指令的目的。因此这里的 rep 指令就是一种空操作,将它作为跳转目的插入,除了能使代码在 AMD 上运行得更快之外,不会改变代码的其他行为。
汇编器产生的“.o”格式的反汇编版本如下:
可见,jmp 和 jg 指令的跳转目标分别被指明为 0x8 和 0x5。反观指令的字节编码,会看到 jmp 跳转指令的目标编码为 0x03,把它加上下一条指令的地址 0x5,就得到跳转目标地址 0x8。类似地,jg 跳转指令的目标用单字节、补码表示编码为 0xf8(十进制 -8),将其加上下一条指令的地址 0xd(十进制 13),就得到跳转地址 0x5。
下面是链接后的程序反汇编版本:
可见,尽管这些指令被重定位到不同的地址,但是 jmp 和 jg 指令中的跳转目标的编码并没有变。通过使用与 PC 相对的跳转目标编码,可使指令编码更为简洁(只需要 2 个字节),而且目标代码可以不做改变就移到内存中不同的位置。
类似的例子说明,当执行 PC 相对寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。这种惯例可以追溯到早期的实现,当时的处理器会将更新程序计数器作为执行一条指令的第一步。
使用控制的条件转移是实现条件操作的传统方法:当条件满足时,程序沿着一条执行路径执行,不满足时则走另一条路径。这种机制简单而通用,但在现代处理器上,它可能会非常低效。一种替代的策略是使用数据的条件转移。这种方法会计算一个条件操作的两种结果,然后再根据条件是否满足从中选取一个。尽管这种策略只有在一些受限制的情况中才可行,但一旦可行,就可以使用一条简单的条件传送指令来实现它。条件传送指令更符合现代处理器的功能特性。
为了理解为什么基于条件数据传送的代码会比基于条件控制转移的代码性能要好,需要了解现代处理器通过使用流水线(pipelining)来获得高性能。在流水线中,一条指令的处理要经过一系列的阶段,每个阶段执行所需操作的一小部分(例如,从内存取指令、确定指令类型、从内存读数据、执行算术运算、向内存写数据,以及更新程序计数器)。这种方法通过重叠连续指令的步骤来获得高性能,例如,在取一条指令的同时,执行它前面一条指令的算术运算。而要做到这一点,就要求能够事先确定要执行的指令序列,这样才能保持流水线中充满待执行的指令。当机器遇到条件跳转时(也称为“分支”)时,只有当分支条件求值完成后,才能决定分支往哪边走。处理器采用非常精密的分支预测逻辑来猜测每条跳转指令是否会执行。只要它的猜测还比较可靠(现代微处理器设计试图达到 90% 以上的成功率),指令流水线中就会充满着指令。另一方面,如果错误预测一个跳转,就要求处理器丢掉它为该跳转指令后所有指令已做的工作,然后再开始用从正确位置处起始的指令去填充流水线。这样一个错误预测通常会招致很严重的惩罚,浪费大约 15~30 个时钟周期,导致程序性能严重下降。而对于编译出来使用条件传送的代码而言,无论测试的数据是什么,所需的时间都是大约 8 个时钟周期。因为控制流不依赖于数据,所以这使得处理器更容易保持流水线是满的。
下表列举了 x86-64 上一些可用的条件传送指令。
每条指令都有两个操作数:源寄存器或者内存地址 S,和目的寄存器 R。与不同的 SET 和跳转指令一样,这些指令的结果取决于条件码的值。源值可以从内存或者源寄存器中读取,但只有在指定的条件满足时,才会被复制到目的寄存器中。源和目的的值可以是 16 位、32 位或 64 位长,但不支持单字节的条件传送。无条件指令的操作数的长度显示地编码在指令名中(如 movw 和 movl),汇编器可以从目标寄存器的名字推断出条件传送指令的操作数长度,所以对所有的操作数长度,都可以使用同一个的指令名字。
不同于条件跳转,处理器无需预测测试的结果就可以执行条件传送。处理器只是读源值,检查条件码,然后要么更新目的寄存器,要么保持不变。考虑下面的条件表达式和赋值的通用形式:
v = test-expr ? then-expr : else-expr;
如果用条件控制转移的标准方法来编译这个表达式会得到如下形式:
if(!test-expr)
goto false;
v = then-expr;
goto done;
false:
v = else-expr;
done:
/* other */
这段代码结合条件跳转和无条件跳转来保证只有一个 then-expr 序列或者 else-expr 序列执行。
而在基于条件传送的代码中,会对 then-expr 和 else-expr 都求值,最终值的选择基于对 test-expr 的求值。代码描述大致如下:
v = then-expr;
ve = else-expr;
if(!test-expr) v = ve;
这里的最后一条语句就是用条件传送实现的。
不过不是所有的条件表达式都可以用条件传送来编译。比如,如果 then-expr 和 else-expr 这两个表达式中的任意一个可能产生错误条件或者副作用,就会导致非法的行为。作为说明,考虑下面这个 C 函数。
乍一看,这段代码似乎很适合被编译成使用条件传送,如下面的汇编代码所示。
不过,这个实现是非法的,因为即使当测试为假时,movq 指令对 xp 的间接引用还是发生了,从而导致一个间接引用空指针的错误,所以必须用分支代码来编译这段代码。
此外,使用条件传送也不总是会提高代码的效率。例如,如果 expr-expr 和 else-expr 的求值需要大量的计算,则当相应的条件不满足时,这些工作就白费了。编译器必须考虑浪费的计算和由于分支预测错误所造成的性能处罚之间的相对性能。对 GCC 编译器来说,通常只有当两个表达式都很容易计算时,比如表达式分别都只是一条加法指令,它才会使用条件传送。
其中,jmp 指令是无条件跳转,它可以是直接跳转,即跳转目标是作为指令的一部分编码的;也可以是间接跳转,即跳转目标是从寄存器或内存位置中读出的。汇编语言中,直接跳转是给出一个标号(如“.L1”)作为跳转目标的。间接跳转的写法是“*”后面跟一个x86-64 中的寄存器与汇编操作数杂述一节中描述的内存操作数格式中的一种操作数指示符。比如,指令“jmp *%rax”用寄存器 %rax 中的值作为跳转目标,而“jmp *(%rax)”则以 %rax 中的值作为读地址,然后从内存中读出跳转目标。
表中的其他跳转指令都是有条件的——它们根据条件码的某种组合,或者跳转,或者继续执行代码序列中的下一条指令。这些指令的名字和跳转条件与 SET 指令(见汇编指令之条件码)的名字和设置条件是相匹配的,而且有些指令同样拥有同义名。注意,条件跳转只能是直接跳转。
如前所述,汇编器以及后来的链接器,可以根据标号产生跳转目标的适当编码。跳转指令有几种不同的编码,但是最常用的都是 PC 相对的(PC-relative),也就是,它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。这些地址偏移量可以编码为 1、2 或 4 个字节。第二种编码方式是给出“绝对”地址,即用 4 个字节直接指定目标。
下面代码片段是一个 PC 相对寻址的例子。它包含两个跳转:jmp 指令前向跳转到更高的地址,而 jg 指令后向跳转到较低的地址。
movq %rdi, %rax jmp .L2 .L3: sarq %rax .L2: testq %rax, %rax jg .L3 rep; ret
这里需要了解的是,使用 rep 后跟 ret 的指令组合主要是为了避免使 ret 指令成为条件跳转指令的目标。如果没有 rep 指令,当分支不跳转时,jg 指令会继续到 ret 指令。而根据 AMD 的说法,当 ret 指令通过跳转指令到达时,处理器不能正确预测 ret 指令的目的。因此这里的 rep 指令就是一种空操作,将它作为跳转目的插入,除了能使代码在 AMD 上运行得更快之外,不会改变代码的其他行为。
汇编器产生的“.o”格式的反汇编版本如下:
0: 48 89 f8 mov %rdi, %rax 3: eb 03 jmp 8 <loop+0x8> 5: 48 d1 f8 sar %rax 8: 48 85 c0 test %rax, %rax b: 7f f8 jg 5 <loop+0x5> d: f3 c3 repz retq
可见,jmp 和 jg 指令的跳转目标分别被指明为 0x8 和 0x5。反观指令的字节编码,会看到 jmp 跳转指令的目标编码为 0x03,把它加上下一条指令的地址 0x5,就得到跳转目标地址 0x8。类似地,jg 跳转指令的目标用单字节、补码表示编码为 0xf8(十进制 -8),将其加上下一条指令的地址 0xd(十进制 13),就得到跳转地址 0x5。
下面是链接后的程序反汇编版本:
4004d0: 48 89 f8 mov %rdi, %rax 4004d3: eb 03 jmp 4004d8 <loop+0x8> 4004d5: 48 d1 f8 sar %rax 4004d8: 48 85 c0 test %rax, %rax 4004db: 7f f8 jg 4004d5 <loop+0x5> 4004dd: f3 c3 repz retq
可见,尽管这些指令被重定位到不同的地址,但是 jmp 和 jg 指令中的跳转目标的编码并没有变。通过使用与 PC 相对的跳转目标编码,可使指令编码更为简洁(只需要 2 个字节),而且目标代码可以不做改变就移到内存中不同的位置。
类似的例子说明,当执行 PC 相对寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。这种惯例可以追溯到早期的实现,当时的处理器会将更新程序计数器作为执行一条指令的第一步。
使用控制的条件转移是实现条件操作的传统方法:当条件满足时,程序沿着一条执行路径执行,不满足时则走另一条路径。这种机制简单而通用,但在现代处理器上,它可能会非常低效。一种替代的策略是使用数据的条件转移。这种方法会计算一个条件操作的两种结果,然后再根据条件是否满足从中选取一个。尽管这种策略只有在一些受限制的情况中才可行,但一旦可行,就可以使用一条简单的条件传送指令来实现它。条件传送指令更符合现代处理器的功能特性。
为了理解为什么基于条件数据传送的代码会比基于条件控制转移的代码性能要好,需要了解现代处理器通过使用流水线(pipelining)来获得高性能。在流水线中,一条指令的处理要经过一系列的阶段,每个阶段执行所需操作的一小部分(例如,从内存取指令、确定指令类型、从内存读数据、执行算术运算、向内存写数据,以及更新程序计数器)。这种方法通过重叠连续指令的步骤来获得高性能,例如,在取一条指令的同时,执行它前面一条指令的算术运算。而要做到这一点,就要求能够事先确定要执行的指令序列,这样才能保持流水线中充满待执行的指令。当机器遇到条件跳转时(也称为“分支”)时,只有当分支条件求值完成后,才能决定分支往哪边走。处理器采用非常精密的分支预测逻辑来猜测每条跳转指令是否会执行。只要它的猜测还比较可靠(现代微处理器设计试图达到 90% 以上的成功率),指令流水线中就会充满着指令。另一方面,如果错误预测一个跳转,就要求处理器丢掉它为该跳转指令后所有指令已做的工作,然后再开始用从正确位置处起始的指令去填充流水线。这样一个错误预测通常会招致很严重的惩罚,浪费大约 15~30 个时钟周期,导致程序性能严重下降。而对于编译出来使用条件传送的代码而言,无论测试的数据是什么,所需的时间都是大约 8 个时钟周期。因为控制流不依赖于数据,所以这使得处理器更容易保持流水线是满的。
下表列举了 x86-64 上一些可用的条件传送指令。
每条指令都有两个操作数:源寄存器或者内存地址 S,和目的寄存器 R。与不同的 SET 和跳转指令一样,这些指令的结果取决于条件码的值。源值可以从内存或者源寄存器中读取,但只有在指定的条件满足时,才会被复制到目的寄存器中。源和目的的值可以是 16 位、32 位或 64 位长,但不支持单字节的条件传送。无条件指令的操作数的长度显示地编码在指令名中(如 movw 和 movl),汇编器可以从目标寄存器的名字推断出条件传送指令的操作数长度,所以对所有的操作数长度,都可以使用同一个的指令名字。
不同于条件跳转,处理器无需预测测试的结果就可以执行条件传送。处理器只是读源值,检查条件码,然后要么更新目的寄存器,要么保持不变。考虑下面的条件表达式和赋值的通用形式:
v = test-expr ? then-expr : else-expr;
如果用条件控制转移的标准方法来编译这个表达式会得到如下形式:
if(!test-expr)
goto false;
v = then-expr;
goto done;
false:
v = else-expr;
done:
/* other */
这段代码结合条件跳转和无条件跳转来保证只有一个 then-expr 序列或者 else-expr 序列执行。
而在基于条件传送的代码中,会对 then-expr 和 else-expr 都求值,最终值的选择基于对 test-expr 的求值。代码描述大致如下:
v = then-expr;
ve = else-expr;
if(!test-expr) v = ve;
这里的最后一条语句就是用条件传送实现的。
不过不是所有的条件表达式都可以用条件传送来编译。比如,如果 then-expr 和 else-expr 这两个表达式中的任意一个可能产生错误条件或者副作用,就会导致非法的行为。作为说明,考虑下面这个 C 函数。
long cread(long *xp){ return (xp ? *xp : 0); }
乍一看,这段代码似乎很适合被编译成使用条件传送,如下面的汇编代码所示。
; long cread(long *xp) ; Invalid implementation of function cread ; xp in register %rdi cread: movq (%rdi), %rax ; v = *xp,间接引用 movl $0, %edx ; Set ve = 0 testq %rdi, %rdi ; Test xp cmove %rdx, %rax ; If xp == 0, v = ve ret ; return v
不过,这个实现是非法的,因为即使当测试为假时,movq 指令对 xp 的间接引用还是发生了,从而导致一个间接引用空指针的错误,所以必须用分支代码来编译这段代码。
此外,使用条件传送也不总是会提高代码的效率。例如,如果 expr-expr 和 else-expr 的求值需要大量的计算,则当相应的条件不满足时,这些工作就白费了。编译器必须考虑浪费的计算和由于分支预测错误所造成的性能处罚之间的相对性能。对 GCC 编译器来说,通常只有当两个表达式都很容易计算时,比如表达式分别都只是一条加法指令,它才会使用条件传送。
发表评论
-
浮点运算指令
2019-05-22 23:13 1568上一节介绍了浮点数与各种数值类型之间的相互转换 ... -
浮点数类型转换指令
2019-05-15 22:37 1691在浮点寄存 ... -
浮点寄存器概述
2019-05-14 22:31 2568本文介绍的浮点寄存器是基于 AVX2(Adva ... -
汇编指令之条件码
2019-04-08 21:05 2336在系统底层,除了整数寄存器,CPU 还维护着一 ... -
汇编指令之算术和逻辑操作指令
2019-03-28 22:16 1347下表是 x86-64 ... -
汇编指令之数据传送指令
2019-03-25 21:28 1274在x86-64 中的 ... -
x86-64 中的寄存器与汇编操作数杂述
2019-03-20 21:45 981Intel 中常用 ... -
hello 程序执行背后的故事
2018-12-26 21:48 606源文件 hello. ... -
linux启动服务概述
2017-04-08 02:43 396传统的linux中定义了七个运行级,分别如下: ... -
unix限制
2017-04-04 16:08 572UNIX系统实现定义了很多幻数和常量,其中有很 ... -
linux引导加载程序--GRUB
2017-04-04 04:22 622linux世界里有两种 ... -
存储器映射
2016-06-13 00:12 550注:本文摘自《深入理解计算机操作系统》第九章--虚拟存 ... -
虚拟存储器对存储器管理的作用
2016-06-10 16:00 692注:本文中的大部分内容均是摘录自《深入理解计算机系统》一书,权 ... -
信号处理问题
2016-06-03 08:31 560注:本文摘自《深入理解计算机系统》第8章 --- 异常控制流。 ... -
僵尸进程
2016-05-23 23:57 358在解释僵尸进程的概念之前,我们得先了解这样的一个事实: 一个进 ... -
程序优化之存储器别名使用
2016-05-20 08:55 793说明:本文示例摘自《深入理解计算机系统》第五章----优化程序 ... -
条件变量基本概念与原理(转载)
2016-05-20 08:54 1567对于条件变量,我一直感到很困惑,搞不清其与互斥锁到底有啥区别, ... -
CPU与磁盘的交互过程
2016-05-19 09:05 1829对于计算机系统底层技术,想必很多人都和我一样不太了解,最近在学 ... -
存储器层次结构中基本的缓存原理
2016-05-19 09:00 663对于操作系统,我们知道,越靠近CPU的存储器,其存储速度就会越 ... -
异常处理
2016-05-19 00:29 422我知道很多人都知道异常处理,但可能对其底层并不太了解,现在我们 ...
相关推荐
- 查找指令:用户可以通过关键词搜索特定的汇编指令,如`MOV`(数据传输)、`ADD`(加法)、`JMP`(无条件跳转)等。 - 参数解释:查询器会解释指令的操作数类型和数量,以及它们在指令中的作用。 - 示例代码:...
汇编指令查询器是一款工具,帮助程序员和计算机科学学习者快速查找并理解各种汇编指令的含义和用法。 汇编语言的核心在于它的指令集,通常包括数据处理、算术运算、逻辑操作、转移控制、输入输出等类别。下面将详细...
条件跳转指令通常基于PSW中的标志位来决定是否执行跳转。 4. I/O访问指令:如读写内部或外部寄存器(MOV)、加载和存储(LDI, STI)等,用于与STM8S的外设进行通信。 5. 控制指令:包括设置和清除位(SETB, CLR)...
《汇编指令查询工具——深入理解与应用》 汇编语言是计算机科学的基础之一,它是一种低级编程语言,直接对应于机器指令。对于计算机硬件的直接操作,汇编指令起着至关重要的作用。本文将围绕"汇编指令查询工具"进行...
RH850 D1汇编指令作为RH850系列微控制器的核心组成部分之一,其强大的功能和灵活性使其成为实现高性能、低功耗应用的理想选择。通过对汇编指令的深入了解和掌握,开发者能够更好地利用微控制器的各项特性,从而提高...
每条汇编指令都有其特定的功能,例如,`MOV`指令用于在寄存器或内存位置之间移动数据,`ADD`指令用于执行加法操作,`SUB`用于减法,`JMP`用于无条件跳转,`CALL`用于调用子程序,`RET`则用于返回主程序等。...
《汇编指令查询器——揭开汇编语言的神秘面纱》 在计算机科学的世界里,汇编语言是一种底层编程语言,它与机器语言紧密相连,是程序员与硬件直接沟通的桥梁。汇编指令查询工具,如"汇编指令查询器",是学习和理解...
由于给定的文件信息未能提供具体的汇编指令细节,以下将基于现有的知识,对ARM Cortex-M0微控制器核心可能包含的汇编指令进行汇总,并简单介绍这些指令的特点。 ARM Cortex-M0核心支持的汇编指令大致可以分为以下几...
5. **指令顺序**:汇编语言程序的执行顺序严格遵循从上到下的顺序,除非有跳转指令(如JMP、CALL)改变流程。 6. **跳转与循环**:JMP、CALL等指令用于控制程序流程,实现条件分支和循环。正确设置条件码和使用比较...
关于汇编跳转指令 汇编语言中,跳转指令是控制程序流程的重要组成部分。跳转指令可以根据条件码标志位的状态来决定程序的执行方向。本文将详细讲解汇编语言中的跳转指令,包括状态寄存器PSW、直接标志转移、间接...
"DSP C6000系列常用汇编指令大全" DSP C6000系列常用汇编指令大全是面向DSP C6000系列芯片的汇编指令大全,旨在供大家在编写汇编时进行参考,特别是在CCS中编C与汇编混合编程时。该文档包含了DSP C6000系列芯片的...
《汇编指令大全+很全的汇编指令.pdf》是一份详尽的汇编语言学习资料,旨在帮助读者深入理解并快速掌握汇编语言的基本概念和指令系统。汇编语言,作为计算机科学的基础,是直接对应机器语言的一种编程语言,每一个...
根据给定的文件信息,我们将深入探讨瑞萨MCU(微控制器)的汇编指令手册,这是一份针对RENESAS 16位单片微型计算机M16C/60、M16C/20和M16C/Tiny系列的软件手册。这份手册不仅提供了对汇编语言指令集的全面概述,还...
在IT领域,汇编语言是一种低级编程语言,它与计算机...总的来说,汇编语言是计算机科学的基础之一,理解并掌握汇编指令对于深入理解计算机的工作原理至关重要。而汇编指令字典则为这一学习过程提供了必要的支持和帮助。
- 使用 BX 指令跳转并切换处理器状态 3. ADS 工具的使用方法 - 使用 ADS 工具来建立工程 - 使用 ADS 工具来编译和调试程序 - 使用 AXD 来进行软件仿真调试 4. 汇编指令编程的应用 - 使用汇编指令编程来实现...
ARM汇编指令集是嵌入式系统开发中的基础,它是一种低级编程语言,用于直接控制计算机硬件。本文将深入探讨ARM汇编指令集的主要概念、语法和常见指令,帮助你更好地理解和应用这一关键技术。 首先,理解ARM架构是至...
《汇编指令查询器:深入理解与应用》 在计算机科学的世界里,汇编语言作为底层编程语言,扮演着至关重要的角色。它是一种符号化的机器语言,每一条指令都直接对应于计算机硬件上的操作,因此对汇编指令的熟练掌握是...
1. **MOV(移动)**:这是最基本的指令之一,用于将数据从一个位置复制到另一个位置。例如,`MOV AX, BX` 将BX寄存器的内容复制到AX寄存器。 2. **ADD(加法)**:此指令执行两个操作数的加法运算。例如,`ADD AX, ...
《汇编指令查询器:深入理解与应用》 在计算机科学的世界里,汇编语言作为最底层的编程语言,扮演着至关重要的角色。它是一种直接对应机器语言的编程方式,每一行汇编指令都能直接转化为计算机可以执行的二进制代码...
"汇编指令查询工具"正是为帮助程序员和计算机科学爱好者理解和使用汇编指令而设计的实用工具。 这款工具提供了便捷的方式,使得用户能够查询到各种常见的汇编指令。在学习和使用汇编语言时,理解并熟练运用汇编指令...