从实模式切换到保护模式后进行远跳转

7
根据这个教程,创建一个简单的操作系统并切换到保护模式非常简单,只需要使用以下代码而无需执行其他已知操作,例如启用A20……
无论如何,我是这个领域的新手,我按照他们的说明编写了以下代码,并根据此SO所提供的修改进行了修改。
代码结构: 这个简单的操作系统应该按以下方式简要加载:
1. 加载/读取15个扇区 2. 启用GDT 3. 切换到保护模式(并打印“成功进入32位保护模式”) 4. 加载内核并打印“X”
然而,仿真器仍在重新启动。请查看完整的代码。
bootloader.asm
[bits 16]
[org 0x7C00]

KERNEL_OFFSET equ 0x1000

xor ax, ax
mov ds, ax
mov es, ax
mov [BOOT_DRIVE], dl
mov ax, 0x07E0                  ; End of stack
cli
mov ss, ax
mov sp, 0x1200                  ; Size of Stack. By this, we assume that stack starts at 9000h
                            ; of size 1200h and ends at 7E00h to avoid being overwritten.
sti

call    load_kernel
call    switch_to_pm

jmp $

%include "src/functions/disk_load.asm"

load_kernel:
    mov bx, KERNEL_OFFSET   
    mov dh, 15
    mov dl, [BOOT_DRIVE]
    call    disk_load

    ret

; Global variables
BOOT_DRIVE  db 0
SECTORS     db 0
MSG_PROT_MODE   db "Successfully landed in 32-bit Protected Mode" , 0

%include "src/functions/gdt.asm"
%include "src/functions/switch_to_pm.asm"

[ bits 32]
; This is where we arrive after switching to and initialising protected mode.
BEGIN_PM:
    mov ebx , MSG_PROT_MODE
    call    print_string_pm     ; Use our 32 - bit print routine.


    ;call   KERNEL_OFFSET       ; Now jump to the address of our loaded
                    ; kernel code , assume the brace position ,
                    ; and cross your fingers. Here we go !

    jmp $           ; Hang.

%include "src/functions/writing_video_mode.asm"

; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55

; 15 sector padding
times 15*256 dw 0xDADA

disk_load.asm

disk_load:
    mov [SECTORS], dh
    mov ch, 0x00            ;C=0
    mov dh, 0x00            ;H=0
    mov cl, 0x02            ;S=2

next_group:
    mov di, 5                ;Max 5 tries

again: 
    mov ah, 0x02            ;Read sectors
    mov al, [SECTORS]
    int 0x13
    jc   maybe_retry
    sub [SECTORS], al ;Remaining sectors
    jz  ready
    mov cl, 0x01            ;Always sector 1
    xor dh, 1                ;Next head on diskette!
    jnz next_group
    inc ch                      ;Next cylinder
    jmp next_group

maybe_retry:
    mov ah, 0x00            ;Reset diskdrive
    int 0x13
    dec di
    jnz again
    jmp disk_error

ready:
    ret

disk_error:
    mov ah, 0x0e
    mov al, 'Y'
    int 0x10
    jmp $

DISK_ERROR_MSG db "Disk read error!", 0

gdt.asm

gdt_start:

    gdt_null:
        dd 0x0 ; ’ dd ’ means define double word ( i.e. 4 bytes )
        dd 0x0

    gdt_code:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10011010b ; 1 st flags , type flags
        db 11001111b ; 2 nd flags , Limit ( bits 16 -19)
        db 0x0

    gdt_data:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10010010b ; 1 st flags , type flags
        db 11001111b ; 2 nd flags , Limit ( bits 16 -19)
        db 0x0

    gdt_end:

    gdt_descriptor:
        dw gdt_end - gdt_start - 1
        dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

switch_to_pm.asm

[ bits 16 ]

switch_to_pm:
    cli
    lgdt [ gdt_descriptor ]
    mov eax , cr0
    or eax , 0x1
    mov cr0 , eax
    jmp CODE_SEG:init_pm

[ bits 32 ]

init_pm:
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ebp , 0x90000
    mov esp , ebp
    call BEGIN_PM

为了确保我们进入了受保护模式: writing_video_mode.asm
[ bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print_string_pm:
    push eax
    push ebx
    push edx
    mov edx , VIDEO_MEMORY ; Set edx to the start of vid mem.

    print_string_pm_loop:
        mov al, [ebx]
        mov ah, WHITE_ON_BLACK
        cmp al, 0
        je print_string_pm_done
        mov [edx], ax
        add ebx, 1
        add edx, 2
        jmp print_string_pm_loop

    print_string_pm_done:
        pop edx 
        pop ebx 
        pop eax 
        ret

kernel.c

void main () {
    char * video_memory = (char *) 0xb8000;
    *video_memory = 'X';
}

顺便说一下,我正在使用这个Makefile

all: bootloader.bin kernel.bin

bootloader.bin: src/bootloader.asm
    nasm src/bootloader.asm -f bin -o output/bootloader.bin

kernel.o: src/kernel/kernel.c
    gcc -ffreestanding -c src/kernel/kernel.c -o output/kernel.o -m32

kernel.bin: kernel.o
    ld -o output/kernel.bin -Ttext 0x1000 --oformat binary output/kernel.o -melf_i386

clean:
    rm -f output/*.* output/*

为了将它移动到闪存中,我使用以下命令:

cat output/bootloader.bin output/kernel.bin > os-image
sudo dd if=os-image of=/dev/sdb bs=512 conv=notrunc && sync

为了运行它,我正在使用带有以下命令的qemu:
qemu-system-i386 -hda /dev/sdb

注意:/dev/sdb是我的闪存驱动器。

问题: 实际上,当在bootloader.asm中禁用/注释call KERNEL_OFFSET时,代码会进入保护模式(即打印“成功进入32位保护模式”)。但是启用此行时,它开始引导和重启。

我希望我已经提供了所有必要的信息。对我来说,远程跳转不应该以这种方式完成。任何评论都将不胜感激。


使用调试器(例如内置于bochs中的调试器)来查看发生了什么。 - Jester
2
@fuz gcc 不在乎。引用手册的话:“您可以混合选项和其他参数。在大多数情况下,使用的顺序并不重要。” - Jester
1
在 bootloader.asm 中注释掉 times 15*256 dw 0xDADA。你创建了一个具有 512 字节引导扇区的映像。然后,你用另外 15 个扇区进行填充,最后使用 cat output/bootloader.bin output/kernel.bin > os-image 将两者连接起来。你的内核将被放置在前 16 个扇区之外。 - Michael Petch
1
@Jester:没错,因为GCC不符合POSIX标准。 - Michael Petch
1
你真的需要解决堆栈问题,否则它会回来咬你的屁股。 - Michael Petch
显示剩余7条评论
1个回答

1

只需删除

times 15*256 dw 0xDADA

(顺便问一句,为什么是DADA?)
然后编译你的内核,之后

cat output/bootloader.bin output/kernel.bin > os-image

并以某种方式使你的操作系统镜像长度为8192字节(16个扇区,引导程序+15)。我不是Linux/Unix粉丝(甚至不会使用它们),但我认为dd命令(类似于dd if=dev\zero of=temp_file count=(8192 - 文件实际大小),然后cat os-image temp-file > os-image)应该可以完成任务。我也不确定这个编译命令是否正确(只是不确定)。我会从链接器命令中删除“-melf_i386”,但我不知道,我只在Windows上使用过MinGW(它只类似于GCC)。

抱歉我的英语不好,希望我有所帮助。


如果使用DD,通常最好先创建整个磁盘映像,然后将所需的文件放置在特定位置。一些想法可以在此SO答案中找到:https://dev59.com/bY_ea4cB1Zd3GeqPRa7f#34108769。 - Michael Petch
关于-melf_i386,如果您正在使用64位编译器和链接器进行开发,则需要该选项。64位工具链将默认生成64位代码和对象。指定-melf_i386会特别针对32位对象。使用32位对象来执行32位和16位代码会更加方便。如果您正在生成64位代码,则当然不希望使用-melf_i386。这也是GCC使用-m32编译文件的原因。如果您生成32位对象,则无法将它们与其他64位对象链接,并且会出现链接器错误。 - Michael Petch
如果您使用生成32位程序(而不是64位)的GCC,则无需指定-melf_i386-m32,因为这些编译器的默认值。对于i386、i486、i586、i686(等等)交叉编译器也是如此。 - Michael Petch
1
如果您阅读了我在第一条评论中提供的答案,您会发现我实际上有一个关于如何在Windows上使用_Chrysocome_(来自Chrysocome)的章节,并且可以从哪里下载。它使用与Linux版本相同的命令行语法。唯一不同的地方是,如果您想直接写入物理介质(如USB驱动器等),则Chrysocome的DD与Linux不同。 - Michael Petch
哦,谢谢。你说得对,我没有看到它。我开始阅读第二条评论,然后就忘了它。但即使没有那个,我也创建了一个.bat文件来编译它,在我的情况下 dd 看起来像 dd bs=1 if=/dev/zero of=temp_file count=%sizeoftempfile%,其中 sizeoftempfile 是 8192-os-image 大小(由 cat bootloader.bin kernel.bin > os-image 制成的文件),然后只需 cat os-image temp_file > finaloutput - TheNNX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接