multiboot2示例
编译
multiboot2的官网示例代码(https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html#Example-OS-code
)有三个文件: multiboot2.h
、boot.S
、kernel.c
。
将它们复制下来放到一个文件夹里分别编译kernel.c
和boot.S
gcc -m32 -c -fno-builtin -I. kernel.c -o kernel.o
gcc -m32 -c -fno-builtin -I. boot.S -o boot.o
在编译boot.S
时会报错
boot.S: Assembler messages:
boot.S:57: 错误:can't resolve `0' {.text section} - `L0' {*UND* section}
这是因为boot.S
代码里的GRUB_MULTIBOOT_ARCHITECTURE_I386
根本没有定义,将它在所有出现的地方改成MULTIBOOT_ARCHITECTURE_I386
即可。
链接
ld -m elf_i386 kernel.o boot.o -o kernel.bin
测试
把它放到img文件里用grub引导,grub会报错: error: ../../grub-core/loader/multiboot_mbi2.c:263:unsupported tag: 0x8.
解决方案是在framebuffer_tag_end
前添加.align 8
,这是因为multiboot2协议《Multiboot2 Specification version 2.0》的General tag structure
小节有说需要按8字节对齐。
Tags constitutes a buffer of structures following each other padded when necessary in order for each tag to start at 8-bytes aligned address.
这个示例里framebuffer_tag_end
不是8字节对齐,所以会报错,而framebuffer_tag
的位置刚好是8字节对齐,所以不用加。但为了避免麻烦最好是在每个tag前都加.align 8
。
如果不按上面的方法处理,framebuffer_tag_end
的位置为0x2c == 44
不是8的倍数:
objdump -d boot.o
boot.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: eb 32 jmp 34 <multiboot_entry>
2: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
00000008 <multiboot_header>:
8: d6 (bad)
9: 50 push %eax
a: 52 push %edx
b: e8 00 00 00 00 call 10 <multiboot_header+0x8>
10: 2c 00 sub $0x0,%al
12: 00 00 add %al,(%eax)
14: fe (bad)
15: ae scas %es:(%edi),%al
16: ad lods %ds:(%esi),%eax
17: 17 pop %ss
00000018 <framebuffer_tag_start>:
18: 05 00 01 00 14 add $0x14000100,%eax
1d: 00 00 add %al,(%eax)
1f: 00 00 add %al,(%eax)
21: 04 00 add $0x0,%al
23: 00 00 add %al,(%eax)
25: 03 00 add (%eax),%eax
27: 00 20 add %ah,(%eax)
29: 00 00 add %al,(%eax)
...
0000002c <framebuffer_tag_end>:
2c: 00 00 add %al,(%eax)
2e: 00 00 add %al,(%eax)
30: 08 00 or %al,(%eax)
而加了.align 8
后framebuffer_tag_end
地址就是0x30 == 48
是8的倍数。
Disassembly of section .text:
00000000 <_start>:
0: eb 36 jmp 38 <multiboot_entry>
2: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
00000008 <multiboot_header>:
8: d6 (bad)
9: 50 push %eax
a: 52 push %edx
b: e8 00 00 00 00 call 10 <multiboot_header+0x8>
10: 30 00 xor %al,(%eax)
12: 00 00 add %al,(%eax)
14: fa cli
15: ae scas %es:(%edi),%al
16: ad lods %ds:(%esi),%eax
17: 17 pop %ss
00000018 <framebuffer_tag_start>:
18: 05 00 01 00 18 add $0x18000100,%eax
1d: 00 00 add %al,(%eax)
1f: 00 00 add %al,(%eax)
21: 04 00 add $0x0,%al
23: 00 00 add %al,(%eax)
25: 03 00 add (%eax),%eax
27: 00 20 add %ah,(%eax)
29: 00 00 add %al,(%eax)
2b: 00 .byte 0x0
2c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
00000030 <framebuffer_tag_end>:
30: 00 00 add %al,(%eax)
32: 00 00 add %al,(%eax)
34: 08 00 or %al,(%eax)
...
GRUB相关的调试技巧
有时候,在grub的命令行里执行命令,一屏显示不完,就很容易看不到最前面输出了什么。解决这个问题可以采用如下方法:
修改grub.conf
,在里面添加
serial --unit=0 --speed=115200
terminal_input serial
terminal_output serial
将其写到磁盘或打包到光盘镜像文件里后,用qemu
启动
qemu-system-i386 \
-serial tcp::6666,server,nowait \
-drive file=kernel.iso,index=1,media=cdrom \
-s -S
然后连上它的串口输出
nc localhost 6666
然后gdb
通过localhost:1234
连上qemu
开始调试,就可以在nc
命令窗口看到grub
的输出了,也可以输入c回车
进入它的命令行,执行任何命令都可以看到全部结果。
也可以用-serial mon:stdio
的方式,但如果命令执行的方式不对,就可能造成虽然可以在串口看到输出,却无法输入,比如如下的脚本
qemu-system-i386 \
-serial mon:stdio \
-drive file=kernel.iso,index=1,media=cdrom \
-s -S &
pid=$!
echo "pid is ${pid}"
i386-elf-gdb KERNEL.ELF -x gdbscript; kill -9 $pid
echo "kill pid ${pid}"
这里的核心问题在于’&‘,把qemu
放到后台执行了。就响应不了输入。因此为了避免这个问题就需要把这个脚本拆成两部分,分别执行。
启动qemu
的脚本
qemu-system-i386 \
-serial mon:stdio \
-drive file=kernel.iso,index=1,media=cdrom \
-s -S
使用gdb
连接qemu
调试的脚本
i386-elf-gdb KERNEL.ELF -x gdbscript