ZYB ARTICLES REPOS

multiboot2示例

编译

multiboot2的官网示例代码(https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html#Example-OS-code)有三个文件: multiboot2.hboot.Skernel.c

将它们复制下来放到一个文件夹里分别编译kernel.cboot.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 8framebuffer_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