在 DOS EXE 程序中从保护模式切换到实模式
Switch from protected mode to real mode in a DOS EXE program
我学会了使用基于 DOS 的简单引导加载程序切换到保护模式。此加载程序将 kernel.bin
加载到缓冲区并将缓冲区复制到 100000h(内核为 8KiB)。然后控制权被转移到内核。当我 return 从内核并尝试切换到实模式时,我遇到了一个问题。
我的FASM汇编代码(和我之前的类似)如下:
format MZ
push cs
pop ds
mov eax,cs
shl eax,4
mov [AdresSegmentuProgramu_32],eax ;Calculating real mode segment
add eax,gdt_table
mov [gdtr+2],eax
mov ax,[AdresSegmentuProgramu_32+2]
mov [code_realsegment_descriptor+3],ax
mov al,[AdresSegmentuProgramu_32+1] ;Setting 16-bit descriptors to return from protected mode
mov [code_realsegment_descriptor+2],al
mov ax,[AdresSegmentuProgramu_32+2]
mov [data_realsegment_descriptor+3],ax
mov al,[AdresSegmentuProgramu_32+1]
mov [data_realsegment_descriptor+2],al
mov ax,3d00h
mov dx,NazwaPliku
int 21h
mov bx,ax
mov ax,3f00h
mov cx,8192
mov dx,KernelGDOS32
int 21h
mov ax,3e00h
int 21h
lgdt [gdtr]
mov eax,[AdresSegmentuProgramu_32]
add eax, pmode_entry ;Far jump to reset CS and jump to simple code
mov [AdresSegmentu_PMODE_entry],eax
mov [far_jump],eax
mov eax,[AdresSegmentuProgramu_32]
add eax,KernelGDOS32
mov [AdresSegmentu_KernelGDOS_32],eax
cli ; Turn off interrupts
mov eax,cr0
or eax,1 ;Switch to PMODE
mov cr0,eax
; Move code that was between turning on protectedmode bit and the FAR JMP
; to an earlier point.
; We need to do a FAR JMP but with a 32-bit offset and a 16-bit selector
; rather than a 16-bit offset and 16-bit selector. A 16-bit offset will
; not work for any address greater than 64KiB. Use FWORD modifier to allow
; a 32-bit offset in the indirect address for the JMP FAR.
; FWORD = 48 bit address (32+16)
mov edi,[AdresSegmentu_KernelGDOS_32]
jmp far fword [ds:far_jump]
far_jump:
dd 0
dw 08h ; Selector 0x08
gdtr: dw 128
dd 0
ldtr:
dw 0x3ff
dd 0
AdresSegmentuProgramu_32 : dd 0
AdresSegmentu_PMODE_entry dd 0
AdresSegmentu_KernelGDOS_32 dd 0
NazwaPliku db 'kernel.bin',0
use32
align 4
gdt_table:
dq 0 ;NULL Descriptor
code_descriptor:
dw 0ffffh
dw 0 ;8h
db 0
db 09ah
db 11001111b
db 0
data_descriptor: ;I prefer flat mode. There are two descriptors for data and code points to 0.
dw 0ffffh
dw 0
db 0 ;10h
db 092h
db 11001111b
db 0
stack_descriptor:
dw 0ffffh
dw 0
db 0 ;18h
db 096h
db 11001111b
db 0
code_realsegment_descriptor:
dw 0ffffh ;20h
dw 0
db 0
db 09ah
db 10001111b
db 0
data_realsegment_descriptor:
dw 0ffffh
dw 0
db 0
db 092h ;28h
db 10001111b
db 0
stack_realsegment_descriptor : dq 0
TIMES 8 dq 0
KernelGDOS32 : TIMES 8192 db 0 ;Jądro naszego systemu który zostanie załadowany
do obszaru pamięci powyżej 1 MB.
pmode_entry:
;this section works correctly
mov ebx,100000h
mov ax, 10h ;Set DS to a 32-bit data selector
mov ds, ax
mov ax,18h ;Set SS to a 32-bit stack selector
mov ss,ax
mov esi,100000h
xor cx,cx
petla_glowna:
mov al,[edi]
mov [esi],al
inc esi
inc edi
cmp cx,8192
inc cx
jne petla_glowna
mov esp,200000h
call dword ebx
mov eax,0
mov cr3,eax
use16
;but i want to switch to real mode cpu generates protection fault
;I was reading osdev.org instructions and still nothing
mov ax,0x28
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,16000
jmp far 0x20:entry16
entry16:
mov eax,cr0
mov eax,0 ;back to real mode
mov cr0,eax
jmp dword 0:rmode ;far jump
rmode:
mov sp,16000
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov es,ax
mov ss,ax
lidt [ldtr]
sti
mov ax,4c00h ;Exit process
int 21h
对您的代码的观察:
- 禁用分页清除 CR0 的第 31 位。正确标识映射包含 GDT 的页面和包含代码从保护模式转换到实模式的指令的页面将是内核的责任。
use16
应该在 jmp far 0x20:entry16
之后和 entry16
标签之前。您在代码中放置得太早了。
entry16
标签必须像 pmode_entry
一样固定(转换为线性地址)。
jmp dword 0:rmode
使用硬编码的实模式段 0。这会导致问题,因为 DOS 可能不会在 CS=0x0000 加载您的程序。您需要在进入保护模式之前存储原始的 CS 段寄存器,以便您可以使用它来执行 FAR JMP(或等效)实模式段。
- 您将 SP 硬编码为 16000。您应该保存原始 SS:SP 并在之后将它们恢复到之前的值return正在返回实模式。地址 16000 可能位于 DOS 使用的内存中间。这可能会导致腐败。
此代码改编自某些code I wrote for a question posed on the #OSDev IRC channel. It incorporates your idea of loading kernel.bin
using DOS int 21h
并将其复制到 0x100000 之后的内存中。如果 DOS 使用 0x100000 和 0x10FFEF 之间的内存作为 DOS 高端内存区 (HMA),我将内核放在 0x110000。
; Assemble to pmdos.exe using FASM with:
; fasm pmdos.asm
format MZ
STACK32_TOP EQU 0x200000
KERNEL_START EQU 0x110000
KERNEL_SIZE EQU 8192
use16
main:
push cs
pop ds ; DS=CS
; Read KERNEL.BIN into 8kb buffer
mov ah, 0x3d
mov dx, kernel_file
int 0x21 ; Open KERNEL.BIN
mov bx, ax ; BX = file handle
jnc .read ; No Error? Read file
mov dx, file_not_fnd ; Print an error and exit back to DOS
mov ah, 0x9
int 0x21 ; Print Error
jmp exit
.read:
mov ah, 0x3f ; Read file
mov cx, KERNEL_SIZE ; Maximum 8192 bytes
mov dx, kernel_mem ; Buffer to read into
int 0x21
jnc .close ; No Error? Finished, close file
mov dx, file_read_err ; Print an error and exit back to DOS
mov ah, 0x9
int 0x21 ; Print Error
jmp exit
.close:
mov ah, 0x3e ; Close file
int 0x21
call check_pmode ; Check if we are already in protected mode
; This may be the case if we are in a VM8086 task.
; EMM386 and other expanded memory manager often
; run DOS in a VM8086 task. DOS extenders will have
; the same effect
jz not_prot_mode ; If not in protected mode proceed to switch
mov dx, in_pmode_str ; otherwise print an error and exit back to DOS
mov ah, 0x9
int 0x21 ; Print Error
jmp exit ; Exit program
not_prot_mode:
call a20_on ; Enable A20 gate (uses Fast method as proof of concept)
cli ; Disable interrupts
; Compute linear address of label gdt_start
; Using (segment SHL 4) + offset
mov eax, cs ; EAX = CS
shl eax, 4 ; EAX = (CS << 4)
mov ebx, eax ; Make a copy of (CS << 4)
add [gdtr+2], eax ; Add base linear address to gdt_start address
; in the gdtr
lgdt [gdtr] ; Load gdt
; Compute linear address of labels using (segment << 4) + offset.
; EBX already is (segment << 4). Add it to the offsets of the labels to
; convert them to linear addresses
lea edi, [pm16_entry+ebx] ; EDI = 16-bit protected mode entry (linear address)
lea esi, [kernel_mem+ebx] ; ESI = Kernel memory buffer (linear address)
add ebx, code_32bit ; EBX = code_32bit (linear address)
push ds ; Save real mode segments on real mode stack
push es
push fs
push gs
mov ecx, cs ; ECX = DOS real mode code segment
push dword CODESEL32 ; CS = 32-bit PM code selector
push ebx ; Linear offset of code_32bit
mov bp, sp ; m16:32 address on top of stack, point BP to it
mov eax, cr0
or eax, 1
mov cr0, eax ; Set protected mode flag
; We are in quasi 16-bit protected mode at this point
; JMP to 32-bit protected mode
jmp far fword [bp] ; Indirect m16:32 FAR jmp with
; m16:32 constructed at top of stack
; DWORD allows us to use a 32-bit offset in 16-bit code
; 16-bit functions that run in real mode
; Check if protected mode is enabled, effectively checking if we are
; in in a VM8086 task. Set ZF to 1 if in protected mode
check_pmode:
smsw ax
test ax, 0x1
ret
; Enable a20 (fast method). This may not work on all hardware
a20_on:
cli
in al, 0x92 ; Read System Control Port A
test al, 0x02 ; Test current a20 value (bit 1)
jnz .skipfa20 ; If already 1 skip a20 enable
or al, 0x02 ; Set a20 bit (bit 1) to 1
and al, 0xfe ; Always write a zero to bit 0 to avoid
; a fast reset into real mode
out 0x92, al ; Enable a20
.skipfa20:
sti
ret
; 16-bit protected mode entry point and code
pm16_entry:
mov ax, DATASEL16 ; Set all data segments to 16-bit data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0 ; Disable protected mode
and eax, NOT 0x80000001 ; Disable paging (bit 31) and protected mode (bit 0)
; The kernel will have to make sure the GDT is in
; a 1:1 (identity mapped page) as well as lower memory
; where the DOS program resides before returning
; to us with a RETF
mov cr0, eax
push cx ; Return to the real_mode code
push real_mode_entry ; with the original CS value (in CX)
retf
; 16-bit real mode entry point
real_mode_entry:
xor esp, esp ; Clear all bits in ESP
mov ss, dx ; Restore the real mode stack segment
lea sp, [bp+8] ; Restore real mode SP
; (+8 to skip over 32-bit entry point and Selector that
; was pushed on the stack in real mode)
pop gs ; Restore the rest of the real mode data segment
pop fs
pop es
pop ds
lidt [idtr] ; Restore the real mode interrupt table
sti ; Enable interrupts
exit:
mov ax, 0x4c00 ; Return to DOS with value 0
int 0x21
; Code that will run in 32-bit protected mode
;
; Upon entry the registers contain:
; EDI = 16-bit protected mode entry (linear address)
; ESI = Kernel memory buffer (linear address)
; EBX = code_32bit (linear address)
; ECX = DOS real mode code segment
align 4
use32
code_32bit:
mov ebp, esp ; Get current SS:ESP
mov edx, ss
cld ; Direction flag forward
mov eax, DATASEL32 ; Set protected mode selectors to 32-bit 4gb flat
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, STACK32_TOP ; Should set ESP to a usable memory location
; Stack will be grow down from this location
; Build linear address and selector on stack for RETF to return to
push CODESEL16 ; Put 16-bit protected mode far entry point 0x18:pm16_entry
push edi ; on stack as a return address (linear address)
push edx ; Save EDX, EBP, ECX
push ebp
push ecx
mov edi, KERNEL_START ; EDI = linear address where PM code will be copied
mov ecx, KERNEL_SIZE ; ECX = number of bytes to copy
rep movsb ; Copy all code/data from kernel buffer to KERNEL_START
call CODESEL32:KERNEL_START ; Absolute jump to relocated code
pop ecx ; ECX = Real mode code segment
pop ebp ; Recover old SS:SP into EDX:EBP
pop edx
retf ; Switch to 16-bit protected mode, goto pm16_entry
kernel_file: db "kernel.bin", 0
file_not_fnd: db "KERNEL.BIN not found - exiting", 0x0a, 0x0d, "$"
file_read_err: db "Can't read KERNEL.BIN - exiting", 0x0a, 0x0d, "$"
in_pmode_str: db "Processor already in protected mode - exiting", 0x0a, 0x0d, "$"
align 4
; Real mode interrupt vector table (IDTR)
idtr:
dw 0x3ff
dd 0
align 4
gdtr:
dw gdt_end-gdt_start-1
dd gdt_start
align 4
gdt_start:
; First entry is always the Null Descriptor
dd 0
dd 0
gdt_code:
; 32-bit 4gb flat r/w/executable code descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; granularity
db 0 ; base high
gdt_data:
; 32-bit 4gb flat r/w data descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; granularity
db 0 ; base high
gdt16_code:
; 16-bit 4gb flat r/w/executable code descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 10001111b ; granularity
db 0 ; base high
gdt16_data:
; 16-bit 4gb flat r/w data descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 10001111b ; granularity
db 0 ; base high
gdt_end:
CODESEL32 = gdt_code - gdt_start
DATASEL32 = gdt_data - gdt_start
CODESEL16 = gdt16_code - gdt_start
DATASEL16 = gdt16_data - gdt_start
kernel_mem: TIMES KERNEL_SIZE db 0x0
为了让内核在完成时return,你必须使用RETF
(远return)指令。在显示屏上打印一些字母的示例 kernel.asm
文件和 returns 可能类似于:
; Assemble to kernel.bin using FASM with:
; fasm kernel.asm
format binary
VIDEO_MEM EQU 0x0b8000
org 0x110000 ; Set ORG to address kernel is loaded at
use32
kernel_entry:
; Write MDP in white on magenta starting on second row, column 0
mov eax, 0x5f SHL 8 OR 'M'
mov [VIDEO_MEM+80*2], ax
mov eax, 0x5f SHL 8 OR 'D'
mov [VIDEO_MEM+80*2+2], ax
mov eax, 0x5f SHL 8 OR 'P'
mov [VIDEO_MEM+80*2+4], ax
retf ; Exit with a FAR return
Assemble 与:
fasm pmdos.asm
fasm kernel.asm
产生 pmdos.exe
和 kernel.bin
。在 DOSBox 中 运行 时的输出应该类似于:
我学会了使用基于 DOS 的简单引导加载程序切换到保护模式。此加载程序将 kernel.bin
加载到缓冲区并将缓冲区复制到 100000h(内核为 8KiB)。然后控制权被转移到内核。当我 return 从内核并尝试切换到实模式时,我遇到了一个问题。
我的FASM汇编代码(和我之前的
format MZ
push cs
pop ds
mov eax,cs
shl eax,4
mov [AdresSegmentuProgramu_32],eax ;Calculating real mode segment
add eax,gdt_table
mov [gdtr+2],eax
mov ax,[AdresSegmentuProgramu_32+2]
mov [code_realsegment_descriptor+3],ax
mov al,[AdresSegmentuProgramu_32+1] ;Setting 16-bit descriptors to return from protected mode
mov [code_realsegment_descriptor+2],al
mov ax,[AdresSegmentuProgramu_32+2]
mov [data_realsegment_descriptor+3],ax
mov al,[AdresSegmentuProgramu_32+1]
mov [data_realsegment_descriptor+2],al
mov ax,3d00h
mov dx,NazwaPliku
int 21h
mov bx,ax
mov ax,3f00h
mov cx,8192
mov dx,KernelGDOS32
int 21h
mov ax,3e00h
int 21h
lgdt [gdtr]
mov eax,[AdresSegmentuProgramu_32]
add eax, pmode_entry ;Far jump to reset CS and jump to simple code
mov [AdresSegmentu_PMODE_entry],eax
mov [far_jump],eax
mov eax,[AdresSegmentuProgramu_32]
add eax,KernelGDOS32
mov [AdresSegmentu_KernelGDOS_32],eax
cli ; Turn off interrupts
mov eax,cr0
or eax,1 ;Switch to PMODE
mov cr0,eax
; Move code that was between turning on protectedmode bit and the FAR JMP
; to an earlier point.
; We need to do a FAR JMP but with a 32-bit offset and a 16-bit selector
; rather than a 16-bit offset and 16-bit selector. A 16-bit offset will
; not work for any address greater than 64KiB. Use FWORD modifier to allow
; a 32-bit offset in the indirect address for the JMP FAR.
; FWORD = 48 bit address (32+16)
mov edi,[AdresSegmentu_KernelGDOS_32]
jmp far fword [ds:far_jump]
far_jump:
dd 0
dw 08h ; Selector 0x08
gdtr: dw 128
dd 0
ldtr:
dw 0x3ff
dd 0
AdresSegmentuProgramu_32 : dd 0
AdresSegmentu_PMODE_entry dd 0
AdresSegmentu_KernelGDOS_32 dd 0
NazwaPliku db 'kernel.bin',0
use32
align 4
gdt_table:
dq 0 ;NULL Descriptor
code_descriptor:
dw 0ffffh
dw 0 ;8h
db 0
db 09ah
db 11001111b
db 0
data_descriptor: ;I prefer flat mode. There are two descriptors for data and code points to 0.
dw 0ffffh
dw 0
db 0 ;10h
db 092h
db 11001111b
db 0
stack_descriptor:
dw 0ffffh
dw 0
db 0 ;18h
db 096h
db 11001111b
db 0
code_realsegment_descriptor:
dw 0ffffh ;20h
dw 0
db 0
db 09ah
db 10001111b
db 0
data_realsegment_descriptor:
dw 0ffffh
dw 0
db 0
db 092h ;28h
db 10001111b
db 0
stack_realsegment_descriptor : dq 0
TIMES 8 dq 0
KernelGDOS32 : TIMES 8192 db 0 ;Jądro naszego systemu który zostanie załadowany
do obszaru pamięci powyżej 1 MB.
pmode_entry:
;this section works correctly
mov ebx,100000h
mov ax, 10h ;Set DS to a 32-bit data selector
mov ds, ax
mov ax,18h ;Set SS to a 32-bit stack selector
mov ss,ax
mov esi,100000h
xor cx,cx
petla_glowna:
mov al,[edi]
mov [esi],al
inc esi
inc edi
cmp cx,8192
inc cx
jne petla_glowna
mov esp,200000h
call dword ebx
mov eax,0
mov cr3,eax
use16
;but i want to switch to real mode cpu generates protection fault
;I was reading osdev.org instructions and still nothing
mov ax,0x28
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,16000
jmp far 0x20:entry16
entry16:
mov eax,cr0
mov eax,0 ;back to real mode
mov cr0,eax
jmp dword 0:rmode ;far jump
rmode:
mov sp,16000
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov es,ax
mov ss,ax
lidt [ldtr]
sti
mov ax,4c00h ;Exit process
int 21h
对您的代码的观察:
- 禁用分页清除 CR0 的第 31 位。正确标识映射包含 GDT 的页面和包含代码从保护模式转换到实模式的指令的页面将是内核的责任。
use16
应该在jmp far 0x20:entry16
之后和entry16
标签之前。您在代码中放置得太早了。entry16
标签必须像pmode_entry
一样固定(转换为线性地址)。jmp dword 0:rmode
使用硬编码的实模式段 0。这会导致问题,因为 DOS 可能不会在 CS=0x0000 加载您的程序。您需要在进入保护模式之前存储原始的 CS 段寄存器,以便您可以使用它来执行 FAR JMP(或等效)实模式段。- 您将 SP 硬编码为 16000。您应该保存原始 SS:SP 并在之后将它们恢复到之前的值return正在返回实模式。地址 16000 可能位于 DOS 使用的内存中间。这可能会导致腐败。
此代码改编自某些code I wrote for a question posed on the #OSDev IRC channel. It incorporates your idea of loading kernel.bin
using DOS int 21h
并将其复制到 0x100000 之后的内存中。如果 DOS 使用 0x100000 和 0x10FFEF 之间的内存作为 DOS 高端内存区 (HMA),我将内核放在 0x110000。
; Assemble to pmdos.exe using FASM with:
; fasm pmdos.asm
format MZ
STACK32_TOP EQU 0x200000
KERNEL_START EQU 0x110000
KERNEL_SIZE EQU 8192
use16
main:
push cs
pop ds ; DS=CS
; Read KERNEL.BIN into 8kb buffer
mov ah, 0x3d
mov dx, kernel_file
int 0x21 ; Open KERNEL.BIN
mov bx, ax ; BX = file handle
jnc .read ; No Error? Read file
mov dx, file_not_fnd ; Print an error and exit back to DOS
mov ah, 0x9
int 0x21 ; Print Error
jmp exit
.read:
mov ah, 0x3f ; Read file
mov cx, KERNEL_SIZE ; Maximum 8192 bytes
mov dx, kernel_mem ; Buffer to read into
int 0x21
jnc .close ; No Error? Finished, close file
mov dx, file_read_err ; Print an error and exit back to DOS
mov ah, 0x9
int 0x21 ; Print Error
jmp exit
.close:
mov ah, 0x3e ; Close file
int 0x21
call check_pmode ; Check if we are already in protected mode
; This may be the case if we are in a VM8086 task.
; EMM386 and other expanded memory manager often
; run DOS in a VM8086 task. DOS extenders will have
; the same effect
jz not_prot_mode ; If not in protected mode proceed to switch
mov dx, in_pmode_str ; otherwise print an error and exit back to DOS
mov ah, 0x9
int 0x21 ; Print Error
jmp exit ; Exit program
not_prot_mode:
call a20_on ; Enable A20 gate (uses Fast method as proof of concept)
cli ; Disable interrupts
; Compute linear address of label gdt_start
; Using (segment SHL 4) + offset
mov eax, cs ; EAX = CS
shl eax, 4 ; EAX = (CS << 4)
mov ebx, eax ; Make a copy of (CS << 4)
add [gdtr+2], eax ; Add base linear address to gdt_start address
; in the gdtr
lgdt [gdtr] ; Load gdt
; Compute linear address of labels using (segment << 4) + offset.
; EBX already is (segment << 4). Add it to the offsets of the labels to
; convert them to linear addresses
lea edi, [pm16_entry+ebx] ; EDI = 16-bit protected mode entry (linear address)
lea esi, [kernel_mem+ebx] ; ESI = Kernel memory buffer (linear address)
add ebx, code_32bit ; EBX = code_32bit (linear address)
push ds ; Save real mode segments on real mode stack
push es
push fs
push gs
mov ecx, cs ; ECX = DOS real mode code segment
push dword CODESEL32 ; CS = 32-bit PM code selector
push ebx ; Linear offset of code_32bit
mov bp, sp ; m16:32 address on top of stack, point BP to it
mov eax, cr0
or eax, 1
mov cr0, eax ; Set protected mode flag
; We are in quasi 16-bit protected mode at this point
; JMP to 32-bit protected mode
jmp far fword [bp] ; Indirect m16:32 FAR jmp with
; m16:32 constructed at top of stack
; DWORD allows us to use a 32-bit offset in 16-bit code
; 16-bit functions that run in real mode
; Check if protected mode is enabled, effectively checking if we are
; in in a VM8086 task. Set ZF to 1 if in protected mode
check_pmode:
smsw ax
test ax, 0x1
ret
; Enable a20 (fast method). This may not work on all hardware
a20_on:
cli
in al, 0x92 ; Read System Control Port A
test al, 0x02 ; Test current a20 value (bit 1)
jnz .skipfa20 ; If already 1 skip a20 enable
or al, 0x02 ; Set a20 bit (bit 1) to 1
and al, 0xfe ; Always write a zero to bit 0 to avoid
; a fast reset into real mode
out 0x92, al ; Enable a20
.skipfa20:
sti
ret
; 16-bit protected mode entry point and code
pm16_entry:
mov ax, DATASEL16 ; Set all data segments to 16-bit data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0 ; Disable protected mode
and eax, NOT 0x80000001 ; Disable paging (bit 31) and protected mode (bit 0)
; The kernel will have to make sure the GDT is in
; a 1:1 (identity mapped page) as well as lower memory
; where the DOS program resides before returning
; to us with a RETF
mov cr0, eax
push cx ; Return to the real_mode code
push real_mode_entry ; with the original CS value (in CX)
retf
; 16-bit real mode entry point
real_mode_entry:
xor esp, esp ; Clear all bits in ESP
mov ss, dx ; Restore the real mode stack segment
lea sp, [bp+8] ; Restore real mode SP
; (+8 to skip over 32-bit entry point and Selector that
; was pushed on the stack in real mode)
pop gs ; Restore the rest of the real mode data segment
pop fs
pop es
pop ds
lidt [idtr] ; Restore the real mode interrupt table
sti ; Enable interrupts
exit:
mov ax, 0x4c00 ; Return to DOS with value 0
int 0x21
; Code that will run in 32-bit protected mode
;
; Upon entry the registers contain:
; EDI = 16-bit protected mode entry (linear address)
; ESI = Kernel memory buffer (linear address)
; EBX = code_32bit (linear address)
; ECX = DOS real mode code segment
align 4
use32
code_32bit:
mov ebp, esp ; Get current SS:ESP
mov edx, ss
cld ; Direction flag forward
mov eax, DATASEL32 ; Set protected mode selectors to 32-bit 4gb flat
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, STACK32_TOP ; Should set ESP to a usable memory location
; Stack will be grow down from this location
; Build linear address and selector on stack for RETF to return to
push CODESEL16 ; Put 16-bit protected mode far entry point 0x18:pm16_entry
push edi ; on stack as a return address (linear address)
push edx ; Save EDX, EBP, ECX
push ebp
push ecx
mov edi, KERNEL_START ; EDI = linear address where PM code will be copied
mov ecx, KERNEL_SIZE ; ECX = number of bytes to copy
rep movsb ; Copy all code/data from kernel buffer to KERNEL_START
call CODESEL32:KERNEL_START ; Absolute jump to relocated code
pop ecx ; ECX = Real mode code segment
pop ebp ; Recover old SS:SP into EDX:EBP
pop edx
retf ; Switch to 16-bit protected mode, goto pm16_entry
kernel_file: db "kernel.bin", 0
file_not_fnd: db "KERNEL.BIN not found - exiting", 0x0a, 0x0d, "$"
file_read_err: db "Can't read KERNEL.BIN - exiting", 0x0a, 0x0d, "$"
in_pmode_str: db "Processor already in protected mode - exiting", 0x0a, 0x0d, "$"
align 4
; Real mode interrupt vector table (IDTR)
idtr:
dw 0x3ff
dd 0
align 4
gdtr:
dw gdt_end-gdt_start-1
dd gdt_start
align 4
gdt_start:
; First entry is always the Null Descriptor
dd 0
dd 0
gdt_code:
; 32-bit 4gb flat r/w/executable code descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; granularity
db 0 ; base high
gdt_data:
; 32-bit 4gb flat r/w data descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; granularity
db 0 ; base high
gdt16_code:
; 16-bit 4gb flat r/w/executable code descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 10001111b ; granularity
db 0 ; base high
gdt16_data:
; 16-bit 4gb flat r/w data descriptor
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 10001111b ; granularity
db 0 ; base high
gdt_end:
CODESEL32 = gdt_code - gdt_start
DATASEL32 = gdt_data - gdt_start
CODESEL16 = gdt16_code - gdt_start
DATASEL16 = gdt16_data - gdt_start
kernel_mem: TIMES KERNEL_SIZE db 0x0
为了让内核在完成时return,你必须使用RETF
(远return)指令。在显示屏上打印一些字母的示例 kernel.asm
文件和 returns 可能类似于:
; Assemble to kernel.bin using FASM with:
; fasm kernel.asm
format binary
VIDEO_MEM EQU 0x0b8000
org 0x110000 ; Set ORG to address kernel is loaded at
use32
kernel_entry:
; Write MDP in white on magenta starting on second row, column 0
mov eax, 0x5f SHL 8 OR 'M'
mov [VIDEO_MEM+80*2], ax
mov eax, 0x5f SHL 8 OR 'D'
mov [VIDEO_MEM+80*2+2], ax
mov eax, 0x5f SHL 8 OR 'P'
mov [VIDEO_MEM+80*2+4], ax
retf ; Exit with a FAR return
Assemble 与:
fasm pmdos.asm
fasm kernel.asm
产生 pmdos.exe
和 kernel.bin
。在 DOSBox 中 运行 时的输出应该类似于: