Linux汇编语言编程-汇编语言

术语

Figure 3-13. 8086 Computer (Partial Model)

reg 代表寄存器。 它可以是表 3.13 中列出的任何寄存器。

imm 代表立即数【immediate】(可以理解为字面量,常量)。 术语“立即数【immediate】”用于指代直接由十进制或十六进制表示形式给出的数值,而不是包含该值的寄存器或内存位置。

4.1 The Four Field Format

根据这种格式,每个汇编语言程序都由行【line】组成。 每行【line】由四个字段/域【field】组成。 这四个字域【field】是标签域【label field】、助记符域【mnemonic field】、操作数域名【operand field】和注释域【comment field】。

  • 标签域【label field】用于指定跳转指令【jump instruction】的目标的标签。 跳转【jump】与goto 指令相同, 4.4 节给出了跳转指令的示例。
  • 助记符域【mnemonic field】包含指令说明符【instruction specifier】。 助记符【mnemonic】的示例有 MOV, ADD, SUB 等。助记符【mnemonic】一词表明它使机器代码更加易于记住,尽管事实并非如此; 它使得机器代码无需记住。
  • 操作数域【operand field】包含指令正在操作的一个或多个对象。 如果有多个操作数,则用逗号分隔。 正如我们上面注意到的, ADD  需要两个操作数。 另一方面,JMP 则只需要一个。 有些助记符则根本不需要操作数。
  • 注释域【comment field】包含文档。 它以分号【semicolon】开头。 在任何计算机语言中,文档都很重要。 在汇编语言中,文档尤其重要,因为没有它,汇编语言程序特别难以阅读。 有时一行【line】可能只包含注释。

程序 4.1 是一个 x86 汇编语言程序的示例。 读者应该注意到它们都是四个域布局的,即使某些行【line】上的某些域【field】为空。 程序 4.1 中的许多指令都是在本书中首次出现。 这些新指令都将在本章中讨论。

;; Greatest common divisor program;MOV EDX, 0    ; 0 is the only Edlinas input portIN EAX,[DX]   ; Get the user's first inputMOV ECX, EAX  ; Get the input out of harm's wayIN EAX,[DX]   ; Get the user's second inputMOV EDX, EAX  ; Use EDX for the larger of the two inputs
ORD:   SUB EAX, ECX  ; Use EAX as a working copy of EDXJZ GCD        ; When equality is obtained we are done.JNS NXT       ; We want EDX to be larger. No swap neededMOV EAX, ECX  ; Swap EDX and ECX (Takes three MOV's)MOV ECX, EDX
NXT:   MOV EDX, EAX  ; If there was no swap then EDX = EDX-ECXJMP ORD       ; End of the loop
GCD:   MOV EAX, EDX  ; The GCD is in EDXMOV EDX, 1    ; We need EDX for the output port numberOUT [DX],EAX  ; Display the answer to the userRET

Example 4.1.  

4.2 Computers from the CPU Standpoint

计算机通常被描述为由三部分组成:CPU、内存,和输入/输出【input/output】(或 I/O 系统)。 根据此图绘制的示意图如图4-1所示。 I/O 系统包括显示器、键盘、硬盘【hard drive】、打印机【printer】、调制解调器【modem】、声卡【sound card】和 CDROM。 这些都只是 I/O 设备。 这是一个以处理器【processor】为中心的视图。 正如一个群体的成员有时会根据它们与群体的关系对人进行分类一样,这张计算机的示意图根据计算机的各个部分与 CPU 的关系来定义它们。 然而,由于汇编语言都是与 CPU 一起工作的,因此它对我们来说是一个有用的视图(即以处理器为中心)。

Figure 4-1. All Computers are Divided into Three Parts

如果我们将图中的CPU想象成 386,那么我们可以将数据总线【data bus】视为一组 32 根线连接到 CPU 上的 32 个引脚,而地址总线【address bus】则视为另一组连接到 CPU 上的32根线 。 请注意,内存和 I/O 系统以基本相同的方式连接到处理器。 如果我们想象一下在总线上流动的比特位,CPU 和内存之间的数据传输看起来几乎与 CPU 和 I/O 系统之间的数据传输完全相同。 二者之间的差异实际上归结为 CPU 上的一个特定引脚。 该引脚称为 M/IO# 引脚。 当该引脚处于逻辑 1 时,它发出内存传输信号。 当它处于逻辑 0 时,则表示 I/O 传输。 如果没有 M/IO# 线,上图实际上没有任何意义。 它还需要一条 W/R# 线来指示数据是流入还是流出处理器【processor】。 这些线称为控制线【 control wires】,是控制总线【 control bus】的一部分。

强烈建议读者将图 4-1 与图 3-11 进行比较,图 3-11 更详细地显示了来自三种不同总线的电线如何连接到存储器电路【memory circuit】。

Memory vs. I/O

在表 4.1 中,我们看到 MOV 命令的两种  imm  形式与相应的 I/O 命令进行比较。 这些命令执行 AL 寄存器同内存或 I/O 设备(位于地址 12 的)之间的传输。处理器使用 W/R# 引脚发出读与写的信号,使用 M/IO# 引脚发出内存传输还是 I/O 传输的信号 。 除此之外,交易【transaction】非常相似。

MemoryI/O
ReadMOV AL,[12]IN AL,[12]
WriteMOV [12],ALOUT [12],AL

Table 4.1. Examples of Memory and I/O Transfer Commands

x86 上的内存寻址和 I/O 寻址之间的一个显着区别是:内存地址在离开处理器之前要经过处理。 该处理有两个阶段:分段和分页。第 12 章描述了分段,第 8 章描述了分页。

I/O 系统和内存之间的另一个区别是它们不具有相同的有效地址范围。 正如第 3 章中所讨论的,有效内存地址的范围因 x86 处理器而异(这取决于处理器具有的地址引脚的数量)另一方面,有效 I/O 地址的范围在所有 x86 处理器上都是相同的。 它是从 0 到 65,535 的 16 位范围。 顺便说一句,这意味着 I/O 系统不需要连接到所有 32 条地址线,只需连接到底部 16 条即可。这个范围内的地址称为端口【ports 】或端口号【port numbers】。

许多现有的I/O设备,如磁盘驱动器、打印机和串行端口,每个设备都使用少量端口。例如,表4.2列出了标准串口使用的8个地址。(显然,表中的端口号1到4不是我们在这里考虑的特定意义上的端口号。但以下十六进制地址是这些端口号的完美示例。)在Shanley的ISA System Architecture的附录A中可以找到标准PC上使用的相对完整的端口号列表。Linux机器上使用的I/O端口可以使用命令通过/proc目录访问

许多现有的 I/O 设备(例如磁盘驱动器、打印机和串行端口),每个设备都使用少量端口。 例如,表 4.2 列出了标准串行端口【standard serial ports】使用的八个地址 (显然,表中的端口号 1 到 4 不是我们在这里考虑的特定意义上的端口号。但以下十六进制地址是这些端口号的完美示例)。标准 PC 上使用的相对完整端口号的列表可以在 Shanley 的 ISA 系统架构的附录 A 中找到。 Linux 机器上使用的 I/O 端口可以通过 /proc 目录访问到(使用如下命令):

Com PortI/O Addresses
13F8H-3FFH
22F8H-2FFH
33E8H-3EFH
42F8H-2FFH

    linuxbox$ cat /proc/ioports

内存和 I/O 传输的密切相似性并没有在 x86 汇编语言中得到充分体现,x86 汇编语言有很多涉及内存访问的命令,但控制 I/O 访问的命令却很少,例如 IN 和 OUT。 使用 MOV 和许多其他命令来访问内存将在第 6 章 6.2 节中讨论。 接下来讨论 IN 和 OUT 命令。

The IN Command

输入【input】命令将数据从 I/O 端口传输到处理器中。 该命令有六种有效形式:

    IN EAX, [DX]IN AX, [DX]IN AL, [DX]IN EAX,[imm]IN AX,[imm]IN AL,[imm]

其中 imm 是任意一字节数字【one-byte number】。 请注意,本指令遵循一般格式。 其中有效的目标寄存器是 EAX、AX 和 AL。 而端口号对应的 I/O 设备就是源【source】,其中端口号存储在 DX 寄存器中或由 imm 给出。

  • COMMAND destination, source

请注意,DX 是一个 16 位寄存器。 这是有意义的,因为端口号是一个 16 位数字。 大多数 Intel 汇编程序(包括 NASM)都不在端口号(这里说的端口号,是指的引用的值,她既可以是DX,也可以是imm)两边使用括号(这里的括号指的是[])。 但是,如果您这样做,您可以将命令  IN AL, [DX], 视为:

  • let AL = [DX]

其中 [DX] 指位于地址 DX 的数据。 在 Edlinas 中这样做是为了保持一致性。 内存命令以相同的方式使用括号。 括号成为解引用运算符,就像 C 语言中的 * 一样。 在 Edlinas 和 NASM 中,所有通过地址对内存中数据的引用都使用括号括起来的地址。 NASM 不使用括号作为 I/O 引用。 因此,在使用 NASM 时,IN 和 OUT 指令中的括号被省略。

由于 IN 命令的 imm 形式仅接受一字节的端口【one-byte ports】,因此只能使用 imm 形式访问 65.536 个端口中的底部 256 个端口。 请注意,IN 命令不是作为  IN reg, [reg] 给出的。 这是因为  IN EBX, [DX]  和 IN EAX, [CX] 等形式都是无效的(简而言之,IN

 命令只能将数据从 I/O 传输到 EAX/AX/AL中的一个)。

The OUT Command

OUT 命令将数据从处理器传输到 I/O 设备。 该命令的六种有效形式是:

    OUT [DX], EAXOUT [DX], AXOUT [DX], ALOUT [imm], EAXOUT [imm], AXOUT [imm], AL

其中 imm 是任意一字节的数字。 操作数的顺序与 IN 命令中的顺序恰好相反,因为数据流向相反。 源【source】是 EAX、AX 或 AL 寄存器,目标【target】是 I/O 设备。 同样, OUT 命令在NASM 不使用括号。

Memory Mapped I/O

事实上,当内存地址空间中的地址连接到 I/O 设备时,这称为内存映射 I/O【Memory Mapped I/O】。 标准 PC 上的视频缓冲区【video buffer】就是一个例子。 从内存地址 B8000H 开始的 4000 字节中存储的字符被输出到监视器【monitor,就是显示器】。

为 I/O 设备使用单独的地址空间并不是 I/O 寻址的唯一可行方法。 许多架构使用一组地址并简单地保留其中一些用于 I/O。

保留地址【Reserved addresses】也用于 PC 上的 ROM【只读内存,read-only memory】。 如果对只读内存进行内存写入,则处理器会将适当的写入信号发送到总线上,但不会对“存储”的数据产生任何更改。

4.3 Simple Assembly Language Programs


简单的高级语言程序通常具有三部分格式:输入数据,进行计算,然后输出结果。 使用 Edlinas,可以用 x86 汇编语言编写相同风格的简单程序。

Edlinas Ports

Edlinas 模拟的虚拟机器(假想机器)只使用两个端口号:

  • 0 是键盘输入端口
  • 1 是屏幕输出端口

键盘输入回显在屏幕左下方,输出显示在屏幕右下方。 当 IN EAX, [DX] 与 Edlinas 一起使用时,存储在 DX 寄存器中的值应为 0。当端口 0 被寻址到时,系统会提示用户输入输入值。 当使用  OUT [DX],EAX  时,DX 中存储的值应为 1。当使用端口 1 时,输出值【output value】以十进制为基数显示在屏幕右下方。 使用这两个端口可以用真正的 x86 汇编语言编写非常简单的程序并在 Edlinas 模拟器上运行。

使用 Edlinas 端口的程序不能在 DOS 或 Unix 下运行。 第 8 章第 8.4.7 节显示了如何通过调用 C 库函数scanf() 和 printf()来替换这些对模拟机的 I/O 命令。 这样,使用实际 I/O 的简单Edlinas程序就可以在 Linux 下运行了。

The RET Command

程序由操作系统启动。 当程序完成时,它必须将控制权返回给操作系统。 RET 就是这样做的。 子程序也使用 RET 将控制权返回给调用程序。 RET 使用堆栈工作,将在第 7 章 7.2 节中讨论。

Program to Add Two Numbers

我们现在拥有编写一些简单但完整的程序所需的所有命令。 这是一个 Edlinas 程序,用于将两个数字相加:

       MOV EDX, 0     ;Making all 32 bits zero makes DX zero.IN EAX, [DX]   ;User enters the first number via port zero.MOV EBX, EAX   ;Get the first number out of the way.IN EAX, [DX]   ;User enters the second number.ADD EAX, EBX   ;Add the first number to the second.MOV EDX, 1     ;The Edlinas output port is one.OUT [DX], EAX  ;The result is output to the user.RET            ;End the simulation.

Example 4.2. 

4.4 Assembler Programs with Jumps

高级语言需要循环【loop】才能完成其工作。 这些循环必须由编译器转换为汇编代码。 例如,如图 4-2 所示,要以最直接的方式执行 C 语言 while 循环,需要有条件向前跳转和无条件向后跳转。

Figure 4-2. while (test) Body;

The JMP Command

取指-执行周期【fetch-execute cycle 】通过计算下一条指令在内存中的地址来工作。 该地址通常只需转到当前指令之后的第一个地址来确定的。 然而,为了支持高级语言中的 if-then 语句或循环,就需要跳转。  JMP  命令将执行转移到由标签指定的地址。

 JMP 命令的格式为:

    JMP label

标签是由程序员创建的。 设计有助于理解程序的标签是明智的。 为此,长标签很有用(言外之意不要缩写)。 另一方面,Edlinas 使用的屏幕空间非常有限,因此超过三个字符的标签在显示时会被截断,除非它们单独位于一行。 标签域中的标签指定了跳转的目标【target】,并且通常会附加一个冒号以明确它是一个标签而不是晦涩难懂的助记符。 许多跳转命令可能指向相同的目标【target】,但任何两行都不能在其标签字段中携带相同的标签。 在以下程序中,最后一行将控制转移到前一行。 该程序继续将 2 添加到 EAX 寄存器中。 这是一个无限循环。 无限循环通常都是由于写错了。

Conditional Jumps and Flags

条件跳转是根据真值【truth value】进行的跳转。 此类决策所依据的信息包含在称为标志【flags】的一位寄存器【 one-bit registers】中。 当标志【flags】包含 1 时,称为已设置【set】。 当它包含 0 时,称为被清除【 cleared】。 两个非常重要的标志【flags】是零标志【zero flag】和符号标志【sign flag】。

       MOV EAX, 0MOV EBX, 2
XYZ:   ADD EAX, EBXJMP XYZ

Example 4.3. 

The Zero and Sign Flags

ADD EAX, EBX

ADD 和 SUB 命令不仅影响其目标寄存器【destination registers】,还影响许多标志【flags】。 假设命令被执行。 该命令的结果将保存在 EAX 寄存器中。 如果该结果为 0,则设置零标志【zero flag】。 如果不为 0,则零标志【zero flag】被清除。

ADD 指令后赋予符号标志【sign flag】的值是其结果的最高有效位的值。 如果结果是一个有符号数,且为负数,则该位为 1。 因此,如果执行 ADD EAX, EBX 命令给 EAX 一个负值,作为一个四字节有符号数,则设置【set】符号标志【sign flag】; 否则清除标志位。

The JZ, JS, JNZ, and JNS Commands

以下条件跳转命令均与JMP命令格式相同:

    JZ   labelJS   labelJNZ  labelJNS  label

每一个的操作数字段都是一个标签【label】。 这些命令的含义如下:

    JZ   Jump if the zero flag is set.JS   Jump if the sign flag is set.JNZ  Jump if the zero flag is not set.JNS  Jump if the sign flag is not set.

以下汇编语言程序说明了如何在程序中使用 JS 指令来确定两个用户输入中哪一个较大。 程序 4.4 看起来无懈可击。 事实上它有一个错误。

; If the first input is larger output 1; If the second input is larger output 2; The program uses subtraction:; B < A is true if and only if B — A is negative.; A subtraction followed by a JS does the job;MOV EDX, 0IN EAX, [DX]MOV EBX, EAX   ; The first input is now in EBXIN EAX, [DX]   ; The second input is now in EAX.SUB EBX, EAX   ; This is (first – second).JS SIB         ; Second is BiggerMOV EAX, 1     ; Otherwise First is BiggerJMP END        ; Don't drift into the other case!
SIB:   MOV EAX, 2     ;
END:   MOV EDX, 1     ; Either way now EAX is ready.OUT [DX], EAXRET

Example 4.4. 

4.5 Assembler Programs with Loops

条件跳转用于实现循环和 if-then 语句。 假设我们考虑以下程序,它使用重复加法进行乘法。 例如,要执行 7 × 5,我们可以执行 5 + 5 + 5 + 5 + 5 + 5 +5。 在程序 4.5 中,数字 1 在一行中增加,在另一行中减去。 为此有一些特殊的命令,可以占用更少的内存空间。

       ;MOV EDX, 0IN EAX,[DX]    ; First input is the multiplierMOV EBX, EAX   ; Put Multiplier in EBXIN EAX,[DX]    ; Second input is the multiplied numberMOV ECX, 0     ; Initialize the running total.
RPT:   ADD ECX, EAX   ; Do one addition.SUB EBX, 1     ; One less yet to be done.JNZ RPT        ; If that's not zero, do another.MOV EAX, ECX   ; Put the total in EAXADD EDX, 1     ;OUT [DX], EAX  ; Output the answer.RET

Example 4.5. 

The INC and DEC Commands

下面的自增和自减命令分别用于加减 1 :

    INC regDEC reg

reg 可以是 24 个通用寄存器中的任何一个。 这些命令通常用于递增或递减循环计数器【loop counter】。 程序 4.5 中,SUB EBX, 1 可以替换为 DEC EBX , ADD EDX, 1 可以替换为 INC EDX。

4.6 Signed Comparisons

Comparison-Based Jumps

高级语言经常在 if-then 语句中使用比较。 使用符号标志【sign flag】来测试减法的结果(如第 4.4 节中所做的那样)似乎是实现此测试的合理方法,因为这是一个数学事实:

  • 当且仅当 B − A 为负数时,B < A

由于符号标志【sign flag】表示减法的结果的符号,我们似乎建立在坚实的基础上。 但事实并非如此。

The Overflow Flag

假设我们使用一字节的有符号数【one-byte signed numbers】,并且我们希望测试

The Overflow Flag

是否成立。

在一字节寄存器中:

The Overflow Flag

  • 100的二进制:0110 0100
  • -50的二进制:先计算50的二进制0011 0010,取反 1100 1101,再+1取补:1100 1110
  • 再计算-(-50),先取反,0011 0001 ,再+1取补,0011 0010,对应的就是50的二进制
  • 100 + 50 = 0110 0100 + 0011 0010 = 1001 0110  
  • -106的二进制:先计算106的二进制0110 1010,取反 1001 0101 ,再+1取补:1001 0110
  • 100 + 50 的二进制,等于-106的二进制,其实发生了溢出

这会发生溢出【overflow】。 结果是负数。 符号标志【sign flag】被置位,比较判断为真! 使用 符号标志【sign flag】来确定跳转的话,最终会给出错误的答案。 但由于溢出【overflow】总是将结果的符号更改为与应有符号相反的符号,因此可以固定【fixed】跳转条件。

  • 如果没有发生溢出,则在设置了符号标志时跳转。

  • 如果发生溢出,如果未设置符号标志则跳转。

x86 处理器有一个溢出标志【overflow flag】。 它用于实现这个固定跳转条件【fixed-up jump condition】。 如果我们使用 SF 作为符号标志【sign flag】的二进制值,使用 OF 作为溢出标志【overflow flag】的二进制值,我们可以将固定跳转条件【fixed jump condition】重新表述为:

  • Jump if (SF XOR OF)

当且仅当 AL < BL,此跳转条件将在减法(如下所示)之后产生跳转:

    SUB AL, BL

有一个 x86 命令用于此跳转。 它的助记符是 JL,它代表如果小于则跳转。

The CMP Command

当减法只是为了比较而执行时,可以使用 CMP 指令来完成。 该命令以与  SUB  以完全相同的方式设置标志【flags】,但具有不会因存储不需要的结果而浪费寄存器空间的优点。 使用 JL 和 CMP  指令,我们可以将程序 4.4 重写如下:

Example 4.6. 

       ; If the first input is larger output 1; If the second input is larger output 2; The program uses subtraction:; B < A is true if and only if B − A is negative.MOV EDX, 0IN EAX, [DX]MOV EBX, EAX   ;The first input is now in EBXIN EAX, [DX]   ;The second input is now in EAX.CMP EBX, EAX   ; This is first – second.JL SIB         ; Second is BiggerMOV EAX, 1     ; Otherwise First is BiggerJMP END        ; Don't drift into the other case!
SIB:   MOV EAX, 2     ;
END:   MOV EDX, 1     ; Either way now EAX is ready.OUT [DX], EAXRET

程序 4.6 对于无符号四字节范围内的所有数字都能正确工作

More Jump Commands

CMP EAX, EBX  命令之后,如果条件  EAX < EBX  为真,则 JL 命令执行跳转。 命令:

    CMP EAX, EBXJL ABC

意味着:

  • 将 EAX 与 EBX 进行比较并
  • 如果 EAX 小于 EBX 则跳转。

这里发生的是减法和标志检查,但设计良好的语法允许我们忽略这一点。 小于【 less than 】条件并不是 CMP 命令之后方便访问的唯一条件。 这里给出了其他几个条件跳转命令的列表。

    JL    less than                  EAX < EBXJLE   less than or equal         EAX ≤ EBXJG    greater than               EAX > EBXJGE   greater than or equal      EAX ≥ EBXJE    equal                      EAX = EBXJNL   not less than              EAX ≮ EBXJNLE  not less than or equal     EAX ≰ EBXJNG   not greater than           EAX ≯ EBXJNGE  not greater than or equal  EAX ≱ EBXJNE   not equal                  EAX ≠ EBX

由于这些不同的跳转命令很容易解释,因此它们将比较命令变成了一种语法上的意外收获。

4.7 Unsigned Comparisons

4.6 节展示了如何使用标志【flag】来确定不等式【inequalities】的真值,以便这些不等式【inequalities】可以用于条件跳转。 这些所获得的条件也适用于无符号数。

但假设我们考虑同一个例子

Unsigned Comparisons

 

我们在上一节中考虑过这一点,并注意到,当用无符号数表示同样的不等式时,它就变成了:

Unsigned Comparisons

 

这种不等式具有相反的真值。 因此,在这种情况下,无符号数的“如果小于则跳转【jump if less than】”命令必须执行跳转。 但 JL  命令不会这样做。 上一节介绍的有符号跳转命令不适用于无符号数。 如果 AL = 64H = 100 且 BL = CEH = 206。则  JL  将不会执行跳转,因为它其实是作用于 100 < −50。 因此需要一个无符号的“如果小于则跳转【jump if less than】”。 

The Carry Flag

对于无符号数,不等式 A < B 比带符号数更容易测试。 由于无符号数 A − B 的减法不能超出上限,因此当且仅当它为负数时,它才超出范围。 无符号数的超出范围错误称为进位错误【 carry errors】。 因此,当且仅当减法 A − B 产生进位错误时,A < B 为真。 因此,进位错误的标志【flag】可以表示减法产生了负的结果。 x86 处理器有一个进位标志来指示这些错误。 使用 CF 来表示该进位标志的值,我们可以轻松地将用于实现无符号“如果小于则跳转【jump if less than】”命令的条件声明为“如果 CF 则跳转【jump if CF】”。

与符号标志【sign flag】、零标志【zero flag】和溢出标志【overflow flag】一样,进位标志【carry error】响应在使用 ADD 或 SUB 时发生的条件。 与其他三个标志不同,它不受INC 和 DEC  命令的影响。

Out of Range Flag Summary

 ADD 和 SUB 等算术命令是在二进制位上执行的,没有任何信息指定这些位是用于表示有符号数还是无符号数。 软件【software】有责任对此进行跟踪。

当软件使用处理器对有符号数进行算术时,它应该查阅溢出标志【overflow flag】以确定是否发生了超出范围的错误。 当软件使用处理器处理无符号数时,软件应查阅进位标志【carry sign】以检查是否存在超出范围的错误。

硬件无法知道这些数字是有符号的还是无符号的。 这并不重要。

例如,在单字节寄存器上,将 FFH 添加到 FFH 的命令可能来自处理有符号数字的软件。 在这种情况下,总和 FEH 不是问题。 软件将刚刚发生的算术视为:

Out of Range Flag Summary

这没什么问题。

另一方面,该命令可能来自使用无符号数字的软件。 在这种情况下,软件将刚刚发生的算术视为:

Out of Range Flag Summary

  • FF 255   1111 1111 
  • FF 255   1111 1111
  •      254 1 1111 1110

这就不行了。 正确答案 510 超出了单字节无符号的范围: 0-255。 因此,将 FFH 和 FFH 做加和会清除溢出标志【overflow sign】,因为在有符号的算术中,该答案是正确的(-1 + -1 = -2),并设置进位标志【carry flag】以通知软件存在无符号、超出范围的错误【unsigned, out of range error】。

再举一个例子,假设单字节寄存器接收到将 40H 和 40H 相加的命令。 总和是80H。 如果命令来自处理无符号数字的软件,那么刚刚完成的算术将显示为:

Out of Range Flag Summary

这没什么问题。

假设该命令来自使用带符号数字的软件。 那么算术看起来像这样: 

Out of Range Flag Summary

这就不行了。 正确答案 +128 超出单字节有符号范围 -128 到 +127。 硬件设置溢出标志【overflow sign】,因为这是有符号数系统中的超出范围错误,并清除进位标志【carry sign】,因为无符号系统中不存在超出范围错误。 硬件不知道软件正在使用哪个系统,因此它必须处理这两个标志。

进位标志【carry sign】和溢出标志【overflow sign】都是超出范围指示标志。 如果无符号系统中解释的算术产生了超出范围的错误,则设置进位标志【carry sign】。 如果有符号系统中解释的算术产生了超出范围的错误,则设置溢出标志【overflow sign】。

Still More Jump Commands

鉴于我们需要一个不同的“如果小于则跳转【jump if less than】”命令以用于无符号数,我们还需要一个不同的助记符。 此外,考虑到所有其他跳转条件,“大于”、“大于或等于”等,我们需要一套全新的命令和助记符。 为了使这个新集合系统化,使用 above 和 below 单词来代替短语大于【 greater than 】和小于【less than】。 因此,适用于无符号数的“如果小于则跳转【jump if less than】”命令使用助记符 JB, 表示“如果低于则跳转【jump if below】”。 这些命令的完整集如下:

    JB    below               EAX < EBXJBE   below or equal      EAX ≤ EBXJA    above               EAX > EBXJAE   above or equal      EAX ≤ EBXJNB   not below           EAX ≮ EBXJNBE  not below or equal  EAX ≰ EBXJNA   not above           EAX ≯ EBXJNAE  not above or equal  EAX ≱ EBX

 

Linux .s files

gcc 将 C 源程序转换为 a.out 文件的中间步骤之一是汇编语言文件。 该文件是编译器【compiler】本身的输出和汇编器【assembler】gas 的输入。 要查看这些汇编程序文件,可以使用 -S 开关指示 gcc 在到达此阶段后输出汇编程序文件。 此输出的文件扩展名为 .s。 例如,考虑以下 C 程序 mult.c:

Example 4.7. 

main()
{int x, y;register int a, b, c;printf("Enter a number:   ");scanf("%d", &x);printf("Multiply it by what number?  ");scanf("%d", &y);a = x;b = y;c = 0;while (b != 0){c = c + a;b = b − 1;}printf ("The result is %d.\n.",c);
}

假设输入数字 x = 5 和 y = −7。 该程序实际上输出了正确答案 -35,为了计算它,循环执行了 4,294,967,289 (2^{32} - 7)次。 注意:

每次运行总数【running total】 b 超过翻转值 2^{32} 时,就会从总数中删除 2^{32}。 这种情况发生四次(因为2^{32} 被乘了5次,乘第二/三/四/五次时,溢出进位,最终结果只剩下2^{32})。 b 的结果值为 2^{32} - 35 ,它是 −35 的二进制补码表示形式。

汇编语言编程的主要实际用途之一是对执行多次的循环进行编码,每次执行所需的时间的改进实际上会对总运行时间产生显着的影响。 在 120 MHz Pentium 的测试中,5 × -7 计算需要 110.90 秒。 要获取该程序的汇编代码,可以使用以下命令:

    linuxbox$ gcc -S mult.c

生成的文件 mult.s 是 gcc 用于生成可执行文件的汇编代码。 该汇编代码的语法与本书中使用的广泛使用的英特尔汇编语言的语法不同,但仍然可以被破译。 为此添加了注释。 第一行使用 TEST。 这是一个与CMP 相似的指令:

Example 4.8. 

.L2:testl %esi.%esi ; Is ESI = 0jne . L4        ; If not go to L4jmp . L3        ; otherwise go to L3
.L4:addl %ebx,%edi  ; Let EDI = EDI + EBXdecl %esi       ; Let ESI = ESI - 1jmp .L2         ; Go to L2
.L3:

第一行使用 TEST。 它是一个类似于 CMP 指令,将在下一章中讨论,%esi 指的是 ESI 寄存器。 与 CMP 类似,如果 ESI = 0,TEST 命令会设置零标志【zero flag】。许多命令末尾的 l 用于指定操作数的长度,在本例中为“long”,这意味着四个字节。 请注意,add 使用“COMMAND source destination”格式的操作数顺序,与本书中使用的顺序相反。

该循环由所示的六个指令组成。 如果我们注意到,我们可以通过在循环结束时测试 ESI 是否为零来跳出循环,那么我们可以将循环从 6 个指令减少到 3 个。唯一的改变是循环末尾的跳转。 修改后的文件是使用 gcc 编译的。

    linuxbox$ gcc mult.s

Example 4.9. 

.L2:testl %esi,%esijne .L4jmp .L3
.L4:addl %ebx,%edidecl %esijne .L4         ; If ESI isn't 0 go to L4
.L3:

运行生成的程序,在同一台机器上计算 5 × -7 花费了 76.14 秒。

The Pentium's Dual Pipeline

TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/221619.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的药房药品采购集中管理系统的设计与实现论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对药房药品采购信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

DevEco Studio 项目启动工程和Device Manage

DevEco Studio 项目启动工程和Device Manage 鸿蒙&#xff08;HarmonyOS&#xff09; 一、操作环境 操作系统: Windows 10 专业版 IDE:DevEco Studio 3.1 SDK:HarmonyOS 3.1 二、创建虚拟机&#xff08;Device Manage&#xff09; 鸿蒙IDE创建虚拟设备入口有2个地方&…

c⽂件操作

1.什么是⽂件&#xff1f; 磁盘上的⽂件是⽂件。但是在程序设计中&#xff0c;我们⼀般谈的⽂件有两种&#xff1a;程序⽂件、数据⽂件&#xff08;从⽂件功能的⻆度来分类的&#xff09;。 1.程序⽂件 程序⽂件包括源程序⽂件&#xff08;后缀为.c&#xff09;,⽬标⽂件&#…

操作系统笔记——概论、进程、线程(王道408)

文章目录 前言计算机系统概述OS的基本概念OS的发展历程OS的运行机制OS体系结构OS引导虚拟机 进程和线程进程和线程基础进程进程状态进程控制进程通信线程线程实现 CPU调度调度的层次进程调度细节调度算法评价指标批处理调度算法交互式调度方法 同步与互斥基本概念互斥互斥软件实…

shell实战-批量修改主机密码

1.编写执行脚本 vim host-pass.sh #!/bin/bash#配置旧的密码文件 cat >old_pass.txt <<EOF 10.36.192.182 root 123 22 10.36.192.184 root 123 22 EOF[ -f /etc/init.d/functions ] && . /etc/init.d/functions OLD_INFOold_pass.txt NEW_INFOnew_pass.txt…

设计模式(2)--对象创建(2)--生成器

1. 意图 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 2. 四种角色 指挥(Director)、抽象生成器(Builder)、具体生成器(Concrete Builder)、产品(Product) 3. 优点 3.1 可以改变一个产品的内部表示(通过定义新的生成器)。 3.2 将构…

云计算与AI融合:Amazon Connect开创客户服务智能时代

授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 在亚马逊云科技 re:Invent 2023 大会上&#xff0c;Amazon Connect…

C/C++常见面试题(二)

接前面C/C常见面试题&#xff08;一&#xff09;&#xff0c;继续巩固 目录 1 sizeof和strlen的区别 2 宏定义的陷阱 3 不使用sizeof计算出类型或者变量所占的内存的字节数 4 给定一个数判断是否其是2的N次幂 5 C/C打印所在文件、行号、函数、日期&#xff0c;时间、遵循的…

力扣131. 分割回文串(java 回溯法)

Problem: 131. 分割回文串 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 题目要求我们给出所有的回文子字符串&#xff0c;而涉及到穷举我们可以利用回溯来解决&#xff0c;另外我们也可以发现问题中涉及到元素存在重复但不可复用的特性&#xff0c;因此我们可以类…

18 5G - NR物理层解决方案支持6G非地面网络中的高移动性

文章目录 非地面网络场景链路仿真参数实验仿真结果 非地面网络场景 链路仿真参数 实验仿真结果 Figure 5 && Figure 6&#xff1a;不同信噪比下的BER和吞吐量 变量 SISO 2x2MIMO 2x4MIMO 2x8MIMOReyleigh衰落、Rician衰落、多径TDL-A(NLOS) 、TDL-E(LOS)(a)QPSK (b)16…

【知识积累】深度度量学习综述

原文指路&#xff1a;https://hav4ik.github.io/articles/deep-metric-learning-survey Problem Setting of Supervised Metric Learning 深度度量学习是一组旨在衡量数据样本之间相似性的技术。 Contrastive Approaches 对比方法的主要思想是设计一个损失函数&#xff0c;直…

Leetcode 51 N 皇后

题意理解&#xff1a; N皇后问题指的是在一个nn的棋盘上&#xff0c;防止皇后棋子&#xff0c;每行、每列、每45斜角只能有一个皇后存在。 这是一道困难的题&#xff1a;困难在于&#xff1a; 如何处理棋盘&#xff0c;如何表示棋子。 将期盼看作是2维数组&#xff0c;一行一行…

关东升老师极简系列丛书(由清华大学出版社出版)

极简系列丛书&#xff0c;编程学习新体验 在这个科技日新月异的时代&#xff0c;编程已经成为了一种必备技能。但是面对各种复杂的编程语言&#xff0c;你是否也曾感到过迷茫和困惑&#xff1f;由清华大学出版社出版的“极简系列丛书”就是为了帮助你解决这个问题。 这套丛书…

解决nuxt3引入图片报错:ReferenceError: require is not defined

现象&#xff1a; 原因&#xff1a;在nuxt3中不支持require的方式引入图片/文件等静态资源。 解决办法&#xff1a; 1. 直接在img标签中的src属性里写明图片的路径&#xff0c;但是此时src前面不能有冒号做动态绑定&#xff01;&#xff1a; src"/assets/images/loading…

【为什么POI的SXSSFWorkbook占用内存更小?】

&#x1f513;为什么POI的SXSSFWorkbook占用内存更小&#xff1f; &#x1f3c6;POI的SXSSFWorkbook&#x1f3c6;POI的SXSSFWorkbook占用内存&#x1f3c6;扩展配置行缓存限制 &#x1f3c6;POI的SXSSFWorkbook SXSSFWorkbook类是Apache POI库的一部分&#xff0c;它是一个流…

【论文阅读】LoRA: Low-Rank Adaptation of Large Language Models

code&#xff1a;GitHub - microsoft/LoRA: Code for loralib, an implementation of "LoRA: Low-Rank Adaptation of Large Language Models" 做法&#xff1a; 把预训练LLMs里面的参数权重给冻结&#xff1b;向transformer架构中的每一层&#xff0c;注入可训练的…

STM32 LCD 简单显示彩色图片

STM32 LCD 数组方式简单显示彩色图片 文章目录 STM32 LCD 数组方式简单显示彩色图片前言1、图片处理1.1 准备图片1.2 查看和调整图片大小 2、Picture2Hex软件使用3、函数代码实现3、图片显示效果4、显示图片太大会报错总结 前言 在使用LCD填充的时候发现正点原子提供了一个很好…

用于解释非目标代谢组学数据的集成深度学习框架

摘要 非定向代谢组学正获得广泛应用。数据分析的关键方面包括建模代谢网络的复杂活动、选择与临床结果相关的代谢物以及发现关键代谢途径以揭示生物学机制。数据分析中的一个关键障碍未得到很好解决&#xff0c;即数据特征与已知代谢物之间的匹配不确定性问题。鉴于实验技术的…

字符雨canvas

整体思路&#xff1a; 确定好字符雨的具体字符是什么&#xff0c;需要多少行多少列这里是写死的其实也可以用循环加随机的方式生成不一样的字符雨&#xff0c;行列也可以读一下宽度然后做一下出发算一下也行首先得有一张画布搞起&#xff0c;然后循环列数去绘画字符定时器循环…

获取Java类路径

利用System.getProperty(“java.class.path”)可以获取Java类路径&#xff08;Java class path&#xff09;。 package com.thb;import java.io.IOException;public class Test5 {public static void main(String[] args) throws IOException {System.out.println(System.getP…