在DOS EXE程序中从保护模式切换到实模式

时间:2019-02-19 18:15:55

标签: assembly x86 fasm real-mode protected-mode

我学会了使用基于DOS的简单引导加载程序切换到保护模式。该加载器将kernel.bin加载到缓冲区中,并将缓冲区复制到100000h(内核为8KiB)。然后将控制权转移到内核。当我从内核返回并尝试切换到实模式时,我遇到了问题。

我的FASM汇编代码(类似于我先前的Stackoverflow question)如下:

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

1 个答案:

答案 0 :(得分:2)

对代码的观察:

  • 要禁用分页清除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 并将它们恢复为以前的值。地址16000可能位于DOS使用的内存中间。这可能会导致腐败。

此代码是我为code上的一个问题写的一些#OSDev IRC channel的改编而成。它包含您使用DOS int 21h加载kernel.bin并将其复制到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

    jmp .flush_ipfq             ; Flush the instruction prefetch queue
.flush_ipfq:

    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

为使内核在完成后返回,必须使用RETF(远返回)指令。一个示例kernel.asm文件,该文件在显示屏上打印一些字母并返回,可能看起来像这样:

; 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

组装于:

fasm pmdos.asm
fasm kernel.asm

产生pmdos.exekernel.bin。在DOSBox中运行时的输出应类似于:

enter image description here

相关问题