首页 > 解决方案 > 如何正确链接 16 位和 32 位 .o 文件?

问题描述

我最近换了电脑,从那时起,我的 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

内核.c:

int _main() {
    while(1) {}
}

链接器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);  
    }
}

链接器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);  
    }
}

生成文件:

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

预期的输出是一个空白屏幕,当查看兼容监视器(“信息寄存器”输出)时,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:我通过反复试验发现,删除链接器的 -relocatable 参数会清除大部分零,但它仍然无法按预期工作并停留在引导循环中。

编辑 3:如果有人遇到与我相同的问题,我希望代码能够实际工作。在上面的代码中,我修复了 GDT,因为我在其中犯了一个错误。我将所有 DB 缩小到 DD,但忘记了 little endian 会反转其中的所有字节,因此所有 GDT 描述符中的已使用位都设置为零,从而无法进行跳转。结合fuz的回答,现在可以让这个噩梦运行起来。

标签: cmakefilex86ldosdev

解决方案


你的程序发生了很多奇怪的事情,所以我不会试图解决这个问题,而是从头开始,从头开始做一些正确的事情。

您的引导加载程序大部分都很好。正如您已经注意到的,您不能在引导加载程序中引用内核中的符号。默认的解决方案是直接跳转到内核中的一个已知位置(例如开头),然后为内核安排一些东西,以便在那里有它的入口点。所以我们更改boot.asm并删除EXTERN _start,将其替换为

_start  EQU 0x7e00

要让内核可靠地在 处输入0x7e00,有一个窍门。在链接描述文件中,我们将以下几行置于.text部分的开头linker32.ld

.text : AT(0x7E00)
{
    _start = .;
    BYTE(0xE9);
    LONG(_main - _start - 5);

这使得.textJMP跳转到的指令开始_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/ :
    {
    *(*);
    }
}

您可以在 中以类似方式修复丢弃的部分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

它应该像这样工作,假设引导加载程序是正确的(我在这台计算机上没有 QEMU)。


推荐阅读