NASM与Clang/LLVM生成不同目标文件:Multiboot内核适配问题
Multiboot内核GNU AS版本无法运行的问题解决
问题背景
基于Multiboot标准编写简易内核时,NASM实现的Multiboot header可正常工作,但改用MacOS上Clang的GNU AS语法重写后出现异常。推测是Multiboot header被放置在8K以外的地址,导致引导加载器无法识别,且全程使用相同的链接脚本。
相关代码与输出
可正常运行的NASM代码
MBALIGN equ 1 << 0 MEMINFO equ 1 << 1 FLAGS equ MBALIGN | MEMINFO MAGIC equ 0x1BADB002 CHECKSUM equ -(MAGIC + FLAGS) section .multiboot_header header_start: align 4 dd MAGIC dd FLAGS dd CHECKSUM header_end: section .text global start start: mov dword [0xb8000], 0x2f4b2f4f hlt
无法正常运行的GNU AS代码
.set MBALIGN, 1 << 0 .set MEMINFO, 1 << 1 .set FLAGS, MBALIGN | MEMINFO .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) .section .multiboot_header header_start: .align 4 .long MAGIC .long FLAGS .long CHECKSUM header_end: .section .text .global start start: movl $0x2f4b2f4f, (0xb8000) hlt
链接脚本
ENTRY(start) SECTIONS { . = 1M; .boot : ALIGN(4K) { /* ensure that the multiboot header is at the beginning */ *(.multiboot_header) } .text : ALIGN (4K) { *(.text) } }
nm工具输出
kernel-nasm.bin: e4524ffb a CHECKSUM 00000003 a FLAGS 1badb002 a MAGIC 00000001 a MBALIGN 00000002 a MEMINFO 0010000c r header_end 00100000 r header_start 00101000 T start kernel-gas.bin: e4524ffb a CHECKSUM 00000003 a FLAGS 1badb002 a MAGIC 00000001 a MBALIGN 00000002 a MEMINFO 0000000c n header_end 00000000 n header_start 00100000 T start
编译命令(Homebrew LLVM 14.0.6)
# NASM版本编译 nasm -felf32 kernel-nasm.asm -o kernel-nasm.o ld.lld -n -o kernel-nasm.bin -T linker.ld kernel-nasm.o # GNU AS版本编译 as --target=i386-pc-none-elf kernel-gas.S -o kernel-gas.o ld.lld -n -o kernel-gas.bin -T linker.ld kernel-gas.o
objdump -x kernel-nasm.bin输出
kernel-nasm.bin: file format elf32-i386 kernel-nasm.bin architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x00101000 Program Header: LOAD off 0x00001000 vaddr 0x00100000 paddr 0x00100000 align 2**12 filesz 0x0000000c memsz 0x0000000c flags r-- LOAD off 0x00002000 vaddr 0x00101000 paddr 0x00101000 align 2**12 filesz 0x0000000b memsz 0x0000000b flags r-x STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**0 filesz 0x00000000 memsz 0x00000000 flags rw- Sections: Idx Name Size VMA LMA File off Algn 0 .boot 0000000c 00100000 00100000 00001000 2**12 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 0000000b 00101000 00101000 00002000 2**12 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .comment 0000001c 00000000 00000000 0000200b 2**0 CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 hdr.asm 00000001 l *ABS* 00000000 MBALIGN 00000002 l *ABS* 00000000 MEMINFO 00000003 l *ABS* 00000000 FLAGS 1badb002 l *ABS* 00000000 MAGIC e4524ffb l *ABS* 00000000 CHECKSUM 00100000 l .boot 00000000 header_start 0010000c l .boot 00000000 header_end 00101000 g .text 00000000 start
objdump -x kernel-gas.bin输出
kernel-gas.bin: file format elf32-i386 kernel-gas.bin architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x00100000 Program Header: LOAD off 0x00001000 vaddr 0x00100000 paddr 0x00100000 align 2**12 filesz 0x0000000b memsz 0x0000000b flags r-x STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**0 filesz 0x00000000 memsz 0x00000000 flags rw- Sections: Idx Name Size VMA LMA File off Algn 0 .boot 0000000c 00000000 00000000 00002000 2**12 CONTENTS, READONLY 1 .comment 0000001c 00000000 00000000 0000200c 2**0 CONTENTS, READONLY 2 .text 0000000b 00100000 00100000 00001000 2**12 CONTENTS, ALLOC, LOAD, READONLY, CODE SYMBOL TABLE: e4524ffb l *ABS* 00000000 CHECKSUM 00000003 l *ABS* 00000000 FLAGS 1badb002 l *ABS* 00000000 MAGIC 00000001 l *ABS* 00000000 MBALIGN 00000002 l *ABS* 00000000 MEMINFO 0000000c l .boot 00000000 header_end 00000000 l .boot 00000000 header_start 00100000 g .text 00000000 start
问题分析
对比两个版本的objdump输出可以发现:
- NASM版本的
.boot段被标记为ALLOC和LOAD,VMA(虚拟内存地址)是0x00100000(即1M地址,符合链接脚本的设置),会被加载到内存中。 - GNU AS版本的
.boot段没有ALLOC和LOAD标记,VMA是0x00000000,不会被加载到内存,导致引导加载器无法找到Multiboot header。
原因是GNU AS默认创建的.section没有ALLOC属性,而NASM的section指令默认会添加ALLOC属性,链接器只会处理带有ALLOC属性的段,将其纳入内存布局。
解决方法
修改GNU AS代码中的.multiboot_header段声明,显式添加ALLOC属性:
.set MBALIGN, 1 << 0 .set MEMINFO, 1 << 1 .set FLAGS, MBALIGN | MEMINFO .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) // 添加"a"属性表示ALLOC .section .multiboot_header,"a" header_start: .align 4 .long MAGIC .long FLAGS .long CHECKSUM header_end: .section .text .global start start: movl $0x2f4b2f4f, (0xb8000) hlt
重新编译后,.boot段会被正确标记为ALLOC和LOAD,VMA设置为0x00100000,引导加载器就能在内存的前8K范围内找到Multiboot header,内核即可正常启动。
内容的提问来源于stack exchange,提问作者Michael M.
相关产品推荐
相关产品推荐

