在NASM的 x86汇编中,bits指令为什么在我编写用于unreal模式的代码时不起作用?
我是x86汇编语言的新手。我听说BIOS有一种叫做“大模式”的技术,可以执行或访问超出64KB的 BIOS代码和数据,远远超出BIOS的 0-1MB地址空间。因此我想写一段x86汇编代码来模拟这个过程。(编辑者注:这被称为 unreal mode,就像代码块中的注释所称的那样。)
我的做法如下:简要切换到保护模式,将一个基址为0、界限为4GB的数据段描述符加载到某个段寄存器中(如DS、ES、FS、GS),然后立即返回实模式。该段寄存器的描述符缓存应在保护模式下保留设置(基址、界限和属性)。随后,即使在实模式下,使用该段寄存器访问内存时,也能寻址整整4GB的空间,而不再受64KB的限制。
代码如下:
; The Unreal Mode testing program
; For testing in the Bochs emulator
; NASM Syntax
org 0x7c00 ; BIOS loading address
[BITS 16]
start:
; Set the segment register
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
; Display message indicating entering real mode
mov si, msg_real
call print_string
mov si, msg_try_high
call print_string
xor eax, eax
mov ax, 0x1000
mov ds, ax
mov bx, 0
mov al, [ds:bx]
mov si, msg_done
call print_string
mov ax, cs ;
mov ds, ax
; Enter the main mode
mov si, msg_unreal
call print_string
call enter_unreal ; Switch to Unreal Mode
; Now it is in the main mode and you can access 4GB of storage space.
; Set a test mark at the 2MB position.
mov eax, 0x200000 ; 2MB address
mov byte [ds:eax], 0x55 ; Write in the test value
;Then read and display from the 2MB position.
mov eax, 0x200000
mov bl, [ds:eax]
; displaying results
mov si, msg_success
call print_string
; Read and display the written value
mov si, msg_value
call print_string
mov al, bl
call print_hex_byte
; loop
jmp $
; Enter the main mode function
enter_unreal:
cli
; Keep the original DS
push ds
; Load GDT
lgdt [gdt_ptr]
; Enter protection mode
mov eax, cr0
or al, 1
mov cr0, eax
; Switch to the refresh pipeline
jmp 0x08:protect_mode
[BITS 32]
protect_mode:
; Loading 4GB data segment
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Return to real mode
mov eax, cr0
and al, 0xFE
mov cr0, eax
; Return to the real mode
jmp 0:real_mode_back
;The declare below have no effect ,why?
[BITS 16]
real_mode_back:
; Restore SS and SP
xor ax, ax
mov ss, ax
mov sp, 0x7c00
; Restore DS to 0 (but retain the 4GB attribute of the cache)
pop ds ; The original DS = 0
sti
ret
;Print string function
print_string:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp print_string
.done:
ret
; Print hexadecimal bytes
print_hex_byte:
push ax
mov ah, al
shr al, 4
call print_hex_digit
mov al, ah
and al, 0x0F
call print_hex_digit
pop ax
ret
print_hex_digit:
add al, '0'
cmp al, '9'
jle .print
add al, 7
.print:
mov ah, 0x0E
int 0x10
ret
; data segent
msg_real db '1. In Real Mode (16-bit)', 13, 10, 0
msg_try_high db '2. Trying to access >1MB memory...', 13, 10, 0
msg_done db ' (This will wrap around in real mode)', 13, 10, 0
msg_unreal db '3. Entering Unreal Mode...', 13, 10, 0
msg_success db '4. Unreal Mode active!', 13, 10, 0
msg_value db ' Value at 2MB: 0x', 0
; GDT
align 8
gdt:
;NULL descriptor
dw 0, 0, 0, 0
; Code segment descriptor (Base address 0, Maximum length 4GB)
dw 0xFFFF, 0, 0x9A00, 0xCF
; Data segment descriptor (base address 0, limit 4GB)
dw 0xFFFF, 0, 0x9200, 0xCF
gdt_ptr:
dw $ - gdt - 1
dd gdt
; Fill up to 510 bytes and end with the boot sector flag.
times 510-($-$$) db 0
dw 0xAA55
然后我使用 nasm -f bin bigmode.asm -o bigmode.bin 来编译它。接着使用Bochs的 bximage创建一个软盘映像,并使用
dd if=bigmode.bin of=bigmode.img bs=512 count=1 conv=notrunc
来加载代码。然后在Bochs中运行它。然而,发生了一些奇怪的事情:
当我用调试器在代码后查看时,显然进入了保护模式,它出现在第85行:
(0) [0x000000007c75] 0008:0000000000007c75 (unk. ctxt): mov ax, 0x0010 ; 66b81000
<bochs:5> s
Next at t=14103073
(0) [0x000000007c79] 0008:0000000000007c79 (unk. ctxt): mov ds, ax ; 8ed8
<bochs:6> s
Next at t=14103074
(0) [0x000000007c7b] 0008:0000000000007c7b (unk. ctxt): mov es, ax ; 8ec0
<bochs:7> s
Next at t=14103075
(0) [0x000000007c7d] 0008:0000000000007c7d (unk. ctxt): mov fs, ax ; 8ee0
<bochs:8> s
Next at t=14103076
(0) [0x000000007c7f] 0008:0000000000007c7f (unk. ctxt): mov gs, ax ; 8ee8
<bochs:9> s
Next at t=14103077
(0) [0x000000007c81] 0008:0000000000007c81 (unk. ctxt): mov eax, cr0 ; 0f20c0
<bochs:10> q
00014103077i[ ] dbg: Quit
00014103077i[CPU0 ] CPU is in protected mode (active)
00014103077i[CPU0 ] CS.mode = 32 bit
00014103077i[CPU0 ] SS.mode = 16 bit
00014103077i[CPU0 ] EFER = 0x00000000
00014103077i[CPU0 ] | EAX=60000010 EBX=00000000 ECX=00090000 EDX=00000000
00014103077i[CPU0 ] | ESP=00007bfc EBP=00000000 ESI=000e7d4c EDI=0000ffac
00014103077i[CPU0 ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af PF cf
00014103077i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
00014103077i[CPU0 ] | CS:0008( 0001| 0| 0) 00000000 ffffffff 1 1
00014103077i[CPU0 ] | DS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103077i[CPU0 ] | SS:0000( 0005| 0| 0) 00000000 0000ffff 0 0
00014103077i[CPU0 ] | ES:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103077i[CPU0 ] | FS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103077i[CPU0 ] | GS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103077i[CPU0 ] | EIP=00007c81 (00007c81)
00014103077i[CPU0 ] | CR0=0x60000011 CR2=0x00000000
00014103077i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
但继续执行时,CPU应该切换到实模式,但我发现它仍在运行32位代码,导致错误
(0) [0x000000007c81] 0008:0000000000007c81 (unk. ctxt): mov eax, cr0 ; 0f20c0
<bochs:3> s
Next at t=14103078
(0) [0x000000007c84] 0008:0000000000007c84 (unk. ctxt): and al, 0xfe ; 24fe
<bochs:4> s
Next at t=14103079
(0) [0x000000007c86] 0008:0000000000007c86 (unk. ctxt): mov cr0, eax ; 0f22c0
<bochs:5> s
Next at t=14103080
(0) [0x000000007c89] 0008:7c89 (unk. ctxt): jmpf 0x0000:00007c90 ; ea907c00000000
<bochs:6> s
Next at t=14103081
(0) [0x000000007c90] 0000:7c90 (unk. ctxt): xor eax, eax ; 31c0
<bochs:7> s
Next at t=14103082
(0) [0x000000007c92] 0000:7c92 (unk. ctxt): mov ss, ax ; 8ed0
<bochs:8> s
Next at t=14103083
(0) [0x000000007c94] 0000:7c94 (unk. ctxt): mov esp, 0xfb1f7c00 ; bc007c1ffb
<bochs:9> s
Next at t=14103084
(0) [0x000000007c99] 0000:7c99 (unk. ctxt): ret ; c3
<bochs:10> s
Next at t=14103085
bx_dbg_read_linear: physical memory read error (phy=0x00008ec031fa, lin=0x000000008ec031fa)
<bochs:11> q
00014103085i[ ] dbg: Quit
00014103085i[CPU0 ] CPU is in real mode (active)
00014103085i[CPU0 ] CS.mode = 32 bit
00014103085i[CPU0 ] SS.mode = 16 bit
00014103085i[CPU0 ] EFER = 0x00000000
00014103085i[CPU0 ] | EAX=00000000 EBX=00000000 ECX=00090000 EDX=00000000
00014103085i[CPU0 ] | ESP=fb1f7c04 EBP=00000000 ESI=000e7d4c EDI=0000ffac
00014103085i[CPU0 ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf ZF af PF cf
00014103085i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
00014103085i[CPU0 ] | CS:0000( 0001| 0| 0) 00000000 ffffffff 1 1
00014103085i[CPU0 ] | DS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103085i[CPU0 ] | SS:0000( 0005| 0| 0) 00000000 0000ffff 0 0
00014103085i[CPU0 ] | ES:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103085i[CPU0 ] | FS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103085i[CPU0 ] | GS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
00014103085i[CPU0 ] | EIP=8ec031fa (8ec031fa)
00014103085i[CPU0 ] | CR0=0x60000010 CR2=0x00000000
00014103085i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
bx_dbg_read_linear: physical memory read error (phy=0x00008ec031fa, lin=0x000000008ec031fa)
这相当奇怪。我在第100行添加了
[BITS 16]
——为什么它仍然在执行32位代码?
我甚至使用 objdump 对bin文件进行反汇编。
objdump -D -b binary -m i386 -M intel bigmode.bin
结果如下:
70: ea 75 7c 08 00 66 b8 jmp 0xb866:0x87c75 77: 10 00 adc BYTE PTR [eax],al 79: 8e d8 mov ds,eax 7b: 8e c0 mov es,eax 7d: 8e e0 mov fs,eax 7f: 8e e8 mov gs,eax 81: 0f 20 c0 mov eax,cr0 84: 24 fe and al,0xfe 86: 0f 22 c0 mov cr0,eax 89: ea 90 7c 00 00 00 00 jmp 0x0:0x7c90 90: 31 c0 xor eax,eax 92: 8e d0 mov ss,eax 94: bc 00 7c 1f fb mov esp,0xfb1f7c00 99: c3 ret 9a: ac lods al,BYTE PTR ds:[esi] 9b: 08 c0 or al,al 9d: 74 06 je 0xa5 9f: b4 0e mov ah,0xe a1: cd 10 int 0x10 a3: eb f5 jmp 0x9a a5: c3 ret a6: 50 push eax a7: 88 c4 mov ah,al a9: c0 e8 04 shr al,0x4 ac: e8 09 00 88 e0 call 0xe08800ba b1: 24 0f and al,0xf b3: e8 02 00 58 c3 call 0xc35800ba b8: 04 30 add al,0x30 ba: 3c 39 cmp al,0x39 bc: 7e 02 jle 0xc0 be: 04 07 add al,0x7 c0: b4 0e mov ah,0xe c2: cd 10 int 0x10 c4: c3 ret c5: 31 2e xor DWORD PTR [esi],ebp c7: 20 49 6e and BYTE PTR [ecx+0x6e],cl ca: 20 52 65 and BYTE PTR [edx+0x65],dl cd: 61 popa ce: 6c ins BYTE PTR es:[edi],dx cf: 20 4d 6f and BYTE PTR [ebp+0x6f],cl d2: 64 65 20 28 fs and BYTE PTR gs:[eax],ch d6: 31 36 xor DWORD PTR [esi],esi d8: 2d 62 69 74 29 sub eax,0x29746962 dd: 0d 0a 00 32 2e or eax,0x2e32000a e2: 20 54 72 79 and BYTE PTR [edx+esi*2+0x79],dl e6: 69 6e 67 20 74 6f 20 imul ebp,DWORD PTR [esi+0x67],0x206f7420 ed: 61 popa ee: 63 63 65 arpl WORD PTR [ebx+0x65],sp f1: 73 73 jae 0x166 f3: 20 3e and BYTE PTR [esi],bh f5: 31 4d 42 xor DWORD PTR [ebp+0x42],ecx f8: 20 6d 65 and BYTE PTR [ebp+0x65],ch fb: 6d ins DWORD PTR es:[edi],dx fc: 6f outs dx,DWORD PTR ds:[esi] fd: 72 79 jb 0x178 ff: 2e 2e 2e 0d 0a 00 20 cs cs cs or eax,0x2020000a 106: 20 107: 20 28 and BYTE PTR [eax],ch 109: 54 push esp 10a: 68 69 73 20 77 push 0x77207369 10f: 69 6c 6c 20 77 72 61 imul ebp,DWORD PTR [esp+ebp*2+0x20],0x70617277 116: 70 117: 20 61 72 and BYTE PTR [ecx+0x72],ah 11a: 6f outs dx,DWORD PTR ds:[esi] 11b: 75 6e jne 0x18b 11d: 64 20 69 6e and BYTE PTR fs:[ecx+0x6e],ch 121: 20 72 65 and BYTE PTR [edx+0x65],dh 124: 61 popa 125: 6c ins BYTE PTR es:[edi],dx 126: 20 6d 6f and BYTE PTR [ebp+0x6f],ch 129: 64 65 29 0d 0a 00 33 fs sub DWORD PTR gs:0x2e33000a,ecx 130: 2e 131: 20 45 6e and BYTE PTR [ebp+0x6e],al 134: 74 65 je 0x19b 136: 72 69 jb 0x1a1 138: 6e outs dx,BYTE PTR ds:[esi] 139: 67 20 55 6e and BYTE PTR [di+0x6e],dl 13d: 72 65 jb 0x1a4 13f: 61 popa 140: 6c ins BYTE PTR es:[edi],dx 141: 20 4d 6f and BYTE PTR [ebp+0x6f],cl 144: 64 65 2e 2e 2e 0d 0a fs gs cs cs cs or eax,0x2e34000a 14b: 00 34 2e 14e: 20 55 6e and BYTE PTR [ebp+0x6e],dl 151: 72 65 jb 0x1b8 153: 61 popa 154: 6c ins BYTE PTR es:[edi],dx 155: 20 4d 6f and BYTE PTR [ebp+0x6f],cl 158: 64 65 20 61 63 fs and BYTE PTR gs:[ecx+0x63],ah 15d: 74 69 je 0x1c8 15f: 76 65 jbe 0x1c6 161: 21 0d 0a 00 20 20 and DWORD PTR ds:0x2020000a,ecx 167: 20 56 61 and BYTE PTR [esi+0x61],dl 16a: 6c ins BYTE PTR es:[edi],dx 16b: 75 65 jne 0x1d2 16d: 20 61 74 and BYTE PTR [ecx+0x74],ah 170: 20 32 and BYTE PTR [edx],dh 172: 4d dec ebp 173: 42 inc edx 174: 3a 20 cmp ah,BYTE PTR [eax] 176: 30 78 00 xor BYTE PTR [eax+0x0],bh 179: 90 nop 17a: 90 nop 17b: 90 nop 17c: 90 nop 17d: 90 nop 17e: 90 nop 17f: 90 nop ... 188: ff (bad) 189: ff 00 inc DWORD PTR [eax] 18b: 00 00 add BYTE PTR [eax],al 18d: 9a cf 00 ff ff 00 00 call 0x0:0xffff00cf 194: 00 92 cf 00 17 00 add BYTE PTR [edx+0x1700cf],dl 19a: 80 7d 00 00 cmp BYTE PTR [ebp+0x0],0x0 ... 1fe: 55 push ebp 1ff: aa stos BYTE PTR es:[edi],al
它也指出了问题
[BITS 16]
不起作用。
我不知道为什么?需要添加一些代码才能让它工作吗?
我的nasm版本是2.15.05,我的操作系统是WSL的 Ubuntu 22.04。我的Bochs版本是2.6.8。
解决方案
但当它继续执行时,CPU应该切换到实模式,但我发现它仍在运行32位代码,这导致错误
确实如此。有意思的是,它确实切换到了实模式,但仍在运行32位代码。相关日志:
00014103085i[CPU0 ] CPU is in real mode (active)
00014103085i[CPU0 ] CS.mode = 32 bit
00014103085i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
00014103085i[CPU0 ] | CS:0000( 0001| 0| 0) 00000000 ffffffff 1 1
所以这就是原因——CS描述符缓存仍然保持D=1(这是决定何时启用16位还是32位模式的标志)。Unreal模式之所以可行,是因为进入实模式前设置的界限保持不变,并且在重新载入段寄存器时从不被覆盖。问题在于,实模式中不仅基址会被保留,其他一切也会被保留,其中就包括DB标志。这就是为什么任何从32位保护模式切换到16位实模式的代码在执行前必须经过16位 PM(例如参见https://retrocomputing.stackexchange.com/questions/32242/is-it-possible-to-switch-from-32-bit-protected-mode-to-real-mode-without-going-t)。
所以你需要确保在返回到(非)实模式之前,你的代码是在16位保护模式下运行。要么再添加一个16位的CS选择子,要么完全放弃32位(毕竟你并不真的需要它,你只需要短暂地切换到保护模式以重新加载描述符缓存,这在16位就可以完成)。
这很奇怪。我在第100行添加了
[BITS 16]。为什么它仍然在执行32位代码?
因为这不是CPU指令,而是汇编器指令。它并不会切换CPU模式!你的程序控制CPU及其模式,而 BITS 告诉汇编器程序中哪一部分应该在哪种模式下运行(以便它能输出CPU能理解的机器码)。是否能始终让描述所谓CPU模式的指令与实际运行时的CPU模式相匹配,取决于你自己。