我试图写我自己的操作系统内核,并一直有一些问题,使链接正常工作之间的引导装载程序和(什么很快将是)我的内核(用C编写)。
我有以下代码…
src/bootloader.asm
; Allows our code to be run in real mode.
BITS 16
extern kmain
section .text
global _start
_start:
jmp Start
; Moves the cursor to row dl, col dh.
MoveCursor:
mov ah, 2
mov bh, 0
int 10h
ret
; Prints the character in al to the screen.
PrintChar:
mov ah, 10
mov bh, 0
mov cx, 1
int 10h
ret
; Set cursor position to 0, 0.
ResetCursor:
mov dh, 0
mov dl, 0
call MoveCursor
ret
Start:
call ResetCursor
; Clears the screen before we print the boot message.
; QEMU has a bunch of crap on the screen when booting.
Clear:
mov al, ' '
call PrintChar
inc dl
call MoveCursor
cmp dl, 80
jne Clear
mov dl, 0
inc dh
call MoveCursor
cmp dh, 25
jne Clear
; Begin printing the boot message.
Msg: call ResetCursor
mov si, BootMessage
NextChar:
lodsb
call PrintChar
inc dl
call MoveCursor
cmp si, End
jne NextChar
call kmain
BootMessage: db "Booting..."
End:
; Zerofill up to 510 bytes
times 510 - ($ - $$) db 0
; Boot Sector signature
dw 0AA55h
src/god.c
asm(".code16gcc");
// JASOS kernel entry point.
void kmain()
{
asm( "movb $0, %dl;"
"inc %dh;"
"movb $2, %ah;"
"movb $0, %bh;"
"int $0x10;"
"movb $'a', %al;"
"movb $10, %ah;"
"movw $1, %cx;"
"int $0x10;" );
while (1);
}
最后生成文件
bootloader: src/bootloader.asm
nasm -f elf32 src/bootloader.asm -o build/bootloader.o
god: src/god.c
i686-elf-gcc -c src/god.c -o build/god.o -ffreestanding
os: bootloader god
i686-elf-ld -Ttext=0x7c00 --oformat binary build/bootloader.o build/god.o -o bin/jasos.bin
bootloader目前非常简单。它只是键入“Booting...”并(尝试)加载kmain。但是,在打印字符串之后什么也没有发生。
当kmain
被调用时,我仍然处于实模式,所以我不认为失败是因为无法从内联程序集访问BIOS中断。如果我说错了请纠正我。
1条答案
按热度按时间mum43rcc1#
我不推荐GCC用于16位代码。GCC的替代方案可能是单独的IA16-GCC project,这是一个正在进行的工作,是实验性的。
因为需要内联汇编,所以很难让GCC发出正确的实模式代码。如果您希望避免细微的错误,特别是在启用优化时,GCC的内联汇编很难正确。写这样的代码是可能的,但我强烈建议不要这样做。
您没有链接器脚本,因此编译后的 C 代码被放置在引导加载程序签名之后。BIOS只将一个扇区读入内存。您的
jmp kmain
最终会跳转到内核实际加载到内存中时应该在的内存中,但它没有加载,因此无法按预期工作。您需要添加代码来调用BIOSInt 13/AH=2
来读取从Cylinder,Head,Sector(CHS)=(0,0,2)开始的其他磁盘扇区,这是 Bootstrap 之后的扇区。您的引导加载程序没有正确设置段寄存器。因为你使用的是GCC,所以它需要CS=DS=ES=SS。因为我们需要将数据加载到内存中,所以我们需要将堆栈放在安全的地方。内核将被加载到0x 0000:0x 7 e00,所以我们可以将堆栈放在引导加载程序0x 0000:0x 7 c 00下面,这样它们就不会冲突。在调用GCC之前,您需要使用
CLD
清除方向标志(DF),因为这是必需的。其中许多问题都在我的通用引导加载程序提示中得到了解决。在我的另一个Stackoverflow answer中可以找到一个更复杂的 Bootstrap ,它确定内核的大小(stage 2)并从磁盘读取适当数量的扇区。我们需要一个链接器脚本来正确地在内存中布局,并确保指令在一开始就跳转到真实的 C 入口点
kmain
。我们还需要正确地将BSS部分归零,因为GCC希望如此。链接器脚本用于确定BSS部分的开始和结束。函数zero_bss
将该内存清零为0x 00。Makefile
可以稍微清理一下,以便将来更容易添加代码。我已经修改了代码,所以目标文件被构建在src
目录中。这简化了制作过程。当实模式代码支持被引入并添加到GNU汇编程序中时,它在GCC中通过使用
asm (".code16gcc");
启用。一段时间以来,GCC一直支持-m16
选项,它可以做同样的事情。使用-m16
,您不需要将.code16gcc
指令添加到所有文件的顶部。我还没有修改将
a
打印到屏幕的内联程序集。只是因为我没有修改它,并不意味着它没有问题。由于寄存器被破坏,编译器没有被告知,这可能会导致奇怪的错误,特别是当优化打开时。这个答案的第二部分展示了一种使用BIOS将字符和字符串打印到控制台的机制。我推荐编译器选项
-Os -mregparm=3 -fomit-frame-pointer
来优化空间。Makefile:
link.ld:
src/god.c:
src/bootloader.asm:
创建一个名为
build/disk.img
的1.44MiB软盘映像。它可以在QEMU中运行,命令如下:预期输出应类似于:
使用BIOS正确使用Inline Assembly写字符串
下面是使用更复杂的GCC extended inline assembly的代码版本。这个答案并不意味着要讨论GCC的扩展内联汇编用法,但在线上有关于它的information。应该注意的是,有很多不好的建议,文档,教程和充满问题的示例代码是由可能对主题没有正确理解的人编写的。”你被警告!**1
Makefile:
link.ld:
src/biostty.c:
include/x86helper.h:
include/biostty.h:
src/god.c:
链接器脚本和引导加载程序与本答案中给出的第一个版本相比没有修改。
在QEMU中运行时,输出应该类似于:
脚注:
我已经提供了第二个代码示例作为最小的完整可验证示例,以显示正确的GCC内联程序集打印字符和字符串的样子。很少有文章展示如何使用GCC正确地做到这一点。第二个例子展示了在 C 函数内部编写汇编代码和编写带有低级内联汇编的 C 函数之间的区别,这些汇编代码用于BIOS调用等所需的事情。如果你打算使用 GCC 来 Package 整个汇编代码函数,那么在开始时用汇编编写函数会更容易,问题也更少。这违背了使用 C 的目的。