想必接触操作系统的朋友一定对real mode不陌生,那么,你知道big real mode吗?
Big Real Mode与Real Mode十分相似,这种模式可以说是介于Real Mode与Protect Mode之间,在这种模式下,你可以访问4GB的内存,但你的指令编码仍然是16位的。
以下是一个big real mode进入示例,仅供参考:
big_real:
mov ax, 0x2401
int 0x15 ;open A20
cli
db 0x66
lgdt [cs:gdtr]
mov eax, cr0
or eax, 1
mov cr0, eax
mov ax, 0x10; data
mov fs, ax
mov eax, cr0
and al, 0xfe
mov cr0, eax
sti
push 0x0000
pop fs
ret
gdt: dq 0
dq 0x004F9A000000FFFF ; code
dq 0x00CF92000000FFFF ; data for fs
gdtr : dw 23
dd gdt
请注意,有些资料表示,在物理机中,重新为fs赋值将会导致fs段寄存器失去32位寻址功能,
但笔者在物理机中实验过后,却发现事实并非如此,重新赋值并不会导致段寄存器失去32位寻址功能,而是会让寻址紊乱。换句话说,在real mode中,寻址模式为段寄存器*0x10+偏移;在big real mode中,寻址模式则为段基地址+偏移(有点像protect mode),但是如果重新赋值的话,就会变成原来的段寄存器*0x10+偏移,不过偏移可以是32位的。
如果fs=0x10的话,会导致一些问题,比如有些BIOS中断例程过于负责,它可能是以下这样的:
bios_int:
push fs
push gs
...
pop gs
pop gs
iret
这会导致后续代码寻址紊乱。换句话说,如果fs的值是0x10,那么返回后由于fs被重新赋值,寻址模式发生了改变,导致出现一些奇葩的问题,比如这一段代码:
;fs=0x10
push fs
pop fs
mov eax, 0x00000000
mov [fs:eax], byte 0x41
这段代码执行后,0x0000_0000并不会出现0x41,0x41将出现在0x0000_0100.
如果fs=0x00,就不会出现这样的问题,因此,笔者在big_real函数中加入把fs赋值为0的代码。
而且在物理机中,只要打开了A20即可(不必大费周折去进入保护模式),因此,在物理机中,big_real函数还可以继续简化,如下:
big_real:
mov ax, 0x2401
int 0x15 ;open A20
push 0x0000
pop fs
ret
以上说的都是物理机的情况,那么,虚拟机的情况如何呢?
首先,重新为fs赋值不会导致紊乱,寻址模式仍然为段寄存器基地址+偏移,但是,代码不能像刚刚那一段那么简化,还是得回到之前的第一段代码,所以可以说,以下之前的第一段代码是通用的:
big_real:
mov ax, 0x2401
int 0x15 ;open A20
cli
db 0x66
lgdt [cs:gdtr]
mov eax, cr0
or eax, 1
mov cr0, eax
mov ax, 0x10; data
mov fs, ax
mov eax, cr0
and al, 0xfe
mov cr0, eax
sti
push 0x0000
pop fs
ret
gdt: dq 0
dq 0x004F9A000000FFFF ; code
dq 0x00CF92000000FFFF ; data for fs
gdtr : dw 23
dd gdt
还有一点要特别注意:在物理机中的Big Real Mode,任何段寄存器都有32位寻址能力,然而在虚拟机中,只有在保护模式为之赋值的段寄存器才能拥有32位的寻址能力,其他没有在保护模式下赋值的段寄存器就只有16位寻址能力。
比如这段代码:
call big_real
push 0
pop gs
mov eax, 0x00000000
mov [gs: eax], byte 0x41
如果在虚拟机中运行,最后一行会导致宕机;如果在物理机中运行,最后一行可以正常运行。
对了,笔者用的两台物理机处理器都是Inter的64位处理器;第三台虚拟机是VMWare,在amd64上运行,版本是17.0.1。如果笔者有什么错误之处,欢迎大家在评论区加以指正。