文章目录
查看系列文章点这里: 操作系统真象还原
前言
在上一节x86 CPU的实模式——寻址(二)中,我们介绍了如何获取数据,但是光有数据没有什么用,还要有代码才行。我们知道在内存中有非常多少代码段,它们互相配合共同为我们提供各种各样的服务,接下来我们来看看如何将这些代码段联系起来。
三、实模式下的调用(call、ret)
无论使用什么语言编写程序,为了方便简洁,程序员们会设计各种各样的函数,它们有各种各样的功能,在代码中被程序员调用,完成工作后就返回继续执行源代码。
间接通过这种方式使用别的地方的代码,我们称之为调用,通过 call 和 ret 指令实现,其中call负责“ 去 ”,ret负责“ 回来 ”,根据被调用的代码是否在一个段,地址给出的方式,分为以下四类:
1、直接相对近调用
被调用的代码和当前代码在同一个代码段,目标地址通过立即数给出。
call near 立即数地址(标号) ;near可以省略
call near near_proc
...
near_proc:
...
ret
值得注意的是,因为在同一个代码段,call指令在被编译的时候,操作数是当前地址与目标地址的增量。因此,如果操作数是以标号的形式给出,则需要多一步处理才能得到真正的操作数。
2、间接绝对近调用
被调用的代码和当前代码在同一个代码段,目标地址在寄存器或内存中。
call 寄存器地址
call 内存地址
mov word[addr],near_proc
call [addr] ;给出内存地址
...
near_proc:
...
ret
值得注意的是,这里的地址是真正的地址,不需要像相对近调用那样转换地址才能使用。
3、直接绝对远调用
被调用的代码和当前代码不在同一个代码段,直接给出目标地址的段基址和段内偏移地址。
call far 段基址(立即数):段内偏移地址(立即数) ;far可以省略
call far 0 : far_proc ;这里为了方便,故还是在一个段,far_proc代表偏移地址
...
far_proc:
...
retf
值得注意的是,远调用返回需要使用 retf 指令。
4、间接绝对远调用
被调用的代码和当前代码不在同一个代码段,目标地址在内存中,一共四个字节,高2字节是段基址,低2字节是段内偏移地址。
call far 内存地址 ;far不可以省略
addr dw far_proc,0 ;这里为了演示方便,故还是在一个段,far_proc代表偏移地址
...
call [addr]
...
far_proc:
...
retf
四、实模式下的跳转(jmp、jxx)
跳转也是去执行别的地方的代码,和调用最大的区别就是跳转“ 一去不复返 ”,在具体使用上分为无条件转移和有条件转移,咱们挨个介绍。
1、无条件转移
(1)相对短转移
跳转的代码和当前代码在同一个代码段,目标地址通过立即数给出。
jmp short 立即数地址(标号) ;操作数范围-128~127
jmp short near_proc
...
near_proc:
...
(2)相对近转移
跳转的代码和当前代码在同一个代码段,目标地址通过立即数给出。
jmp near 立即数地址(标号) ;操作数范围-32768~32767
jmp near near_proc
...
near_proc:
...
值得注意的是,因为在同一个代码段,jmp short/near 指令在被编译的时候,操作数是当前地址与目标地址的增量,与call指令相同。
除此之外,short和near可以省略,编译器会根据立即数的大小,自动添加short或者near,但前提是操作数在范围之内。
(3)间接绝对近转移
跳转的代码和当前代码在同一个代码段,目标地址在寄存器或内存中。
jmp near 寄存器地址
jmp near 内存地址
mov ax,near_proc
jmp near ax ;给出内存地址
...
near_proc:
...
值得注意的是,这里的地址是真正的地址,不需要像相对近调用那样转换地址才能使用。
(4)直接绝对远转移
跳转的代码和当前代码不在同一个代码段,直接给出目标地址的段基址和段内偏移地址。
jmp 段基址(立即数):段内偏移地址(立即数)
jmp 0 : far_proc ;这里为了方便,故还是在一个段,far_proc代表偏移地址
...
far_proc:
...
(5)间接绝对远转移
跳转的代码和当前代码不在同一个代码段,目标地址在内存中,一共四个字节,高2字节是段基址,低2字节是段内偏移地址。
jmp far 内存地址 ;far不可以省略
addr dw far_proc,0 ;这里为了演示方便,故还是在一个段,far_proc代表偏移地址
...
call [addr]
...
far_proc:
...
2、有条件转移
上面介绍的都是无条件转移指令,也就是说执行到这条指令必转移,但这很明显不能满足我们的需要,所以还必须要有有条件转移,使用与无条件转移一模一样,只需要将 jmp 替换为对应的条件转移指令即可,具体如下表所示:
转移指令 | 条件 | 意义 |
---|---|---|
jz/je | ZF=1 | 结果等于0 |
jnz/jne | ZF=0 | |
js | SF=1 | 负数 |
jns | SF=0 | |
jo | OF=1 | 溢出 |
jno | OF=0 | |
jp/jpe | PF=1 | 低字节中有偶数个1 |
jnp/jpo | PF=0 | |
jbe/jna | CF=1或ZF=1 | 小于等于 |
jnbe/ja | CF=ZF=0 | 大于 |
jc/jb/jnae | CF=1 | 进位 |
jnc/jnb/jae | CF=0 | |
jl/jnge | SF!=OF | 小于 |
jnl/jge | SF=OF | 大于等于 |
jle/jng | ZF!=OF或ZF=1 | 小于等于 |
jnle/jg | SF=OF且ZF=0 | 大于 |
jcxz | CX寄存器=0 |
直接记忆非常困难,建议通过含义在使用的时候用对应的字母进行组合,如下所示:
简写 | j | n | a | b | z | e | g | l | s | c | o | p |
---|---|---|---|---|---|---|---|---|---|---|---|---|
含义 | jmp | not | above | below | zero | equal | great | less | sign | carry | overflow | parity |
总结
这一节的东西非常多,但还是有有一些规律的,不过大多数时候,不会全部用到的,记住一些常用的,就足够啦!
持续更新~~