当前位置: 首页 > news >正文

《操作系统真象还原》第3章 完善MBR【3.1 — 3.2】

 

目录

引用与说明

 3.1、地址、section、vstart 浅尝辄止

1、什么是地址

2、什么是 section【汇编】

3、什么是 vstart【汇编】

3.2、CPU 的实模式

1、CPU 工作原理【重要】

2、实模式下的寄存器

4、实模式下 CPU 内存寻址方式

5、栈到底是什么玩意儿

6 ~ 8  无条件转移【汇编】

6、实模式下的 ret

7、实模式下的 call

8、实模式下的 jmp

9、标志寄存器 flags

10、有条件转移

其他问题


引用与说明

  • 《操作系统真相还原》,作者:郑钢
  • 原书讲解得非常详细,但是战线拉得太长,反而容易找不到重点。所以,编写本篇文章旨在让读者抓住重点,也可以作为原章节的导读。3.1 和 3.2 节在讲一些概念和汇编操作,如果读者对这些概念比较熟悉,完全可以跳过本篇,并不影响实操。从 3.3 节开始进行实际操作,将在下一篇讲解。
建议略读章节
3.3.3、实模式下内存分段由来


 3.1、地址、section、vstart 浅尝辄止

1、什么是地址

地址只是数字,描述各种符号在源程序中的位置,是各符号相对于文件开头的偏移量。编译器给各符号分配地址,这些符号在空间上彼此相邻,连续分布。

​​

地址等于上一个地址 + 上一个地址处的内容的长度。例如地址列第二行的 3 等于 “上一个地址 0” + “上一个地址 0 处的内容:B80000 的长度 3”。“B80000” 是一个 6 位的十六进制数。每两个十六进制数字对应一个字节,“B8” 是第一个字节,“00” 是第二个字节,“00” 是第三个字节。“B80000” 的长度是 3 个字节。“长度是 3”这个描述,它通常指的是这个十六进制数在内存中占用的字节数。

2、什么是 section【汇编】

section 称为节,功能是在程序中宣称一个区域,为了让程序员在逻辑上将程序划分成几个部分。

关键字 section 没有对程序中的地址产生任何影响,section 中的数据的地址依然是相对于整个文件的顺延,仅仅是在逻辑上让开发人员梳理程序之用。

3、什么是 vstart【汇编】

vstart 是虚拟起始地址,section 用 vstart = 来修饰后,被赋予一个虚拟起始地址,它被用来计算在该 section 内的所有引用地址。虚拟起始地址 != x86 CPU 开启分页后的虚拟地址。

vstart 按照开发人员的意愿安排新的起始地址,不再以文件开头 0 为起始,其地址若超过文件大小则不会落在文件内,也就是说根据此地址在文件中是找不到相关数据的,文件中的所有符号都不在这个地址上,所以是虚拟的。

用 vstart 的时机是:预先知道我的程序将来被加载到某地址处。第二章代码中 mbr 用 vstart = 0x7c00 来修饰,是开发人员知道 mbr 要被加载器(BIOS)加载到物理地址 0x7c00,mbr 中后续的物理地址都是 0x7c00 +。



3.2、CPU 的实模式

实模式是指 8086 CPU 的寻址方式、寄存器大小、指令用法等,是用来反应 CPU 在该环境下如何工作的概念,并不是单指某一方面的设置。CPU 的唯一任务就是执行指令。

1、CPU 工作原理【重要】

程序被加载到内存后,指令这时都在内存中了。控制单元要取下一条待运行的指令,该指令的地址在程序计数器 PC 中,在 x86 CPU 上,程序计数器就是 cs:ip。控制单元从 cs 寄存器中读取段基址,从 ip 寄存器读取偏移量。读取 ip 寄存器后,将此地址送上地址总线,CPU 根据此地址便得到了指令,并将其存入到指令寄存器 IR 中。

指令译码器根据指令格式检查指令寄存器中的指令,先确定操作码是什么,再检查操作数类型,若是在内存中,就将相应操作数从内存中取回放入自己的存储单元,若操作数是在寄存器中就直接用,免了取操作数这一过程。

操作码有了,操作数也齐了,操作控制器给运算单元下令,开工,于是运算单元便真正开始执行指令了。运算单元执行指令,并将结果存放在指定的寄存器或内存。

在执行当前指令的同时,在不跨段的情况下,CPU 以 ”当前 IP 寄存器中的值 + 当前执行指令的机器码长度“ 的和作为新的代码段内偏移地址,将其存入 IP 寄存器。如果下一条指令需要跨段访问,还要加载新的段基址到 CS 寄存器。这样便得到了下一条指令的地址。接着控制单元又要取下一条指令了,流程回到了本阶段开头。

2、实模式下的寄存器

寄存器是使用触发器实现的,工作速度极快。

CPU 中的寄存器分类
对程序员不可见的寄存器,无法直接使用,但部分可以被程序员初始化
全局描述符表寄存器 GDTR
中断描述符表寄存器 IDTR
局部描述符表寄存器 LDTR
任务寄存器 TR
控制寄存器 CR0~3
指令指针寄存器 IP
标志寄存器 flags
调试寄存器 DR0~7
对程序员可见的寄存器,进行汇编语言设计时,能直接操作的就是这些寄存器
段寄存器见书【P74、P75】,这里不用太纠结,讲到寻址方式时就知道运用在哪了
通用寄存器非常详细,需要仔细阅读,见书【P75、P76】

 【注】:图 3-3 和 表 3-2 中的各个寄存器的名字要留个印象,接下来讲内存寻址时各个寄存器会频繁出现 ,讲到哪个寄存器时请一定回来查找这几张图和表。

4、实模式下 CPU 内存寻址方式

寻址指的是 CPU 在寻找 “数” 的地址,找到 “数” 的所在地,从哪来,往哪去。这个 “数” 可以是源操作数,也可以是目的操作数。Intel 汇编语言语法是 “指令目的操作数,源操作数”。

每一种寻址方式对应一种电路实现,增加一种寻址方式,会增加硬件电路的复杂性,所以寻址方式是有限的。

  • 寄存器寻址

寄存器寻址是最直接的寻址方式,它是指 “数” 在寄存器中,直接从寄存器中拿数据就行了。

; 用 mul 指令实现 0x10 * 0x9
mov ax, 0x10 ; 第一条命令是将 0x10 存入 ax 寄存器
mov dx, 0x9  ; 第二条命令是将 0x9 存入 dx
mul dx       ; 第三条指令是求 ax 和 dx 的乘积,乘积的高16位在 dx 寄存器,低16位在 ax 寄存器

以上三条指令都是寄存器寻址。只要牵扯到寄存器的操作,无论其是源操作数,还是目的操作数,都是寄存器寻址。上面的第一、二条指令,它们的源操作数都是立即数,所以也属于立即数寻址。

  • 立即数寻址

立即数就是常数。指令由操作码和操作数组成,得到一个数往往不容易。这个数要么在寄存器中,要么在内存中,都是间接给出的,所以得到数就要花费一些 CPU 周期。如果操作数 "直接" 存在指令中,直接拿过来,立即就能用了。为了突显 "立即就能用" 的高效率,此数便称为立即数。立即数免去了找数的过程。

mov ax, 0x18           ; 立即数寻址
mov ds, ax             ; 寄存器寻址
mov ax, macro_selector ; 立即数寻址
mov ax, label_start    ; 立即数寻址

第一条指令中的源操作数 0x18 是立即数,目的操作数 ax 是寄存器,所以它既是立即数寻址,也是寄存器寻址。第二条指令中,源操作数和目的操作数都是寄存器,所以纯粹是寄存器寻址。第三条指令的源操作数 macro_selector 是个宏,第四条指令的源操作数 label_start 是个标号,这两个在编译阶段会转换为数字,最终可执行文件中的依然是立即数。

  • 内存寻址

以上两种寻址方式,操作数一个是在寄存器中,一个是在指令中直接给出。它们都不在内存中。操作数在内存中的寻址方式称为内存寻址。

由于访问内存是用 "段基址:段内偏移地址" 的形式,此形式只用在内存访问中。默认情况下数据段寄存器是 DS,即段基址已经有了,只要再给出段内偏移地址就可以访问内存了,最终起决定作用的、有效的是段内偏移地址,所以段内偏移地址称为有效地址。

“[x]” 表示取了 x 的地址,其中 x 通常指的是一个寄存器或一个计算出的地址。

  • 直接寻址(属于内存寻址)

直接寻址,就是将直接在操作数中给出的数字作为内存地址,通过中括号的形式告诉CPU,取此地址中的值作为操作数。

mov ax, [0x1234]
mov ax, [fs:0x5678]

0x1234 是段内偏移地址,DS 是数据段寄存器的默认段寄存器【图 3-3】。这条指令是将内存地址 DS:0x1234 处的值写入 ax 寄存器。段基址*16 变成 20 位地址后,再加上段内偏移地址 0x1234,结果还是 20 位地址。

第二条指令中,由于使用了段跨越前缀 fs,这意味着该指令使用 FS 段寄存器而不是默认的 DS。最终的内存地址是 fs 寄存器的值*16 + 0x5678,CPU 到此内存地址取值再存入 ax 寄存器。

【注】:不要和立即数寻址混了,立即数寻址中的数字是直接拿来就用作操作数了,直接寻址中的数字是用来进一步寻址的。

  • 基址寻址(属于内存寻址)

基址寻址,就是在操作数中用 bx 寄存器或 bp 寄存器作为地址的起始,地址的变化以它为基础。用寄存器作为内存寻址,在实模式下必须用 bx 或 bp 寄存器。保护模式下,基址寄存器可选择的很多,可以是全部的通用寄存器。

bx 寄存器的默认段寄存器是 DS。例如 "add word[bx], 0x1234" 这条指令将 0x1234 加上内存地址 ds:bx 处的值后再存入内存地址 ds:bx 中。这条指令用到了立即数寻址和内存基址寻址两种方式。

add word[bx], 0x1234

bp 寄存器的默认段寄存器是 SS,即 bp 和 sp 都是栈的偏移地址,即 bp 是用来访问栈的。

【Q】为什么已经有了 sp 寄存器来 "专门" 访问栈,还要再单独准备个 bp 呢?

【A】

访问栈有两种方式,一种是把栈当作 “栈” 来使用,也就是用 push 和 pop 指令操作栈,sp 寄存器作为栈顶指针,相当于栈中数据的游标,这是专门给 push 指令和 pop 指令做导航用的寄存器,push 指令往哪个内存压入数据,pop 将哪个地址的数据弹出栈,都要看 sp 的值是多少。但这样我们只能访问到栈顶,即 sp 指向的地址,没有办法直接访问到栈底和栈顶之间的数据。

; 实模式下,CPU 字长是16,所以实模式下的 push 指令默认情况下是压入2字节的数据。执行 push ax:
sub sp, 2  ; 先将 sp 的值减去
mov sp, ax ; 再将 ax 的值 mov 到新的 sp 指向的内存
; 实模式下 pop 指令。执行 pop ax:
mov ax,[sp] ; 先将 sp 指向的值 mov 到 ax
add sp,2    ; 再将 sp 的指针+2 

很多时候,我们需要读写栈中的数据,即需要把栈当成普通数据段那样访问。举个需要直接写栈的例子,比如标志寄存器 eflags 无法直接修改,只能用 pushf 指令把 eflags 寄存器的内容压到栈中,我们在栈中修改完后,再用 popf 把它弹回到 eflags 中。处理器为了让开发人员方便控制栈中数据,提供了这种把栈当成数据段来访问的方式,可以用寄存器 bp 来给出栈中偏移量,所以 bp 默认的段寄存器就是 SS,这样就可以通过 SS:bp 的方式把栈当成普通的数据端来访问了。

【注】: “栈中保存局部变量和函数参数” 的例子非常详细,见书【P80、P81】。

  • 变址寻址(属于内存寻址)

变址寻址和基址寻址类似,只是寄存器由 bx、bp 换成了 si 和 di。si 是指源索引寄存器,di 是指目的索引寄存器。两个寄存器的默认段寄存器也是 ds。

mov [di], ax        ; 将寄存器 ax 的值存入 ds:di 指向的内存
mov [si+0x1234], ax ; 变址中也可以加个偏移量

变址寻址主要是用于字符搬运方面的指令,这两个寄存器在很多指令中都要成对使用,如 movsb,movsw,movsd 等。

  • 基址变址寻址(属于内存寻址)

名字上看,这是基址寻址和变址寻址的结合,即基址寄存器 bx 或 bp 加一个变址寄存器 si 或 di。

了解即可,项目中没用到。

mov [bx+di], ax ; 将 ax 中的值送入以 ds 为段基址,bx+di 为偏移地址的内存
add [bx+si], ax ; 将 ax 与[ds:bx+si]处的值相加后存入内存[ds: bx+si]

 【汇编】:mul 指令说明

mul dx:这条指令将 ax 寄存器中的值与 dx 寄存器中的值相乘。mul 指令是无符号乘法指令,它会用 ax 寄存器中的值作为被乘数,用 dx 寄存器中的值作为乘数。乘法结果的低 16 位将存放在 ax 寄存器中。乘法结果的高16位将存放在 dx 寄存器中。

  • ax 寄存器的值:0x10 = 16
  • dx 寄存器的值:0x9 = 9
  • 乘积 = 16 × 9 = 144,十六进制表示是 0x90。
  • ax 存放乘积的低 16 位,因此 ax 寄存器中的值为 0x90 (144)。
  • dx 存放乘积的高 16 位。由于 144 的乘积在 16 位范围内没有高位,所以 dx 寄存器中的值将为 0x0 (0)。

 【汇编】:word 数据类型伪指令【P89】

数据类型伪指令有 byte、word、dword、qword 等,它们用在操作数前,相当于做数据类型强制转换。在汇编语言中,无论操作数是立即数、寄存器,或是内存,都可以用数据类型伪指令。

在 16 位实模式下默认数据宽度是 16 位,关键字 “word” 用来告诉 CPU 一次要读或写 2 字节。

mov word [addr], near_proc

例如使用 “mov word [addr], near_proc” 指令时,addr 是个 4 字节的变量,用来存储函数 near_proc  的地址。 near_proc 是个函数名,本身是个地址,在编译阶段就会被替换为数字,这个数字的宽度是不定的,比如 0x18 是 0x0018,还是 0x00000018 呢?这涉及到读写多少个字节的问题,这也是高级语言中数据类型的作用。由于此时是在 16 位的实模式下,我们要用 16 位的地址,必须得告诉 cpu 在此内存地址处连续读 2 字节就够了,所以用关键字 word。


5、栈到底是什么玩意儿

CPU 中有栈段 SS 寄存器和栈指针 SP 寄存器,用来指定当前使用的物理地址。堆栈是人们常说的栈,和堆没关系。

内存中的栈,是物理上的,把数据结构中的栈的概念用物理硬件来实现。它同数据段、代码段一样,是个内存中的区域,也就是栈段寄存器 SS 和栈指针 SP 所指向的内存区域。我们常听说的栈溢出,指的就是这个内存区域无法容纳数据了。

栈要实现线性结构。内存就是线性结构,要做的就是给栈指定一片内存区域,区域的起始地址作为栈基址,存入栈基址寄存器 SS 中,另一端是动态变化的,用栈指针寄存器 SP 来指定。栈在使用过程中是向下扩展的,所以栈顶地址肯定小于栈底地址。栈中的内存地址也是用 "段基址 SS 的值*16+栈指针 SP(段内偏移地址)形成的 20 位地址"访问到的。

由于是硬件实现的栈,故硬件提供了相应的方法来存取栈,即 push 和 pop 指令。在操作 SP 时要加减字长,CPU的字长指一次可处理的数据的长度。在实模式下的字长是 16,所以实模式下的 push 和 pop 指令默认情况下是压入或弹出 2 字节的数据,SP 加上或减去 2 字节。【注】:之前在讲解基址寻址时有例子。
 

【注】如图 3-9 所示,虽然栈是向下发展的,但栈也是内存,访问内存依然是从低地址往高地址,假如当前栈顶是 0x1233E,栈顶数据占 2 字节的话,其范围是 0x1233E ~ 0x1233F。

即使是这里的硬件栈,咱们也可以自己维护指针,如 push ax 可以这样代替:

mov bp, sp
sub bp, 2
mov [bp], ax

bp 默认的段寄存器就是 SS,用 bp 的时候直接操作的便是栈,bp 就相当于栈指针。

6 ~ 8  无条件转移【汇编】

IP 寄存器不能通过赋值更改,所以 CPU 中提供了很多可以改变 CPU 执行流的指令,如 call、ret、jmp 等,它们在内部实现上,包含了许多微操作,用于设置相关数据结构,但是表面上看来只是一个指令。它们在原理上是修改寄存器 CS 和 IP 的值,将 CPU 导向新的位置。 

“相对” 时操作数的计算:无论是 call,还是 jmp,只要是 "相对" 的形式,操作数都是这样来的,目标地址减去当前指令地址后所得的差,再减去机器码大小。

 【注】:这里将几个关键字给总结了一下,这些关键字的组合表示了调用方式的特征,在 call 和 jmp 中都是适用的。我仅在个别调用方法中给出名字的解释,读者可以依葫芦画瓢分析。


【汇编】near、short、far 修饰符

near 的意思同数据类型伪指令 word 一样,指在内存地址处取 2 字节内容,或者将操作数强制转换为 2 字节。可以认为像 near、short、far 这些用在调用或转移中的修饰符,意义就是数据类型转换。far 表示取 4 字节,short 表示取 1 字节。

near 若加在寄存器前面,如 call near ax,表示在 ax 寄存器取 2 字节,相当于给 ax 寄存器中的值做了类型转换。由于 near 的范围可正可负,是个有符号数范围,所以它不等同于数据类型 word。


6、实模式下的 ret

call 指令用来执行一段新的代码,它执行完目标函数后还是要回来的,所以它得提前把回来的路(返回地址)记好。对于 CPU 来说,它是靠程序计数器 PC 来指路的,所以路就在 PC 中。由于随着函数嵌套调用的层数增加,会有更多的返回地址需要保存。内存空间相对是无限的,保存数量未知的返回地址比较理想。利用栈的后进先出的特性,可以保证函数嵌套调用及嵌套返回顺序的一致性,而且栈空间只受限于内存大小。所以 CPU 在栈中保留程序计数器 PC 的值。在 x86 中的程序计数器是 CS:IP,具体保留 IP 部分还是 CS 和 IP 都保留,是要看目标函数的段基址是否和当前段基址一致,是否跨段访问了。

call 指令只会留下返回地址后并踏上新的征程,保留的返回地址是给 ret 或 retf 指令准备的,在目标函数中必须有这两个指令之一,CPU才能回来。

ret (return)把当前栈顶(寄存器 ss: sp 所指向的地址)处的 2 字节 内容弹出栈并用它为 IP 寄存器赋值。ret 指令不管里面的内容是不是地址,内容的正确性由程序员自己控制。ret 只置换了 IP 寄存器,不用换段基址,属于近返回。既然我们称之为弹出栈,也就是说 ret 指令也要负责维护栈顶指针,由于栈是从高地址往低地址发展,所以被回收的栈顶空间应该是使 sp 指针值变大,故 ret 指令会使 sp 指针+2。

retf (return far)是从栈顶取得 4 字节,栈顶处的 2 字节用来替换 IP 寄存器,另外的 2 字节用来替换 CS 寄存器。retf 也不会去检查从栈顶往上的 4 字节内容是不是偏移地址和段基址,由程序员负责栈中数据的正确性。段寄存器都换了,说明这属于远返回。retf 指令也要负责维护栈顶指针,所以 retf 指令会使 sp 指针+4。

【注】call 和 ret 配对,用于近调用和近返回,call far 和 retf 配对,用于远调用和远返回。

7、实模式下的 call

call,意为呼叫、调用。在汇编言中,用 call 命令实现一个函数的调用。在 8086 中 jmp 和 call 两个指令用于改变程序流程。区别是 jmp 属于一去不回头地去执行新的代码,适用环境是 "交接",call 指令用于执行完一段分支后再回来的情况。

  • 16位实模式相对近调用

指令格式: “call near 立即数地址”;near 可省略。

此指令是个 3 字节指令,0xe8 是此操作的操作码,占 1 字节,操作数占 2 字节。立即数地址可以是被调用的函数名、标号、立即数,函数名同标号一样,最终会被编译器转换为一个实际数字地址,如 call near prog_name。

  • 不直接、不间接: 指令中的操作数是立即数,是 call 指令相对于目标地址的偏移量,是个地址差,不是绝对地址。CPU 在实际执行中还要将此偏移量还原成绝对地址。需要转换,不够直接;操作数不是寄存器或内存,不够间接。
  • 相对: “立即数地址” 是 call 指令相对于目标地址的偏移量。
  • :跳转地址和当前指令在同一个段内,只需给出段内偏移地址。操作数是个有符号数且又占 2 字节,由于段是个 16 位大小的空间,所以,正负数的范围是 -32768~32767。

操作数的计算:操作数=目标地址﹣当前指令地址﹣3

call 相对近调用,此指令机器码是 e8llhh,占用 3 字节。其中 e8 是操作码,表示相对近调用,ll 表示操作数的低位,hh 表示操作数的高位,hhll 表示跳转目标为 4 位地址。由于 x86 平台是小端字节序,故写成了 llhh,即高位在高地址,低位在低地址。这 4 位地址是个相对增量,首先用目标函数的地址减去当前 call 指令的地址,所得的差再减去此 call 指令机器码的大小,最终的结果便是 call 指令中的操作数,即与目标地址的相对地址增量。如 call proc_name,proc_name 是某函数名,假如 call 所在的地址是 0x9,事先知道 proc_name 所在的地址是 0x12,此处的 call 是相对近转移,机器码占 3 字节,最终 call 的操作数是 0x12 - 0x9 - 3 = 0x6,由于是小端字节序的原因,低位在低地址,高位在高地址,最终的操作码是 e80600。这个值 0x6 不需要我们去算,编译器会将函数的地址转换成相对地址。

【注】:只要是 “相对”,就是给出跳转指令对于当前指令的偏移量,偏移量都是这么算的。

  • 16位实模式间接绝对近调用

指令格式:"call 寄存器寻址" "call 内存寻址";near 可省略,已省。

如 call ax,call [0x1234]。不同指令形式对应不同的操作码,"call 内存寻址" 对应的操作码是 ff16,机器码是 ff16+16 位内存地址。机器码除了与寻址方式有关外,还和寄存器名称有关,如 "call ax" 的机器码是 ffd0,"call cx" 的机器码是 ffd1。此调用形式也是近调用,near 可以省略,并没有跨段,所以 call 指令只要保留 IP 寄存器的值就好了,将其压入栈后,再用新的偏移地址替换 IP 的值。

  • 间接:"call 寄存器寻址" 或 "call 内存寻址"
  • 绝对:直接给出目标地址
  • :只需给出段内偏移地址

  • 16位实模式直接绝对远调用

指令格式:call far 段基址(立即数):段内偏移地址(立即数)far 可省略

操作码是 0x9a。机器码是 0x9a + 2 字节的偏移地址+2 字节的段基址,即偏移地址在前,段基址在后,和指令的调用形式是相反的。由于是远调用,所以 CS 和 IP 都要用新的,call 指令将来还是要回来的,所以要在栈中保留回来的路,即先把老的 CS 寄存器压入栈,再把老的 IP 寄存器压入栈后,用新的 CS 和 IP 寄存器替换,从此开启新的旅途。

  • 16位实模式间接绝对远调用

这和第 3 种的区别就是 "直接" 变 "间接" 了。也就是说,段基址和段内偏移地址,都不是立即数,要么在内存中,要么在寄存器中。可是,段基址和段内偏移地址都是 16 位地址,用一个寄存器肯定是盛不下了,至少得用两个。寄存器资源还是非常珍贵的,既然要用两个,干脆一个都不用算啦,所以这种间接绝对远调用的形式,不支持寄存器寻址,只支持内存寻址,即段基址和段内偏移地址在内存中。

指令格式:“call far 内存寻址“far 不可省略,否则就和间接绝对近调用一样了。

如 call far [bx],call far [0x1234],操作码是 ffle。在该内存中的内容大小是 4 字节,此内容便是地址,前(低)2字节是段内偏移地址,后(高)2字节是段基址。

新的段基址和段内偏移既然是在内存中,访问内存的话,也要按照 "段基址:段内偏移地址" 的形式去操作。例如上面的 call far [0x1234],由于没有段跨越前缀,则将默认的段基址寄存器 ds*16 后再与 0x1234 相加,得到的和为物理地址,再到该物理地址处去读取新的偏移地址和段基址,以该物理地址为起始的 2 个字节是段内偏移地址,以(该物理地址+2)为起始的 2 个字节是段基址。既然是段基址和段内偏移地址都要用新的,CPU 为了记得回来的路,先把老的 CS 寄存器压入栈,再把老的 IP 寄存器压入栈保存起来,再用新的段基址替换 CS,新的段内偏移地址替换IP。

8、实模式下的 jmp

无条件跳转,是指 "生硬地" 改变 CPU 航线,将程序流转移到新的位置。jmp 转移指令只要更新 CS:IP 寄存器或只更新 IP 寄存器就好了,不需要保存它们的值。

  • 16位实模式相对短转移

指令格式:"jmp short 立即数地址"

操作数是个相对增量,是个有符号数。相对短转移的机器码大小是 2 字节,操作码是 0xeb,可知其占 1 字节。操作数也占 1 字节。

  • 不直接、不间接: 同相对近调用
  • 相对:操作数是个相对增量,是个有符号数。
  • :跳转地址和当前指令在同一个段内,只需给出段内偏移地址。 "短" 体现在操作数中,即跳转的范围只能是 1 字节有符号数所表示的范围,即 -128~127。

操作数的计算:操作数=目标地址﹣当前指令地址﹣2

 CPU 是要用绝对地址来寻址的,目标地址=操作数 + 当前指令地址 + 2,所得的结果便是目标地址的绝对地址,这样的地址 CPU 才能用。CPU  把求得的绝对地址载入 IP 寄存器。短转移目标地址和当前指令在同一个段内,所以 CS 段寄存器不用修改,CPU 就实现了向新位置的转移。

  • 16位实模式相对近转移

同 “相对短转移” 相比,操作数范围增大了,由 8 位宽度变成了 16 位宽度,操作数依然是地址相对量,可正可负,范围是 -32768~32767。其他没啥区别。

指令格式:”jmp near 立即数地址”

操作码是 0xe9。立即数地址也要经过编译器转换为地址偏移量,再变成机器指令中的操作数。

操作数的计算:操作数=目标地址﹣当前指令地址﹣3。

  • 16位实模式间接绝对近转移

同 "jmp 相对近转移" 相比,目标地址是绝对地址,未在指令中直接给出,存在寄存器或内存中。

指令格式: “jmp near 寄存器寻址” 或者 “jmp near 内存寻址”;near 可省。

若操作数在内存中,在不使用段跨越前缀的情况下,段基址寄存器是 DS。由于这也是近转移,CS 寄存器的值不用修改,CPU 只要用 16 位寄存器的值或内存中的 2 字节载入 IP 寄存器,CPU 马上就被带到新的地址。

采用寄存器寻址的 jmp 指令,其操作码是 0xff,操作数随寄存器的不同而不同。采用内存寻址的jmp 指令,其操作码还要看段基址寄存器用的是哪个。【P95】样例说明该点。

  • 16位实模式直接绝对远转移

直接绝对远转移就是以立即数的形式给出目标地址的段基址和段内偏移地址。

指令格式:“jmp 立即数形式的段基址:立即数形式的段内偏移地址”。

例如 jmp0: 0x900,其中 0 是段基址,0x900 是段内偏移地址。由于是远转移,所以 CPU 用操作数中的段基址载入 CS 寄存器,用操作数中的偏移地址载入 IP 寄存器后才完成转移。

  • 16位实模式间接绝对远转移

与 “间接绝对远调用” 一样,由于操作数是两个数,操作数只能放在内存中。为了指示 CPU 在内存中取 4 个字节,需要在指令中用关键字 far,即前两个字节是段内偏移地址,后两个字节是段基址。若不指定,则和第三种的 "间接绝对近转移" 一样,只在内存处取 2 字节。

指令格式:“jmp far 内存寻址”。far 不可省略。

由于操作数在内存中,在不使用段跨越前缀的情况下,段基址寄存器是 DS。此指令的操作数,需要访问内存才能得到,所以需要知道寻址方式。机器码与寻址方式有关,要根据实际使用的数据段寄存器等情况来决定。同样,由于是远转移,CPU 的 CS 寄存器和 IP 寄存器都要修改成操作数中指定的值,从而实现转移。修改方式请参考 “间接绝对远调用” 。

9、标志寄存器 flags

有条件转移指令的条件放在标志寄存器 flags 中。flags 寄存器是 16 位宽,保护模式下对其扩展(extend)成 32 位的 eflags 寄存器。这些用于判断的条件,本质上是上一条指令执行的结果

flags 寄存器中存储的信息,只是结果的特征,即标志,并不是真正的结果,结果可以存储在内存中。这些标志告诉大家,为了产生这个结果,机器都做了什么。比如有时单纯的一个结果并不能让我们了解数据的全貌,产生结果的过程中是否有溢出,不知道这些,怎么确定结果是正确的呢?

无论逻辑多复杂,都可以通过最简单的判断和转移来实现。判断哪里?判断什么?这个判断的对象就是标志寄存器中的标志位。

第 x 位标志位名称用途置 1置 0应用
以下仅在 8088 以上 CPU 中有效
0CF,进位记录进位、借位最高位进位、借位检测无符号数加减法是否有溢出
1空着
2PF,奇偶位标记结果低 8 位中 1 的个数个数为偶数数据传输开始时和结束后对比,判断传输过程中是否出现错误
3空着
4AF,辅助进位标志记录运算结果低 4 位的进、借位情况,低半字节有进、借位
5空着
6ZF,零标志位计算结果为 0
7SF,符号标志位运算结果为负
8TF,陷阱标志位让 CPU 进入单步工作方式让 CPU 进入连续工作方式平时我们用的 debug 程序,在单步调试时,原理上就是让 TF 位为1。
9IF,中断标志位中断开启,CPU 可以响应外部可屏蔽中断中断关闭,CPU 不再响应来自外部的可屏蔽中断
10DF,方向标志位用于字符串操作指令中,给地址的变化提供个方向。指令中的操作数地址会自动减少一个单位,单位的大小取决于指令指令中的操作数地址会自动增加一个单位,单位的大小取决于指令
11OF,溢出标志位标识计算的结果是否超过了数据类型可表示的范围超过,有溢出专门用于检测有符号数运算结果是否有溢出现象
以下仅在 80286 以上 CPU 中有效,相对于8088,它支持特权级和多任务
12~13IOPL,输入/输出特权级用在有特权级概念的 CPU 中有4个任务特权级,特权级 0、特权级 1、特权级 2 和特权级 3。故 IOPL 要占用 2 位来表示这 4 种特权级
14NT,任务嵌套标志位8088 支持多任务,一个任务就是一个进程当一个任务中又嵌套调用了另一个任务(进程)时
15空着
以下仅在 80386 以上 CPU 中有效
16RF,恢复标志位用于程序调试,指示是否接受调试故障,它需要与调试寄存器一起使用忽略调试故障接受调试故障
17VM,虚拟 8086 模式CPU 有了保护模式后,为了兼容实模式下的用户程序可以在保护模式下运行实模式下的程序实模式下的程序不支持多任务,而且程序中的地址就是真实的物理地址。所以在保护模式下每运行一个实模式下的程序,就要为其虚拟一个实模式环境,故称为虚拟模式
以下仅在 80486 以上 CPU 中有效
18AC,对齐检查检查程序中的数据或指令其内存地址是否是偶数,是否是16、32的整数倍,没有余数进行地址对齐检查不检查
以下仅在 80586(奔腾)以上 CPU 中有效
19VIF,虚拟中断标志位虚拟模式下的中断标志
20VIP,虚拟中断挂起标志位在多任务情况下,为操作系统提供的虚拟中断挂起信息,需要与VIF 位配合
21ID,识别标志位系统经常要判断 CPU 型号当前CPU支持 CPU id 指令,可以获取 CPU 的型号、厂商等信息表示当前CPU不支持CPU id 指令
22~31没有实际用途,纯粹是占位用,为了将来扩展

10、有条件转移

有条件转移是一个指令族,在此简单称 jxx。

指令格式:”jxx 目标地址“

若条件满足则跳转到目标地址,否则顺序执行下一条指令。目标地址只能是段内偏移地址。在实模式下,由编译器根据当前指令与目标地址的偏移量,自行将其编译成短转移或近转移。在保护模式下,寄存器中宽度已经到了 32 位,32 位的偏移地址可以访问到整个 32 位地线总线的 4GB 内存空间,编译器不再区分转移方式。

条件转移指令一定得在某个能够影响标志位的指令之后进行。每执行一条指令,标志寄存器中的相应位都会记录这条指令所带来的变化。条件转移指令判断的就是上一条指令对标志位的 "影响",这些 "影响" 就是条件,即条件转移指令中所说的条件就是指标志寄存器中的标志位。

 【注】:表 3-12 要留个印象,经常用的就两三个。



其他问题

【Q】有的编译器中还支持 segment,segment 与 section 功能类似,那么 segment 与 section 有什么区别【P67】

【A】【P25】

【Q】该页提到”平坦模型“,什么是”平坦模型“【P70】

【A】【P12】

【Q】IA32 指令格式是怎么构成的?【P71】

【A】【P71】

【Q】既然指令是存放在指令寄存器中的,那指令中用到的数据存放在哪里?【P71】

【A】存储单元的介绍【P71】



相关文章:

《操作系统真象还原》第3章 完善MBR【3.1 — 3.2】

目录 引用与说明 3.1、地址、section、vstart 浅尝辄止 1、什么是地址 2、什么是 section【汇编】 3、什么是 vstart【汇编】 3.2、CPU 的实模式 1、CPU 工作原理【重要】 2、实模式下的寄存器 4、实模式下 CPU 内存寻址方式 5、栈到底是什么玩意儿 6 ~ 8 无条件转移…...

八大排序-冒泡排序

在里面找动图理解 【数据结构】八大排序(超详解附动图源码)_数据结构排序-CSDN博客 一 简介 冒泡排序应该是我们最熟悉的排序了,在C语言阶段我们就学习了冒泡排序。 他的思想也非常简单: 两两元素相比,前一个比后一个大就交换&#xff0…...

基于Spring Boot+Vue的助农销售平台(协同过滤算法、节流算法、支付宝沙盒支付、图形化分析)

🎈系统亮点:协同过滤算法、节流算法、支付宝沙盒支付、图形化分析; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构:B/S架构 运行环境:win10/win11、jdk17 前端&…...

uniapp写抖音小程序阻止右滑返回上一个页面

最近用uniapp写小程序遇到一个问题因为内部用到右滑的业务&#xff0c;但是只要右滑就会回到上一页面&#xff0c;用了event.preventDeafult()没有用&#xff0c;看了文档找到了解决办法 1.在最外层view加上touchstart事件 <view class"container" touchstart&q…...

华为配置手工负载分担模式链路聚合实验

目录 组网需求 配置思路 操作步骤 配置文件 组网图形 图1 配置手工负载分担模式链路聚合组网图 组网需求配置思路操作步骤配置文件 组网需求 如图1所示&#xff0c;AC1和AC2通过以太链路分别都连接VLAN10和VLAN20&#xff0c;且AC1和AC2之间有较大的数据流量。 用户希望A…...

【Spring】Cookie与Session

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;计算机网络那些事 一、Cookie是什么&#xff1f; Cookie的存在主要是为了解决HTTP协议的无状态性问题&#xff0c;即协议本身无法记住用户之前的操作。 "⽆状态" 的含义指的是: 默认情况…...

chat_gpt回答:qt中,常见格式及格式转换

在Qt中&#xff0c;常见的数据格式包括&#xff1a; QVariant&#xff1a;可以存储多种数据类型&#xff0c;包括整型、浮点型、字符串、布尔值、日期等。QString&#xff1a;用于存储和处理文本字符串。QByteArray&#xff1a;用于处理字节数组&#xff0c;常用于二进制数据。…...

CSS兼容处理

“前端开发兼容——CSS篇” 在前端开发中&#xff0c;CSS样式的兼容性问题常常让开发者感到棘手&#xff0c;尤其是当涉及到IE浏览器时。由于IE浏览器版本繁多&#xff0c;每个版本在CSS支持上还存在差异&#xff0c;这导致开发者在实现统一的视觉效果时&#xff0c;不得不编写…...

制氮机分子筛的材质选择

制氮机分子筛的材质选择对于其性能和效率至关重要。作为制氮设备中的核心部件&#xff0c;分子筛承担着将空气中的氮气与氧气有效分离的重任。以下是对制氮机分子筛常用材质的详细探讨&#xff1a; 制氮机分子筛的主要材质 碳分子筛(CMS) 碳分子筛由活性炭经过特殊工艺加工而成…...

使用Virtual Audio Cable捕获系统音频输出并使用Python处理

一、下载安装Virtual Audio Cable&#xff0c;软件下载地址和安装过程略过。 二、Virtual Audio Cable使用方法Virtual Audio Cable使用笔记一&#xff1a;使用Virtual Audio Cable将播放器的音频流传输到真实声卡驱动中_virtual audio cable control panel-CSDN博客 三、打开…...

微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖及性能分析

微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖及性能分析 目录 微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖及性能分析 1、iOS在scroll-view内部上下滑动吸顶的现象 正常的上下滑动吸顶覆盖&#xff1a; iOS及iPa…...

HDU-1695 GCD

题目大意&#xff1a;已知 1 < x < b , 1 < y < d , 求 gcd ( x , y ) k 的对数。请注意&#xff0c;&#xff08;x5&#xff0c; y7&#xff09; 和 &#xff08;x7&#xff0c; y5&#xff09; 被认为是相同的。 思路&#xff1a;先将 gcd ( x , y ) k 两边同时…...

unity游戏开发之赛车游戏

在这个 unity 2d 赛车游戏教程中&#xff0c;我将构建一款移动超休闲赛车游戏。 这将是一个简单的 unity 2d 汽车游戏。所以这将需要有一个可以无限滚动的背景。 我们需要避开一些障碍。一些评分系统。 以及一种使用我们的手机加速度计控制我们的汽车的方法。然后&#xff0c;我…...

解决milvus migration 迁移数据到出现数据丢失问题

在迁移数据的时候发现数据丢失 问题是数据在批量迁移的过程中&#xff0c;这个错误会被忽略掉 分析下来是因为buuferSize 设置的是500条数据&#xff0c;但是迁移工具对一次迁移的数据是是有大小限制的&#xff0c;如果500条数据的总大小大于4194304&#xff0c;就会导致数据…...

Python Flask 数据库开发

Python Flask 数据库开发 引言环境配置创建 Flask 应用&#xff0c;连接数据库定义路由定义模型创建表创建 API 数据库直接操作启动 Flask 应用app.py 示例运行 Flask访问应用 展望 引言 在现代 web 开发中&#xff0c;Python 的 Flask 框架因其轻量和灵活性受到广泛欢迎。结合…...

深度学习(七)深度强化学习:融合创新的智能之路(7/10)

一、深度强化学习的崛起 深度强化学习在人工智能领域的重要地位 深度强化学习作为一种融合了深度学习和强化学习的新技术&#xff0c;在人工智能领域占据着至关重要的地位。它结合了深度学习强大的感知能力和强化学习优秀的决策能力&#xff0c;能够处理复杂的任务和环境。例如…...

mac电脑通过 npm 安装 @vue/cli脚手架超时问题;

npm 安装 vue/cli遇到的问题步骤 一、安装 Homebrew 如果你还没有安装 Homebrew&#xff0c;首先需要安装它。Homebrew 是 macOS 上的一款包管理工具&#xff0c;它允许你通过简单的命令行指令安装、更新和卸载软件包。&#xff1b; 1, 打开终端&#xff08;Terminal&#xf…...

【52 机器学习 | 基于KNN近邻和随机森林模型对用户转化进行分析与预测】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 字段说明2.4 删除重复值2.5 删除空值 &#x1f3f3;️‍&#x1f308; 3. 数据分析-特征分析3.1 年龄及转化率分析3.2 各营销渠道人数及…...

【Linux】Zookeeper 部署

Zookeeper 搭建方式 单机模式&#xff1a;Zookeeper只运行在一台服务器上&#xff0c;适合测试环境伪集群模式&#xff1a;就是在一台物理机上运行多个Zookeeper 实例&#xff1b;集群模式&#xff1a;Zookeeper运行于一个集群上&#xff0c;适合生产环境&#xff0c;这个计算…...

配置mysql 主主模式 GTID

文章目录 一、前提二、修改my.cnf主1 10.255.131.9主2 10.255.131.10 三、配置主主3.1 配置主 10.255.131.93.2 配置从 10.255.131.103.3 配置主 10.255.131.103.4 配置从 10.255.131.9 四、验证五、同步问题排查以及恢复5.1 查看同步状态5.2 查看同步是否数据一致性&#xff0…...

推荐一款多显示器屏幕亮度调节工具:Twinkle Tray

Twinkle Tray中文版使您可以轻松管理多台显示器的亮度级别。 尽管 Windows 10 能够调节大多数显示器的背光&#xff0c;但它通常不支持外部显示器。 Windows 还缺乏管理多台显示器的亮度的任何功能。 该应用程序将一个新图标插入系统托盘&#xff0c;您可以在其中单击以立即访问…...

第十一章 Shiro会话管理和加密

学习目标 11.1 会话管理11.1.1 会话相关API一、获取会话二、会话属性管理三、会话信息获取四、会话控制五、会话监听六、会话DAO七、会话验证 11.2 缓存一、缓存接口二、内置缓存实现三、配置缓存四、使用缓存五、缓存清理六、注意事项 前面两章我们已经掌握了Shiro四大基石的认…...

DDR4单个DQ仿真实战(一)

目录 引言1、新建Workspace2、导入brd文件3、在SiPro中打开Layout&#xff1a;查看并编辑叠层4、PCB剪裁&#xff08;可选&#xff09;5、创建SiPro6、创建分析模型7、查看分析结果8、创建原理图9、系统行为级仿真 引言 DDR4仿真将按照以下几个步骤进行&#xff1a; 新建Work…...

Android Studio插件版本与Gradle 版本对应关系

一、背景 Android Studio 构建系统以 Gradle 为基础&#xff0c;并且 Android Gradle 插件添加了几项专用于构建 Android 应用的功能。 虽然 Android 插件通常会与 Android Studio 的更新步调保持一致&#xff0c;但插件&#xff08;以及 Gradle 系统的其余部分&#xff09;可…...

Uni-App-01

HBuilder安装卸载 安装 官网地址&#xff1a;https://www.dcloud.io/hbuilderx.html 下载HBuilder最新版 解压到安装目录&#xff0c;路径中不要有中文和空格 在桌面上增加快捷方式 卸载 执行reset.bat 删除HBuilder文件夹&#xff08;如果提示文件被占用&#xff0…...

Java版本鸿鹄工程项目管理系统源码概述

项目背景 随着企业规模的扩大和业务的复杂化&#xff0c;传统的工程项目管理方式已经无法满足高效、准确、实时的管理需求。为了提高工程管理效率、优化资源配置、降低风险并控制成本&#xff0c;企业决定通过数字化转型&#xff0c;构建一个基于Spring Cloud、Spring Boot、M…...

基于echarts、php、Mysql开发的数据可视化大屏

大屏效果展示 管理员进入数据可视化页面将看到数据可视化大屏。大屏内容包括两个条形图&#xff0c;用于统计当前网站所有用户的MBTI 16型人格分布&#xff1b;玫瑰图&#xff0c;用于展示当前网站用户MBTI四个维度&#xff0c;八个字母的占比&#xff1b;折线图&#xff0c;用…...

Me-and-My-Girlfriend-1

Me-and-My-Girlfriend-1 解题 信息收集 nmap扫描存活主机 我的虚拟机为131 所以发现130为目标靶机。 查看网站&#xff0c;找到可利用点 使用浏览器查看&#xff0c;使用xff伪造本地用户。 注册用户cat&#xff0c;观察url有url_id,改为5&#xff0c;发现alice用户。 将…...

R语言实现GWAS meta分析(1)

1、基于数据集的Meta分析 datafilenamec("data1.txt","data2.txt"), setwd(workdir) library(Metalgwas) a1 name1c() for(i in datafilename){ assign(paste("file",a,sep""),data.table::fread(paste(getwd(),"/","…...

Kafka-代码示例

一、构建开发环境 File > New > Project 选择一个最简单的模板 项目和坐标命名 配置maven路径 添加maven依赖 <dependencies><!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients --><dependency><groupId>org.apache.kaf…...

做的单页html怎么放网站/如何规划企业网络推广方案

近期想把服务器换成ubuntu&#xff0c;下载了个ubuntu镜像就安装了。 安装方法&#xff1a;开机后按F11,然后等着一直到了戴尔的一个服务管理平台&#xff0c;可以进BIOS&#xff0c;然后再选择USB启动&#xff0c;即可。 但是装完了很卡&#xff0c;一看就是显卡驱动的问题。…...

大千科技网站建设/网络服务器是指什么

一、源码描述 这是一款功能比较完整的电子会签系统&#xff0c;采用了典型的三层架构技术&#xff0c;可以作为二次开发 使用&#xff0c;有需要的可以下载看看。 二、功能介绍 该源码的功能十分强大&#xff0c;具体介绍如下&#xff1a; 1、发文操作&#xf…...

有什么做设计接任务的网站/排名优化推广

在本章中&#xff0c;我们将介绍以下食谱&#xff1a; 使用授权码授予类型保护资源支持隐式授予类型使用“资源所有者密码凭证”授予类型作为OAuth 2.0迁移的方法配置客户端凭据授予类型添加对刷新令牌的支持使用关系数据库存储令牌和客户详细信息使用Redis作为令牌存储实施客户…...

那些cps网站做的比较好/游戏代理怎么找渠道

2019独角兽企业重金招聘Python工程师标准>>> 前几天&#xff0c;写android代码的时候&#xff0c;遇到R.java报出identifier expected的error。 <string name "3d"> 3D </string> 这样写的问题在于虽然符合XML的规范&#xff0c;但是由于A…...

光明区建设局网站/百度竞价点击软件奔奔

flutter购物商城搜索界面这里数据是固定的&#xff0c;没有从后端获取&#xff0c;仅仅展示页面效果 // ignore_for_file: prefer_const_constructors, sized_box_for_whitespace, prefer_const_literals_to_create_immutables, avoid_unnecessary_containersimport package:…...

开发公司组织架构及岗位职责/宁波seo咨询

一、对MySQL的锁的了解当数据库有并发事务的时候&#xff0c;可能会产生数据的不一致&#xff0c;这时候需要一些机制来保证访问的次序&#xff0c;锁机制就是这样的一个机制。就像酒店的房间&#xff0c;如果大家随意进出&#xff0c;就会出现多人抢夺同一个房间的情况&#x…...