Rap*_*MdN 3 c x86 makefile ld osdev
我最近更换了我的计算机,从那时起,我的 makefile 链会输出一个 512 字节的二进制文件,其中只有 0x00s 或引导加载程序,但没有其他任何内容。我将以下内容创建为 MRE:
引导.asm:
BITS 16
SECTION boot
GLOBAL _entry
EXTERN _start
_entry:
mov [disk],dl
mov ah, 0x2 ; read sectors
mov al, 6 ; amount = 6
mov ch, 0 ; zylinder = 0
mov cl, 2 ; first sector to read = 2
mov dh, 0 ; head = 0 (up)
mov dl, [disk] ; disk
mov bx, _start ; segment:offset address
int 0x13
cli
lgdt [GDT_POINTER]
mov eax, cr0
or al, 1
mov cr0, eax
mov ax, DATA_SEGMENT
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp CODE_SEGMENT:_start
disk: DB 0x00
GDT_POINTER:
DW GDT_EXIT - GDT_ENTRY
DD GDT_ENTRY
CODE_SEGMENT EQU GDT_CODE - GDT_ENTRY
DATA_SEGMENT EQU GDT_DATA - GDT_ENTRY
GDT_ENTRY:
DQ 0x00
GDT_CODE:
DW 0xffff
DW 0x0000
DB 0x00
DB 0x9a
DB 0xcf
DB 0x00
GDT_DATA:
DW 0xffff
DW 0x0000
DB 0x00
DB 0x92
DB 0xcf
DB 0x00
GDT_EXIT:
TIMES 510 - ($ - $$) DB 0x00
DW 0xAA55
Run Code Online (Sandbox Code Playgroud)
内核.c:
BITS 16
SECTION boot
GLOBAL _entry
EXTERN _start
_entry:
mov [disk],dl
mov ah, 0x2 ; read sectors
mov al, 6 ; amount = 6
mov ch, 0 ; zylinder = 0
mov cl, 2 ; first sector to read = 2
mov dh, 0 ; head = 0 (up)
mov dl, [disk] ; disk
mov bx, _start ; segment:offset address
int 0x13
cli
lgdt [GDT_POINTER]
mov eax, cr0
or al, 1
mov cr0, eax
mov ax, DATA_SEGMENT
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp CODE_SEGMENT:_start
disk: DB 0x00
GDT_POINTER:
DW GDT_EXIT - GDT_ENTRY
DD GDT_ENTRY
CODE_SEGMENT EQU GDT_CODE - GDT_ENTRY
DATA_SEGMENT EQU GDT_DATA - GDT_ENTRY
GDT_ENTRY:
DQ 0x00
GDT_CODE:
DW 0xffff
DW 0x0000
DB 0x00
DB 0x9a
DB 0xcf
DB 0x00
GDT_DATA:
DW 0xffff
DW 0x0000
DB 0x00
DB 0x92
DB 0xcf
DB 0x00
GDT_EXIT:
TIMES 510 - ($ - $$) DB 0x00
DW 0xAA55
Run Code Online (Sandbox Code Playgroud)
链接器16.ld:
ENTRY(_entry);
OUTPUT_FORMAT(elf32-i386);
OUTPUT_ARCH(i386);
SECTIONS
{
. = 0x7C00;
.text : AT(0x7C00)
{
*(boot)
*(.text)
}
.data :
{
*(.bss);
*(.bss*);
*(.data);
*(.rodata*);
*(COMMON);
}
/DISCARD/ :
{
*(.note*);
*(.iplt*);
*(.igot*);
*(.rel*);
*(.comment);
}
}
Run Code Online (Sandbox Code Playgroud)
链接器32.ld:
ENTRY(_main);
OUTPUT_FORMAT(elf32-i386);
OUTPUT_ARCH(i386);
SECTIONS
{
. = 0x7E00;
.text : AT(0x7E00)
{
*(.text)
}
.data :
{
*(.bss);
*(.bss*);
*(.data);
*(.rodata*);
*(COMMON);
}
/DISCARD/ :
{
*(.note*);
*(.iplt*);
*(.igot*);
*(.rel*);
*(.comment);
}
}
Run Code Online (Sandbox Code Playgroud)
生成文件:
all:
nasm -O32 -f elf -o boot.o boot.asm
gcc -m32 -c -g -ffreestanding -nostdlib -nostdinc -Wall -Werror -o kernel.o kernel.c
ld -static -nostdlib -build-id=none -relocatable -T linker16.ld -o boot.elf boot.o
ld -static -nostdlib -build-id=none -relocatable -T linker32.ld -o kernel.elf kernel.o
objcopy -O binary boot.elf boot.bin
objcopy -O binary kernel.elf kernel.bin
cat boot.bin kernel.bin > sys.bin~
rm *.o
rm *.elf
rm *.bin
cat sys.bin~ > sys.bin
rm sys.bin~
qemu-system-i386 sys.bin
qemu:
qemu-system-i386 sys.bin
Run Code Online (Sandbox Code Playgroud)
预期的输出是一个空白屏幕,当查看兼容监视器(“信息寄存器”输出)时,GDT 在 0x7C00 之后设置了几个字节。相反,它被困在引导循环中,因为引导加载程序已正确编译,但它之后的所有内容(while 循环)都丢失了。在 .o 文件之前,一切都按预期进行,但 .elf 和 .bin 太短了。有人有解决方案吗?我使用的版本是:
NASM 版本 2.14.02
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
GNU ld & objcopy (GNU Binutils for Ubuntu) 2.34
编辑:更新后的代码反而会产生一堆零,是它应该大小的 60 倍。幻数放置正确,但内核部分仍然无法使用。
编辑 2:我通过反复试验发现,删除链接器的 -relocable 参数会清除大部分零,但它仍然无法按预期工作并停留在引导循环中。
编辑 3:如果有人遇到和我一样的问题,我希望代码能够实际工作。在上面的代码中,我修复了 GDT,因为我犯了一个错误。我将所有 DB 缩小到 DD,但忘记了小端会反转其中的所有字节,因此所有 GDT 描述符中的 used 位都设置为零,从而无法进行跳转。结合fuz的回答,现在有可能让这个噩梦跑起来。
你的程序发生了一些奇怪的事情,所以我不会试图解决这个问题,而是继续从头开始做一些正确的事情。
你的引导加载程序基本没问题。正如您已经注意到的,您不能在引导加载程序中引用内核中的符号。默认的解决方案是直接跳转到内核中的一个已知位置(例如开头),并为内核安排一些东西,以便在那里有它的入口点。所以我们更改boot.asm并删除EXTERN _start,将其替换为
_start EQU 0x7e00
Run Code Online (Sandbox Code Playgroud)
要使内核可靠地在 处可输入0x7e00,有一个技巧。在链接描述文件中,我们将以下几行放入 中.text部分的开头linker32.ld:
.text : AT(0x7E00)
{
_start = .;
BYTE(0xE9);
LONG(_main - _start - 5);
Run Code Online (Sandbox Code Playgroud)
这使得.text开始以JMP跳转到的指令_main,这正是我们想要的。
接下来是将随机垃圾附加到内核的问题。这是因为你没有丢弃足够的垃圾。最简单的方法是丢弃所有内容(即*(*))并明确列出您想要保留的部分。不过你需要小心;编译器可能会决定将额外的垃圾放入保持内核工作所需的奇怪部分。或者,接受编译器做任何它想做的事情并吃掉更大的内核大小。最终的链接脚本linker32.ld是这样的:
OUTPUT_FORMAT(elf32-i386);
OUTPUT_ARCH(i386);
SECTIONS
{
. = 0x7E00;
.text : AT(0x7E00)
{
_start = .;
BYTE(0xE9);
LONG(_main - _start - 5);
*(.text);
*(.text.*);
}
.data :
{
*(.bss);
*(.bss*);
*(.data);
*(.rodata*);
*(COMMON);
}
/DISCARD/ :
{
*(*);
}
}
Run Code Online (Sandbox Code Playgroud)
您可以在 中类似地修复丢弃的部分linker16.ld。
接下来是构建脚本。我不会详细讨论这一点,但您可以查看我自己所做的更改。两个重要的是(a)删除-relocatable(这绝对不是您想要的)和(b)添加-fno-pic -no-pie以便编译器不会得到任何奇怪的想法。
all:
nasm -f elf32 boot.asm
gcc -m32 -c -g -fno-pic -no-pie -ffreestanding -nostdlib -nostdinc -Wall -Werror -o kernel.o kernel.c
ld -static -nostdlib -build-id=none -T linker16.ld -o boot.elf boot.o
ld -static -nostdlib -build-id=none -T linker32.ld -o kernel.elf kernel.o
objcopy -O binary boot.elf boot.bin
objcopy -O binary kernel.elf kernel.bin
cat boot.bin kernel.bin > sys.bin
qemu-system-i386 sys.bin
qemu:
qemu-system-i386 sys.bin
Run Code Online (Sandbox Code Playgroud)
它应该像这样工作,假设引导加载程序是正确的(我在这台计算机上没有 QEMU)。