如果你看objdump -h
输出,您将看到虚拟地址和线性地址与任何部分都不匹配。如果你看objdump -d
输出,您会看到地址都在 0xC0100000 范围内。
但是,您没有在地址信息中提供任何地址信息。多重引导头结构 http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format;您只需提供最少三个字段。相反,引导加载程序将选择一个好的地址(x86 上的 1M,即 0x00100000,对于虚拟地址和线性地址),并在那里加载代码。
人们可能会认为这种差异应该导致内核根本无法运行,但碰巧的是上面生成的代码main.c
除了只读常量之外,不将地址用于任何其他用途。特别是,GCC 生成使用相对地址(相对于 x86 上下一条指令的地址的有符号偏移量)的跳转和调用,因此代码仍然可以运行。
有两种解决方案,第一种很简单。
x86 上的大多数引导加载程序都会在允许的最小虚拟和线性地址 1M (= 0x00100000 = 1048576) 处加载映像。因此,如果您告诉链接器脚本使用从 0x00100000 开始的虚拟地址和线性地址,即
.grub_sig 0x00100000 : AT(0x100000)
{
*(.grub_sig)
}
你的内核将会正常工作。我已经验证这可以解决您遇到的问题,删除额外的void main(void)
当然,来自您的链接描述文件。具体来说,我构建了一个33MB的虚拟磁盘,其中包含一个ext2分区,在其上安装了grub2(使用1.99-21ubuntu3.10)和上述内核,并在qemu-kvm 1.0(1.0+noroms-0ubuntu14)下成功运行该映像.11)。
第二个选项是设置多重引导标志中的位 16,并提供必要的五个附加字来告诉引导加载程序代码预期驻留在何处。然而,0xC0100000 不会工作——至少 grub2 会崩溃并重新启动——而像 0x00200000 这样的东西却可以正常工作。这是因为多重引导实际上被设计为使用虚拟==线性地址,并且最高地址处可能已经存在其他内容(类似于为什么避免低于1M的地址)。
请注意,引导加载程序不为您提供堆栈,因此代码的工作原理有点令人惊讶。
我个人建议您使用简单的汇编文件来构造签名,并保留一些堆栈空间。例如,start.asm
简化自here http://www.cs.vu.nl/~herbertb/misc/writingkernels.txt,
BITS 32
EXTERN main
GLOBAL start
SECTION .grub_sig
signature:
MAGIC equ 0x1BADB002
FLAGS equ 0
dd MAGIC, FLAGS, -(MAGIC+FLAGS)
SECTION .text
start:
mov esp, _sys_stack ; End of stack area
call main
jmp $ ; Infinite loop
SECTION .bss
resb 16384 ; reserve 16384 bytes for stack
_sys_stack: ; end of stack
编译使用
nasm -f elf start.asm -o start.o
并修改您的链接器脚本以使用start
代替main
作为入口点,
ENTRY(start)
从你的系统中删除多重启动的东西main.c
,然后编译并链接到kernel
使用例如
gcc -Wall -nostdlib -ffreestanding -fno-stack-protector -O3 -fomit-frame-pointer -m32 -c main.c -o main.o
ld -T linker.ld -nostdlib -n -melf_i386 start.o main.o -o kernel
这样您就可以开始开发自己的内核了。
问题?评论?