簡體   English   中英

任何在保護模式下將字符串放入屏幕的嘗試都會導致重啟

[英]Any attempt to put a string to the screen in Protected Mode causes reboot

從頭開始開發OS時,我剛剛進入保護模式。 我設法進入C語言並創建了將字符打印到屏幕上的函數(感謝Michael Petch幫助我達到了這一階段)。 無論如何,每當我嘗試建立一個在字符串文字中循環並打印其中每個字符的例程時,都存在一些問題。 QEMU只是進入引導循環,一次又一次地重新啟動,而我再也看不到我美麗的黑底綠燈視頻模式。 如果我將其移出例程並在kmain()函數中kmain()字符地kmain()我已刪除了該部分),則一切正常且繁瑣。 這是我嘗試實現字符串打印功能的文件:

vga.c-

#include <vga.h>

size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t *terminal_buffer;

volatile uint16_t * const VIDMEM = (volatile uint16_t *) 0xB8000;

size_t strlen(const char *s)
{
    size_t len = 0;
    while(s[len]) {
        len++;
    }
    return len;
}

void terminal_init(void) 
{
    terminal_row = 0;
    terminal_column = 0;
    terminal_color = vga_entry_color(LGREEN, BLACK);
    for(size_t y = 0; y < VGA_HEIGHT; y++) {
        for(size_t x = 0; x < VGA_WIDTH; x++) {
            const size_t index = y * VGA_WIDTH + x;
            VIDMEM[index] = vga_entry(' ', terminal_color);
        } 
    }
}

void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
    const size_t index = y * VGA_WIDTH + x;
    VIDMEM[index] = vga_entry(c, color);
}

void terminal_putchar(char c)
{
    terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
    if(++terminal_column == VGA_WIDTH) {
        terminal_column = 0;
        if(++terminal_row == VGA_HEIGHT) {
            terminal_row = 0;
        }
    }
}

void terminal_puts(const char *s)
{
    size_t n = strlen(s);
    for (size_t i=0; i < n; i++) {
        terminal_putchar(s[i]);
    }
}

我使用以下引導程序代碼將內核讀入內存:

extern kernel_start             ; External label for start of kernel
global boot_start               ; Make this global to suppress linker warning
bits 16

boot_start:
    xor ax, ax                  ; Set DS to 0. xor register to itselfzeroes register
    mov ds, ax
    mov ss, ax                  ; Stack just below bootloader SS:SP=0x0000:0x7c00
    mov sp, 0x7c00

    mov ah, 0x00
    mov al, 0x03
    int 0x10

load_kernel:
    mov ah, 0x02                ; call function 0x02 of int 13h (read sectors)
    mov al, 0x01                ; read one sector (512 bytes)
    mov ch, 0x00                ; track 0
    mov cl, 0x02                ; sector 2
    mov dh, 0x00                ; head 0
;    mov dl, 0x00               ; drive 0, floppy 1. Comment out DL passed to bootloader
    xor bx, bx                  ; segment 0x0000
    mov es, bx                  ; segments must be loaded from non immediate data
    mov bx, 0x7E00              ; load the kernel right after the bootloader in memory 
.readsector:
    int 13h                     ; call int 13h
    jc .readsector              ; error? try again

    jmp 0x0000:kernel_start     ; jump to the kernel at 0x0000:0x7e00

我在內核開始時有一個程序集存根,它進入保護模式,將BSS部分清零,發出CLD並調用我的C代碼:

; These symbols are defined by the linker. We use them to zero BSS section
extern __bss_start
extern __bss_sizel

; Export kernel entry point
global kernel_start

; This is the C entry point defined in kmain.c
extern kmain               ; kmain is C entry point
bits 16

section .text
kernel_start:

    cli    

    in al, 0x92
    or al, 2
    out 0x92, al

    lgdt[toc]

    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp 0x08:start32     ; The FAR JMP is simplified since our segment is 0

section .rodata
gdt32:
    dd 0
    dd 0

    dw 0x0FFFF
    dw 0
    db 0
    db 0x9A
    db 0xCF
    db 0

    dw 0x0FFFF
    dw 0
    db 0
    db 0x92
    db 0xCF
    db 0
gdt_end:
toc:
    dw gdt_end - gdt32 - 1
    dd gdt32             ; The GDT base is simplified since our segment is now 0

bits 32
section .text
start32:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x9c000    ; Set the stack to grow down from area under BDA/Video memory

    ; We need to zero out the BSS section. We'll do it a DWORD at a time
    cld
    lea edi, [__bss_start] ; Start address of BSS
    lea ecx, [__bss_sizel] ; Lenght of BSS in DWORDS
    xor eax, eax           ; Set to 0x00000000
    rep stosd              ; Do clear using string store instruction

    call kmain

我有一個專用的鏈接程序腳本,該腳本將引導加載程序置於0x7c00,將內核置於0x7e00。

有什么問題,我該如何解決? 如果需要更多信息,我已經提供了我的git repo

TL; DR :您還沒有使用bootloader在start.asm中將整個內核讀入內存。 缺少代碼和/或數據會導致您的內核因三重錯誤而崩潰,從而導致重新啟動。 隨着內核的增長,您將需要讀取更多的扇區。


我注意到您生成的lunaos.img大於1024個字節。 引導加載程序為512字節,之后的內核略大於512字節。 這意味着內核現在跨越多個扇區。 在您的kernel.asm ,使用以下代碼加載單個512字節的扇區:

load_kernel:
    mov ah, 0x02                ; call function 0x02 of int 13h (read sectors)
    mov al, 0x18                ; read one sector (512 bytes)
    mov ch, 0x00                ; track 0
    mov cl, 0x02                ; sector 2
    mov dh, 0x00                ; head 0
;    mov dl, 0x00               ; drive 0, floppy 1. Comment out DL passed to bootloader
    xor bx, bx                  ; segment 0x0000
    mov es, bx                  ; segments must be loaded from non immediate data
    mov bx, 0x7E00              ; load the kernel right after the bootloader in memory
.readsector:
    int 13h                     ; call int 13h
    jc .readsector              ; error? try again

尤其是:

mov al, 0x01                ; read one sector (512 bytes)

這是您問題的核心。 由於您是以軟盤啟動的,因此建議您生成一個1.44MiB文件,並使用以下命令將其中的Bootloader和內核放入其中:

dd if=/dev/zero of=bin/lunaos.img bs=1024 count=1440
dd if=bin/os.bin of=bin/lunaos.img bs=512 conv=notrunc seek=0

第一個命令使一個1.44MiB文件充滿零。 第二個使用conv=notrunc告訴DD在寫入后不截斷文件。 seek=0告訴DD在文件的第一個邏輯扇區開始寫入。 結果是將os.bin放置在從邏輯扇區0開始的1.44MiB映像內部,完成后不會截斷原始文件。

具有已知軟盤大小的適當大小的磁盤映像可以使其更易於在某些仿真器中使用。

一個1.44MiB軟盤每磁道有36個扇區 (每磁頭18個扇區,每磁道2個磁頭)。 如果您在真實的硬件上運行代碼,則某些BIOS可能不會跨磁道邊界加載。 您可以通過磁盤讀取安全地讀取35個扇區。 BIOS從磁道0磁頭0讀取第一個扇區。在第一個磁道上還有35個扇區。 我將上面的行修改為:

mov al, 35                ; read 35 sectors (35*512 = 17920 bytes)

這樣,即使在實際硬件上,內核也可以保持35 * 512字節長= 17920字節,而將麻煩降到最低。 大於此值的任何內容,都必須考慮使用一個試圖讀取多個軌道的循環來修改引導加載程序。 使事情復雜化的是,您必須擔心自己,更大的內核最終將超過64k段的限制。 磁盤讀取可能必須修改為使用不為0的段( ES )。如果內核變大,則可以在此時修復引導加載程序。


調試

由於您處於保護模式並使用QEMU ,我強烈建議您考慮使用調試器。 QEMU支持使用GDB進行遠程調試。 設置並不難,並且由於您已生成內核的ELF可執行文件,因此您也可以使用符號調試。

您將需要在-Fdwarf之后-felf32-Fdwarf添加到您的NASM組裝命令中,以啟用調試信息。 -g選項添加到GCC命令以啟用調試信息。 以下命令應啟動您的引導程序/內核; kmain上自動中斷; 使用os.elf作為調試符號; 並在終端中顯示源代碼和寄存器。

qemu-system-i386 -fda bin/lunaos.img -S -s &

gdb bin/os.elf \
        -ex 'target remote localhost:1234' \
        -ex 'layout src' \
        -ex 'layout regs' \
        -ex 'break *kmain' \
        -ex 'continue'

如果您通過Google搜索,則有許多有關使用GDB的教程。 有一個備忘單 ,描述了大多數基本命令及其語法。


如果將來發現自己遇到中斷,GDT或分頁的問題,我建議您使用Bochs調試操作系統的那些方面。 盡管Bochs沒有符號調試器,但它比QEMU更容易識別低級問題。 Bochs中 ,像Bootloader這樣的實模式代碼調試起來更容易,因為它可以理解20位段:與QEMU不同的偏移量尋址

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM