c - 如何正确链接 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的回答,现在可以让这个噩梦运行起来。
解决方案
你的程序发生了很多奇怪的事情,所以我不会试图解决这个问题,而是从头开始,从头开始做一些正确的事情。
您的引导加载程序大部分都很好。正如您已经注意到的,您不能在引导加载程序中引用内核中的符号。默认的解决方案是直接跳转到内核中的一个已知位置(例如开头),然后为内核安排一些东西,以便在那里有它的入口点。所以我们更改boot.asm
并删除EXTERN _start
,将其替换为
_start EQU 0x7e00
要让内核可靠地在 处输入0x7e00
,有一个窍门。在链接描述文件中,我们将以下几行置于.text
部分的开头linker32.ld
:
.text : AT(0x7E00)
{
_start = .;
BYTE(0xE9);
LONG(_main - _start - 5);
这使得.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/ :
{
*(*);
}
}
您可以在 中以类似方式修复丢弃的部分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)。
推荐阅读
- sql - 使用 XML PATH 模式将兄弟姐妹分配给其正确的父母
- c# - 如何对下拉列表数据文本字段进行 html 解码?
- debugging - 调试在 Windows 应用程序上运行的 javascript 网页
- c - FFTW 单精度库在使用 SIMD 优化时输出不正确的 DFT
- swift - 尝试对物体进行 360 度旋转
- excel - 使用 VBA 通过屏幕抓取获取自定义元素
- idl-programming-language - 如何在 IDL 编程语言中为多个图分配多个标题
- python - 无法通过 pyenv 在 Ubuntu 20.04 上降级 Python 3.4 的 sqlite3.sqlite_version 以运行 tox 测试
- git - HG-Git:AttributeError:“字节”对象没有属性“编码”
- dbase - dbf 文件(dBase 7 格式)中的时间戳字段没有意义