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

时间:2019-09-04 05:39:58

标签: 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(这意味着竞技场不是问题)
  • 我无法将&__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吗?)

assert的此版本与assert.h中的版本不同,但是我认为这不会引起问题。它只是检查条件,打印条件,然后转至断点。我可以在断言中声明断言的情况下在GDB中重现该问题。

N.B.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很重要。

1 个答案:

答案 0 :(得分:2)

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

在链接程序脚本中添加所有对齐方式可能通过同时导致GOT不在被置零的区域来解决此问题。

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

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文件以找出编译器生成的代码实际在做什么。我怀疑这可能是“我们不应该将数据意外地清零”或“未能包含在图像中或初始化我们应该具有的数据”这类错误,所以事实证明了。 / p>

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

相关问题