无法分配链接描述文件中定义的变量的地址

标签 c raspberry-pi arm gdb qemu

我找到了解决方案,尽管我不明白出了什么问题。这是原来的问题。解决方案在最后。


我正在关注这个Raspberry PI OS tutorial经过一些调整。正如标题所示,一项作业似乎失败了。

这是我的 C 代码:

extern int32_t __end;
static int32_t *arena;

void init() {
    arena = &__end;
    assert(0 != arena); // fails
    ...

断言触发!当然地址不应该是 0。 __end 在我的链接器脚本中声明:

ENTRY(_start)

SECTIONS
{
    /* Starts at LOADER_ADDR. 0x8000 is a convention. */
    . = 0x8000;
    __start = .;
    .text : {
        *(.text)
    }
    .rodata : { *(.rodata) }
    .data : { *(.data) }
    /* Define __bss_start and __bss_end for boot.s to set to 0 */
    __bss_start=.;
    .bss : { *(.bss) }
    __bss_end=.;
    /* First usable address for the allocator */
    . = ALIGN(4);
    __end = .;
}

研究 GDB(在 QEMU 中运行它):

Thread 1 hit Breakpoint 1, init () at os.c:75
75      arena = &__end;
(gdb) p &__end
$1 = (int32_t *) 0x9440
(gdb) p arena
$2 = (int32_t *) 0x0
(gdb) n
76      assert(0 != arena);
(gdb) p arena
$3 = (int32_t *) 0x0

GDB 可以找到 __end 但我的程序却找不到?

以下是我尝试过的其他一些方法:

  • 本教程的代码可以正常运行(这意味着 QEMU 和 ARM 编译器正在运行)
  • 在没有 GDB 的情况下运行时断言仍然失败(意味着 GDB 不是问题)
  • 我可以将 0xccc 分配给 arena(暗示 arena 不是问题)
  • 我无法将 &__end 分配给局部变量(暗示 &__end 是问题所在)。

根据评论中的要求,这就是我尝试分配给局部变量的方式:

void* arena2 = (void*)&__end;
assert(0 != arena2);

断言失败。在 GDB 中:

Thread 1 hit Breakpoint 1, mem_init () at mem.c:77
77      void* arena2 = (void*)&__end;
(gdb) p arena2
$1 = (void *) 0x13
(gdb) p &__end
$2 = (int32_t *) 0x94a4
(gdb) n
78      assert(0 != arena2);
(gdb) p arena2
$3 = (void *) 0x0
(gdb) p &__end
$4 = (int32_t *) 0x94a4
  • assert(0 != &__end); 成功(意味着 &__end 不是问题?)

注意此版本的 assertassert.h 中的版本不同,但我不认为它会导致问题。它只是检查条件,打印条件,然后转到断点。我可以在 GDB 中重现该问题,并将断言注释掉。

注意2。我之前包含了 C 代码的 ARM 汇编,以防出现编译器错误


我的解决方案是将链接描述文件编辑为:

ENTRY(_start)

SECTIONS
{
    /* Starts at LOADER_ADDR. 0x8000 is a convention. */
    . = 0x8000;
    __start = .;
    .text : {
        *(.text)
    }
    . = ALIGN(4096);
    .rodata : { *(.rodata) }
    . = ALIGN(4096);
    .data : { *(.data) }
    . = ALIGN(4096);
    /* Define __bss_start and __bss_end for boot.s to set to 0 */
    __bss_start = .;
    .bss : { *(.bss) }
    . = ALIGN(4096);
    __bss_end = .;
    /* First usable address for the allocator */
    . = ALIGN(4096);
    __end = .;
}

我不明白为什么额外的 ALIGN 很重要。

最佳答案

您遇到的问题是因为 boot.S 中的“清除 BSS”循环还清除了 C 代码在运行时使用的 ELF 文件中编译器生成的一些数据。值得注意的是,它意外地将 .got ELF 部分中的 GOT(全局偏移表)清零,也是链接器放置 __end 标签实际地址的位置。因此,链接器正确地填写了 ELF 文件中的地址,但随后 boot.S 代码将其清零,当您尝试从 C 读取它时,您会得到零,而不是您所期望的。

在链接器脚本中添加所有对齐方式可能会通过巧合地导致 GOT 不在被归零的区域中来解决此问题。

您可以使用“objdump -x myos.elf”查看链接器将内容放置在何处。在我基于您链接的教程的测试用例中,我看到一个符号表,其中包括其他条目:

000080d4 l       .bss   00000004 arena
00000000 l    df *ABS*  00000000 
000080c8 l     O .got.plt       00000000 _GLOBAL_OFFSET_TABLE_
000080d8 g       .bss   00000000 __bss_end
0000800c g     F .text  00000060 kernel_main
00008000 g       .text  00000000 __start
0000806c g       .text.boot     00000000 _start
000080d8 g       .bss   00000000 __end
00008000 g     F .text  0000000c panic
000080c4 g       .text.boot     00000000 __bss_start

所以你可以看到链接器脚本将__bss_start设置为0x80c4,__bss_end设置为0x80d8,这很遗憾,因为GOT位于0x80c4/0x80c8。我认为这里发生的情况是,因为您没有在链接器脚本中明确指定放置 .got 和 .got.plt 部分的位置,所以链接器决定将它们放在 __bss_start 分配之后和 .bss 部分之前,因此它们会被归零代码覆盖。

您可以使用“objdump --disassemble-all myos.elf”查看 .got 的 ELF 文件内容,其中包括:

Disassembly of section .got:

000080c4 <.got>:
    80c4:       000080d8        ldrdeq  r8, [r0], -r8   ; <UNPREDICTABLE>

所以你可以看到我们有一个GOT表条目,其内容是地址0x80d8,这是我们想要的__end值。当 boot.S 代码将其清零时,您的 C 代码会读取 0,而不是它所期望的常量。

您可能应该确保 bss 开始/结束至少 16 对齐,因为 boot.S 代码通过一次清除 16 个字节的循环工作,但我认为如果您将链接器脚本显式修复为将 .got 和 .got.plt 部分放在某处,然后您会发现不需要到处都进行 4K 对齐。

FWIW,我使用以下方法诊断了这一点:(1) QEMU“-d in_asm,cpu,exec,int,unimp,guest_errors -singlestep”选项来获取寄存器状态和指令执行的转储,以及(2) objdump ELF 文件来弄清楚编译器生成的代码实际上在做什么。我怀疑这将是“意外地将我们不应该拥有的数据归零”或“未能包含在图像中或以其他方式初始化我们应该拥有的数据”类型的错误,结果是这样。

哦,当你的代码没有打印正确的 __end 值时,GDB 的原因是 GDB 可以直接在 ELF 文件中的调试/符号信息中查找答案;它不是通过内存中的 GOT 来完成的。

关于无法分配链接描述文件中定义的变量的地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57782096/

相关文章:

c - 缓冲区溢出与环境变量

c++ - 检查指针句柄是否有效

c++ - 如何检测边界点

python - 无法删除python pip

gcc - STM32 ADC 连续转换模式不会自动启动转换

c - 如何比较两个void *的地址

arduino - Raspberry Pi 作为 I2C 中的 Slave 和 arduino 作为 Master

python - 如何使用 Python 将对象插入 MySQL?

ios - arc4random_uniform()和arm64的不同行为

gcc - NDK中的arm内联汇编,如何处理指针和指向的数据?