Skip to content

🔥 .bss 符号地址冲突:从"0x2a00000000"到"链接器布局问题"

一、问题现象

症状__boot_info_ptr 的值变成了奇怪的 0x2a00000000,而不是预期的 0x7000

D[0000002A00000000]
  ^^^^^^^^^^^^^^^^
  这个值 = 42 << 32

值的来源分析

cpp
// main.cpp
static int global_construction_count = 0;

class GlobalCounter {
public:
    GlobalCounter() {
        global_construction_count = 42;  // 0x2A = 42
        debugcon_putc('G');
    }
};

GlobalCounter global_counter;

0x2a00000000 = 42 << 32 明显与 global_construction_count = 42 相关!


二、符号表检查

使用 objdump 检查符号地址

bash
$ objdump -t ./build/kernel/mini/mini_kernel | grep -E "(boot_info|global_construction|global_counter|__bss)"

输出

ffffffff800226ec l     O .bss  0000000000000004 _ZL25global_construction_count
ffffffff8002042c l     F .text 0000000000000012 _GLOBAL__sub_I_global_counter
ffffffff800226f0 g       .bss  0000000000000000 __bss_end
ffffffff800226e8 g       .bss  0000000000000000 __boot_info_ptr
ffffffff800226e8 g     O .bss  0000000000000001 global_counter
ffffffff800206e0 g       .bss  0000000000000000 __bss_start

🔥 关键发现

ffffffff800226e8 g     O .bss  0000000000000001 global_counter
ffffffff800226e8 g       .bss  0000000000000000 __boot_info_ptr
                  ^^^^^^^^^^^^^^^^
                  同一个地址!

global_counter__boot_info_ptr 在同一个地址 0xffffffff800226e8


三、问题分析

内存布局图

地址                 变量                             大小
────────────────────────────────────────────────────────────
0xffffffff800226e8  global_counter                     1 byte
0xffffffff800226e8  __boot_info_ptr (被覆盖!)         8 bytes
0xffffffff800226ec  global_construction_count         4 bytes

执行流程

1. bootloader → _start: %rdi = 0x7000
2. movq %rdi, __boot_info_ptr → 地址 0x800226e8 写入 0x7000
3. rep stosb (BSS 清除) → 清除 0x800226e8 开始的区域
4. _init_global_ctors 调用
5. global_counter.GlobalCounter() 执行
6. global_construction_count = 42 → 写入 0x800226ec
7. 某种原因导致 0x800226e8 被写入 0x2a00000000

为什么是 0x2a00000000

可能的原因:

  1. 编译器优化导致的意外写入
  2. 栈溢出覆盖
  3. 链接器错误导致符号重叠

四、根本原因

链接器的行为

当多个符号被分配到同一个地址时,链接器可能:

  1. 让它们共享同一个地址(这是 bug!)
  2. 后面的符号覆盖前面的符号

不同来源的符号

符号来源
__boot_info_ptrboot.S (汇编).bss
global_countermain.cpp (C++).bss
global_construction_countmain.cpp (C++).bss

链接器在分配 .bss 段时,没有正确处理来自不同文件(.S.cpp)的符号。


五、修复方案

方案:将 __boot_info_ptr 移到 .data

修改前

asm
/* boot.S */
.section .bss
.global __boot_info_ptr
.skip 8
__boot_info_ptr:

修改后

asm
/* boot.S */
.section .data           /* ← 改为 .data */
.global __boot_info_ptr
.align 8
__boot_info_ptr:
    .quad 0              /* 显式初始化为 0 */

为什么 .data 段可以解决问题

  1. .data 段在链接时分配固定地址
  2. C++ 全局变量默认放在 .bss
  3. .data.bss 段不会重叠

六、验证修复

重新编译后的符号表

bash
$ objdump -t ./build/kernel/mini/mini_kernel | grep -E "(boot_info|global_construction|global_counter)"

输出

ffffffff800226e8 g     O .data 0000000000000008 __boot_info_ptr     ← 现在 .data 段!
ffffffff800226f0 l     O .bss  0000000000000004 _ZL25global_construction_count
ffffffff800226f4 g     O .bss  0000000000000001 global_counter       ← 不再冲突!

调试输出

OPLJ12R70003G4D[0000000000007000]
                  ^^^^^^^^^^^^^^^^
                  现在是正确的 0x7000!

七、完整修复代码

boot.S

展开代码 (共 22 行)收起代码
asm
/* ==============================================================
 * BootInfo Pointer Storage (.data section - NOT .bss!)
 * ============================================================== */
.section .data
.global __boot_info_ptr
.align 8
__boot_info_ptr:
    .quad 0

/* ==============================================================
 * Stack Section (.bss)
 * ============================================================== */
.section .bss
.align 16
.global __mini_stack
.global __mini_stack_top

.set MINI_STACK_SIZE, 0x2000    /* 8KB */

__mini_stack:
    .skip MINI_STACK_SIZE
__mini_stack_top:

链接器脚本确保

展开代码 (共 25 行)收起代码
makefile
/* kernel/mini/linker.ld */
SECTIONS
{
    . = KERNEL_Virt_BASE + KERNEL_PHYS_BASE;

    /* .data 段在 .bss 之前 */
    .text : AT(KERNEL_PHYS_BASE) {
        *(.text .text.*)
        *(.rodata .rodata.*)
    }

    .data : AT(KERNEL_PHYS_BASE + SIZEOF(.text)) {
        *(.data .data.*)
        __init_array_start = .;
        *(.init_array .init_array.*)
        __init_array_end = .;
    }

    .bss : {
        __bss_start = .;
        *(.bss .bss.*)
        *(COMMON)
        __bss_end = .;
    }
}

八、经验教训

1. 段的选择很重要

特点使用场景
.data有初始值,不会运行时清零需要持久化的全局变量、关键数据
.bss无初始值,运行时清零普通全局变量
.rodata只读常量

2. 汇编和 C++ 混合编程的注意事项

当同时使用 .S 文件和 .cpp 文件时:

  1. ✅ 汇编中定义的关键数据放在 .data 或特定的专用段
  2. ✅ 使用 extern 声明共享变量
  3. ✅ 用 objdump -t 检查符号地址是否冲突

3. 调试符号冲突的方法

bash
# 查看所有符号及其地址
objdump -t kernel.elf | sort

# 查看特定段的符号
objdump -t kernel.elf | grep "\.bss"

# 查看符号大小
objdump -t kernel.elf | grep "global_counter"

4. 神秘值的来源

当看到奇怪的值时:
0x2a00000000 → 42 << 32 → 检查值为 42 的变量
0xDEADBEEF   → 未初始化内存
0xFFFFFFFF   → -1 的无符号表示

九、相关概念

BSS 段的特点

.bss (Block Started by Symbol):
- 存放零初始化的全局变量和静态变量
- 不占用 ELF 文件空间(只记录大小)
- 程序启动时由运行时清零
- 可以被虚拟内存系统按需分页

.data 段的特点

.data:
- 存放有初始值的全局变量和静态变量
- 占用 ELF 文件空间(包含初始值)
- 程序启动时加载到内存
- 初始值在编译时确定

十、总结

当汇编和 C++ 混合编程时,避免将汇编定义的符号放在 .bss 段。

原因:

  1. .bss 段会被运行时清零,破坏保存的值
  2. 链接器可能将不同来源的符号分配到同一地址
  3. C++ 全局变量的初始化可能覆盖汇编符号

解决方案:

  1. 关键数据放在 .data 段,避免运行时清零
  2. 用 objdump 检查符号表,确认没有地址冲突
  3. 为关键符号显式初始化,避免意外覆盖

在这个案例中:

问题: __boot_info_ptr (0x800226e8) 与 global_counter (0x800226e8) 冲突
结果: __boot_info_ptr 被破坏成 0x2a00000000
解决: 将 __boot_info_ptr 移到 .data 段,地址变为 0x800226e8(独立)

十一、相关文件

文件修改内容
kernel/mini/arch/x86_64/boot.S__boot_info_ptr.bss 移到 .data
kernel/mini/linker.ld确保 .data.bss 之前

附录:完整的内存布局(修复后)

地址                     内容                          段
────────────────────────────────────────────────────────────────
0xFFFFFFFF80020000      _start (代码入口)              .text
0xFFFFFFFF80022000      (代码区)                      .text
0xFFFFFFFF800226e8      __boot_info_ptr = 0x7000      .data ✅
0xFFFFFFFF800226f0      global_construction_count     .bss
0xFFFFFFFF800226f4      global_counter                .bss
0xFFFFFFFF80022180      __mini_stack_top              .bss
0xFFFFFFFF80020180      __mini_stack (8KB)            .bss

注意.data.bss 现在完全分离,不会冲突!

035_multi_terminal-40-g5d72b8b · 5d72b8b · 2026-06-26