C 任何试图在保护模式下将字符串放入屏幕的行为都会导致重新启动
我最近在从头开始开发操作系统时进入了保护模式。我已经成功地进入了C语言,并制作了将字符打印到屏幕上的函数(感谢Michael Petch帮助我达到这个阶段)。不管怎样,每当我尝试创建一个例程,循环遍历字符串文本并打印其中的每个字符时,都会出现一点问题。QEMU只是进入一个引导循环,一次又一次地重新启动,我永远无法在黑色视频模式下看到我美丽的绿色。如果我将其从例程中移出,并在C 任何试图在保护模式下将字符串放入屏幕的行为都会导致重新启动,c,string,x86,osdev,protected-mode,C,String,X86,Osdev,Protected Mode,我最近在从头开始开发操作系统时进入了保护模式。我已经成功地进入了C语言,并制作了将字符打印到屏幕上的函数(感谢Michael Petch帮助我达到这个阶段)。不管怎样,每当我尝试创建一个例程,循环遍历字符串文本并打印其中的每个字符时,都会出现一点问题。QEMU只是进入一个引导循环,一次又一次地重新启动,我永远无法在黑色视频模式下看到我美丽的绿色。如果我将其从例程中移出,并在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]);
}
}
我在内核的开头有一个程序集存根,它进入保护模式,将BSS部分归零,发出CLD并调用我的C代码:
我有一个专门的链接器脚本,它将引导加载程序放在0x7c00,内核放在0x7e00
有什么问题?我如何解决?如果需要更多信息,我会提供我的帮助。TL;DR:您还没有使用
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文件,并将引导加载程序和内核放入其中,其中包括:
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映像中,完成时不会截断原始文件
已知软盘大小的适当大小的磁盘映像使其更易于在某些模拟器中使用
A(每人18个扇区,每条赛道2个磁头)。如果在真实硬件上运行代码,则可能无法跨轨迹边界加载。通过磁盘读取,您可能可以安全地读取35个扇区。BIOS从磁道0磁头0读取第一个扇区。第一轨道上还有35个扇区。我将上面的行修改为:
mov al, 35 ; read 35 sectors (35*512 = 17920 bytes)
这将允许您的内核长度为35*512字节=17920字节,即使在真正的硬件上,也会有最小的麻烦。任何大于此,你将不得不考虑修改你的Bootloader与试图读取不止一个轨道的循环。要使事情复杂化,您必须担心较大的内核最终会超过64k段限制。可能必须修改磁盘读取以使用非0的段。如果你的内核有那么大,你的引导加载程序可以在那个时候修复
调试
由于您处于受保护模式并使用QEMU,我强烈建议您考虑使用调试器。QEMU支持使用GDB进行远程调试。设置并不困难,因为已经生成了内核的ELF可执行文件,所以也可以使用符号调试
您需要在-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'
如果你用谷歌搜索,有很多关于使用GDB的教程。有一个描述大多数基本命令及其语法的示例
如果您将来发现自己在中断、GDT或分页方面有问题,我建议您使用Bochs来调试操作系统的这些方面。尽管Bochs没有符号调试器,但它弥补了QEMU无法识别低级问题的缺陷。在Bochs中调试像bootloader这样的实模式代码更容易,因为它理解20位段:偏移量寻址,而不是QEMU所讨论的代码,而不是存储库。@Martin,我很抱歉!我现在只能使用手机,所以无法正确格式化。你安装过IDT吗?!现在是个好时机。这看起来像是一个三重错误,当双错误处理程序cr*ps事件时会发生。@AnttiHaapala IDT。。。中断描述符表?(根据de.wikipedia.org的说法也是如此。)@Scheff当然,但我们不关心这里的德国人:)
mov al, 35 ; read 35 sectors (35*512 = 17920 bytes)
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'