从 DOS 程序进入保护模式
Entering protected mode from a DOS program
我想我已经阅读了十几个基本上与这个问题重复的问题,但我仍然没有找到解决方案。
期望的结果是进入保护模式并无故障停止。我遇到的问题是在使用 6 字节立即值执行段间 jmp
后出现三重故障。
这是我在 DOSBox 和 Pentium II PC 运行 MS-DOS 7 上产生错误的代码。
汇编程序是 MASM 5.10
single segment stack
assume cs:single,ds:single
gdt dq 0
c_limit_lo dw 0ffffh
c_base_lo dw 0
c_base_mid db 0
c_priv db 10011110b ;present set, highest priv, type set, conforming, read
c_limit_hi db 11001111b ;granularity set, operand size 32
c_base_hi db 0
d_limit_lo dw 0ffffh
d_base_lo dw 0
d_base_mid db 0
d_priv db 10010010b ;present set, highest priv, type clr, expand dn, write
d_limit_hi db 11001111b ;granularity set, big set
d_base_hi db 0
gdt_end:
gdt_limit dw gdt_end-offset gdt-1
gdt_addr dd ?
start:
mov ax, cs
mov ds, ax
;calc phys address of current code segment and
;insert it into code and data descriptors
.386p
xor eax, eax
mov ax, cs
mov cl, 4
shl eax, cl ;multiply cs by 16 to get phys address of seg
mov edx, eax
mov c_base_lo, ax
mov d_base_lo, ax ;low word
mov cl, 16
shr eax, cl
mov c_base_mid, al
mov d_base_mid, al ;middle byte
mov c_base_hi, ah
mov d_base_hi, ah ;high byte
add edx, offset gdt ;add offset of gdt
mov gdt_addr, edx ;gdt address set
;attempt to enter protected mode
cli ;disable interrupts
in al, 70h
or al, 80h
out 70h, al ;turn off nonmasked interrupts
in al, 92h
or al, 2
out 92h, al ;enable A20 line
lgdt [gdt_limit]
mov eax, cr0
or eax, 1
mov cr0, eax ;enter protected mode
db 66h ;specify 32-bit operand
jmp_op db 0eah ;manually encoded "jmp 8h:enter_32" TRIPLE FAULT
jmp_loc_lo dw offset enter_32
jmp_loc_hi dw 0
jmp_sel dw 8
enter_32:
mov eax, 0ffffffffh ;sometimes doesn't triple fault on infinite jump or hlt instruction
back:jmp back ;but always triple faults on mov
the_stack db 64 dup (0ffh) ;64 byte stack
single ends
end start
三重故障似乎在某种程度上是基于"luck"。远跳后的 0x67 前缀和 nop
s 的某些配置导致 cpu 的行为就好像它已停止一样。不是很懂
我想我生成了错误的跳跃目标。
更新:单字节指令(与cpu模式无关的单一编码指令)没有错误。我想我会尝试跳入 USE32 定义的段。
这段代码没有错误:
jmp_op db 0eah
jmp_loc_lo dw offset enter_32
jmp_loc_hi dw 0
jmp_sel dw 8
enter_32:
aaa
daa
cmc
cld
cli
stc
nop
aaa
daa
cmc
cld
cli
stc
nop
hlt
由 fuz 回答,我只需要跳转到为 32 位模式汇编的代码。您通过使用 USE32 关键字定义段来告诉汇编程序生成 32 位代码。
带有 VGA 演示和模式切换回实模式的完整保护模式程序:
single segment stack
assume cs:single,ds:single
gdt dq 0 ;global descriptor table
p_code dq 00cf9e000000ffffh ;protected mode code descriptor
p_data dq 00cf92000000ffffh ;protected mode data descriptor
r_code dq 008f9a000000ffffh ;real mode code descriptor
r_data dq 008f92000000ffffh ;real mode data descriptor
v_buff dq 00cf920a0000ffffh ;vga buffer descriptor
gdt_limit dw offset gdt_limit-offset gdt-1 ;gdt_limit <- gdt byte size -1
gdt_addr dd offset gdt ;gdt_addr <- offset of gdt, phys address of
;code segment will be added
start:
mov ax, cs
mov ds, ax ;ds = cs, single segment
mov ax, 13h
int 10h ;enter vga 320x200x256
.386p ;enable 32-bit extensions
xor eax, eax ;clear high word of eax
mov ax, cs ;eax <- cs
shl eax, 4 ;eax <- physical address of cs
add [gdt_addr], eax ;gdt_addr <- physical address of gdt
mov word ptr [r_code+2], ax
mov word ptr [r_data+2], ax ;insert low word of cs phys address
shr eax, 16
mov byte ptr [r_code+4], al
mov byte ptr [r_data+4], al ;insert middle byte of cs address
mov byte ptr [r_code+7], ah
mov byte ptr [r_data+7], ah ;insert high byte of cs address
xor eax, eax ;clear high word of eax
mov ax, seg32 ;eax <- seg32 segment address
shl eax, 4 ;eax <- physical address of seg32
mov word ptr [p_code+2], ax
mov word ptr [p_data+2], ax ;insert low word of seg32 phys address
shr eax, 16
mov byte ptr [p_code+4], al
mov byte ptr [p_data+4], al ;insert middle byte of seg32 address
mov byte ptr [p_code+7], ah
mov byte ptr [p_data+7], ah ;insert high byte of seg32 address
cli ;disable interrupts
in al, 70h ;al <- cmos ram index register port
or al, 80h ;set bit 7 to disable nmi
out 70h, al ;nmi disabled
in al, 92h ;al <- ps/2 system control port
or al, 2 ;set bit 1 to enable a20
out 92h, al ;a20 enabled
lgdt [gdt_limit] ;load gdt
mov eax, cr0
or eax, 1 ;set pe bit
mov cr0, eax ;enter protected mode
db 66h ;specify 32-bit operand
db 0eah ;manually encoded jmp 8h:0, jump to offset 0 of seg32
dd offset enter_32
dw 8
ret_real:
mov eax, cr0
and al, 11111110b ;clear pe bit
mov cr0, eax ;real mode enabled
db 0eah ;jmp single:real_cs to load cs:ip
dw offset real_cs
dw seg single
real_cs:
mov ax, cs
mov ds, ax ;ds = cs
mov ss, ax ;ss = cs
mov sp, offset s16_end ;top of stack is end of stack
in al, 70h ;al <- cmos ram index register port
and al, 01111111b ;clear bit 7 to enable nmi
out 70h, al ;nmi enabled
sti ;enable interrupts
mov ax, 40h
mov es, ax ;access kbd data area via segment 40h
mov word ptr es:[1ah], 1eh ;set the kbd buff head to start of buff
mov word ptr es:[1ch], 1eh ;set kbd buff tail to same as buff head
;now the keyboard buffer is cleared.
xor ah, ah ;select video mode function
mov al, 3 ;select 80x25 16 colors
int 10h ;restore vga compatible text mode
mov ax, 4c00h ;Terminate process function selected
int 21h ;return to ms-dos
s16 db 256 dup (0ffh) ;needed 256 bytes to call int 10h on fx5200 vga bios
s16_end:
single ends
seg32 segment use32
assume cs:seg32,ds:seg32
enter_32:
mov ax, 10h ;protected mode data segment selector
mov ds, ax ;ds references main data segment
mov ss, ax ;stack is in main data segment
mov esp, offset s32_end ;initial top of stack is end of stack
mov ax, 28h ;vga buffer selector
mov es, ax ;es references vga buffer
mov eax, 0ffffffffh ;initialize eax
write_scr:
inc al
inc ah
rol eax, 16
inc al
inc ah ;increment each byte of eax
xor edi, edi ;init index
mov ecx, 320*200/4 ;vga buffer length in bytes
push eax
mov dx, 3dah ;dx <- vga status register
vrb_set:
in al, dx ;al <- status byte
test al, 8 ;is bit vertical retrace bit set
jnz vrb_set ;if so, wait for it to clear
vrb_clr: ;when clear, wait for it to be set
in al, dx
test al, 8
jz vrb_clr ;loop back until vertical retrace bit has been set
pop eax
rep stosd ;fill vga buffer
push eax
in al, 60h ;al <- keyboard data port
mov ebx, eax
pop eax
cmp bl, 1 ;escape key scancode?
jne write_scr ;if not, update screen
mov ax, 20h ;real mode data selector
mov ds, ax
mov es, ax ;setup ds and es for real mode
db 0eah ;jmp 18h:ret_real to load real mode code descriptor
dd offset ret_real
dw 18h
s32 db 128 dup (0ffh) ;128 byte stack
s32_end:
seg32 ends
end start
为 16 位实模式和 运行 在 32 位保护模式下汇编代码可能会产生意外行为和崩溃。这个 16 位代码:
mov eax, 0ffffffffh
back:jmp back
编码为:
66B8FFFFFFFF mov eax,0xffffffff
EBFE jmp short 0x6
但是,如果将此字节序列解码为 32 位保护模式指令,它们将被解释为:
66B8FFFF mov ax,0xffff
FF db 0xff
FF db 0xff
EBFE jmp short 0x6
将 0xffff 移动到寄存器 AX 后,处理器会在发现无效指令(字节 0xff
)时引发一般保护错误 (#GP)。在没有适当的中断描述符 Table (IDT) 和#GP 的异常处理程序的情况下,会发生三重错误。
我想我已经阅读了十几个基本上与这个问题重复的问题,但我仍然没有找到解决方案。
期望的结果是进入保护模式并无故障停止。我遇到的问题是在使用 6 字节立即值执行段间 jmp
后出现三重故障。
这是我在 DOSBox 和 Pentium II PC 运行 MS-DOS 7 上产生错误的代码。 汇编程序是 MASM 5.10
single segment stack
assume cs:single,ds:single
gdt dq 0
c_limit_lo dw 0ffffh
c_base_lo dw 0
c_base_mid db 0
c_priv db 10011110b ;present set, highest priv, type set, conforming, read
c_limit_hi db 11001111b ;granularity set, operand size 32
c_base_hi db 0
d_limit_lo dw 0ffffh
d_base_lo dw 0
d_base_mid db 0
d_priv db 10010010b ;present set, highest priv, type clr, expand dn, write
d_limit_hi db 11001111b ;granularity set, big set
d_base_hi db 0
gdt_end:
gdt_limit dw gdt_end-offset gdt-1
gdt_addr dd ?
start:
mov ax, cs
mov ds, ax
;calc phys address of current code segment and
;insert it into code and data descriptors
.386p
xor eax, eax
mov ax, cs
mov cl, 4
shl eax, cl ;multiply cs by 16 to get phys address of seg
mov edx, eax
mov c_base_lo, ax
mov d_base_lo, ax ;low word
mov cl, 16
shr eax, cl
mov c_base_mid, al
mov d_base_mid, al ;middle byte
mov c_base_hi, ah
mov d_base_hi, ah ;high byte
add edx, offset gdt ;add offset of gdt
mov gdt_addr, edx ;gdt address set
;attempt to enter protected mode
cli ;disable interrupts
in al, 70h
or al, 80h
out 70h, al ;turn off nonmasked interrupts
in al, 92h
or al, 2
out 92h, al ;enable A20 line
lgdt [gdt_limit]
mov eax, cr0
or eax, 1
mov cr0, eax ;enter protected mode
db 66h ;specify 32-bit operand
jmp_op db 0eah ;manually encoded "jmp 8h:enter_32" TRIPLE FAULT
jmp_loc_lo dw offset enter_32
jmp_loc_hi dw 0
jmp_sel dw 8
enter_32:
mov eax, 0ffffffffh ;sometimes doesn't triple fault on infinite jump or hlt instruction
back:jmp back ;but always triple faults on mov
the_stack db 64 dup (0ffh) ;64 byte stack
single ends
end start
三重故障似乎在某种程度上是基于"luck"。远跳后的 0x67 前缀和 nop
s 的某些配置导致 cpu 的行为就好像它已停止一样。不是很懂
我想我生成了错误的跳跃目标。
更新:单字节指令(与cpu模式无关的单一编码指令)没有错误。我想我会尝试跳入 USE32 定义的段。
这段代码没有错误:
jmp_op db 0eah
jmp_loc_lo dw offset enter_32
jmp_loc_hi dw 0
jmp_sel dw 8
enter_32:
aaa
daa
cmc
cld
cli
stc
nop
aaa
daa
cmc
cld
cli
stc
nop
hlt
由 fuz 回答,我只需要跳转到为 32 位模式汇编的代码。您通过使用 USE32 关键字定义段来告诉汇编程序生成 32 位代码。
带有 VGA 演示和模式切换回实模式的完整保护模式程序:
single segment stack
assume cs:single,ds:single
gdt dq 0 ;global descriptor table
p_code dq 00cf9e000000ffffh ;protected mode code descriptor
p_data dq 00cf92000000ffffh ;protected mode data descriptor
r_code dq 008f9a000000ffffh ;real mode code descriptor
r_data dq 008f92000000ffffh ;real mode data descriptor
v_buff dq 00cf920a0000ffffh ;vga buffer descriptor
gdt_limit dw offset gdt_limit-offset gdt-1 ;gdt_limit <- gdt byte size -1
gdt_addr dd offset gdt ;gdt_addr <- offset of gdt, phys address of
;code segment will be added
start:
mov ax, cs
mov ds, ax ;ds = cs, single segment
mov ax, 13h
int 10h ;enter vga 320x200x256
.386p ;enable 32-bit extensions
xor eax, eax ;clear high word of eax
mov ax, cs ;eax <- cs
shl eax, 4 ;eax <- physical address of cs
add [gdt_addr], eax ;gdt_addr <- physical address of gdt
mov word ptr [r_code+2], ax
mov word ptr [r_data+2], ax ;insert low word of cs phys address
shr eax, 16
mov byte ptr [r_code+4], al
mov byte ptr [r_data+4], al ;insert middle byte of cs address
mov byte ptr [r_code+7], ah
mov byte ptr [r_data+7], ah ;insert high byte of cs address
xor eax, eax ;clear high word of eax
mov ax, seg32 ;eax <- seg32 segment address
shl eax, 4 ;eax <- physical address of seg32
mov word ptr [p_code+2], ax
mov word ptr [p_data+2], ax ;insert low word of seg32 phys address
shr eax, 16
mov byte ptr [p_code+4], al
mov byte ptr [p_data+4], al ;insert middle byte of seg32 address
mov byte ptr [p_code+7], ah
mov byte ptr [p_data+7], ah ;insert high byte of seg32 address
cli ;disable interrupts
in al, 70h ;al <- cmos ram index register port
or al, 80h ;set bit 7 to disable nmi
out 70h, al ;nmi disabled
in al, 92h ;al <- ps/2 system control port
or al, 2 ;set bit 1 to enable a20
out 92h, al ;a20 enabled
lgdt [gdt_limit] ;load gdt
mov eax, cr0
or eax, 1 ;set pe bit
mov cr0, eax ;enter protected mode
db 66h ;specify 32-bit operand
db 0eah ;manually encoded jmp 8h:0, jump to offset 0 of seg32
dd offset enter_32
dw 8
ret_real:
mov eax, cr0
and al, 11111110b ;clear pe bit
mov cr0, eax ;real mode enabled
db 0eah ;jmp single:real_cs to load cs:ip
dw offset real_cs
dw seg single
real_cs:
mov ax, cs
mov ds, ax ;ds = cs
mov ss, ax ;ss = cs
mov sp, offset s16_end ;top of stack is end of stack
in al, 70h ;al <- cmos ram index register port
and al, 01111111b ;clear bit 7 to enable nmi
out 70h, al ;nmi enabled
sti ;enable interrupts
mov ax, 40h
mov es, ax ;access kbd data area via segment 40h
mov word ptr es:[1ah], 1eh ;set the kbd buff head to start of buff
mov word ptr es:[1ch], 1eh ;set kbd buff tail to same as buff head
;now the keyboard buffer is cleared.
xor ah, ah ;select video mode function
mov al, 3 ;select 80x25 16 colors
int 10h ;restore vga compatible text mode
mov ax, 4c00h ;Terminate process function selected
int 21h ;return to ms-dos
s16 db 256 dup (0ffh) ;needed 256 bytes to call int 10h on fx5200 vga bios
s16_end:
single ends
seg32 segment use32
assume cs:seg32,ds:seg32
enter_32:
mov ax, 10h ;protected mode data segment selector
mov ds, ax ;ds references main data segment
mov ss, ax ;stack is in main data segment
mov esp, offset s32_end ;initial top of stack is end of stack
mov ax, 28h ;vga buffer selector
mov es, ax ;es references vga buffer
mov eax, 0ffffffffh ;initialize eax
write_scr:
inc al
inc ah
rol eax, 16
inc al
inc ah ;increment each byte of eax
xor edi, edi ;init index
mov ecx, 320*200/4 ;vga buffer length in bytes
push eax
mov dx, 3dah ;dx <- vga status register
vrb_set:
in al, dx ;al <- status byte
test al, 8 ;is bit vertical retrace bit set
jnz vrb_set ;if so, wait for it to clear
vrb_clr: ;when clear, wait for it to be set
in al, dx
test al, 8
jz vrb_clr ;loop back until vertical retrace bit has been set
pop eax
rep stosd ;fill vga buffer
push eax
in al, 60h ;al <- keyboard data port
mov ebx, eax
pop eax
cmp bl, 1 ;escape key scancode?
jne write_scr ;if not, update screen
mov ax, 20h ;real mode data selector
mov ds, ax
mov es, ax ;setup ds and es for real mode
db 0eah ;jmp 18h:ret_real to load real mode code descriptor
dd offset ret_real
dw 18h
s32 db 128 dup (0ffh) ;128 byte stack
s32_end:
seg32 ends
end start
为 16 位实模式和 运行 在 32 位保护模式下汇编代码可能会产生意外行为和崩溃。这个 16 位代码:
mov eax, 0ffffffffh
back:jmp back
编码为:
66B8FFFFFFFF mov eax,0xffffffff
EBFE jmp short 0x6
但是,如果将此字节序列解码为 32 位保护模式指令,它们将被解释为:
66B8FFFF mov ax,0xffff
FF db 0xff
FF db 0xff
EBFE jmp short 0x6
将 0xffff 移动到寄存器 AX 后,处理器会在发现无效指令(字节 0xff
)时引发一般保护错误 (#GP)。在没有适当的中断描述符 Table (IDT) 和#GP 的异常处理程序的情况下,会发生三重错误。