【问题标题】:GCC for ARM -- ELF output file segment misplacedARM 的 GCC -- ELF 输出文件段错位
【发布时间】:2019-01-11 17:50:57
【问题描述】:

编辑添加:我现在已将其交叉发布到 GNU ARM Embedded Toolchain 网站,因为我相当确定这是一个链接器错误。

另外,我注意到它似乎发生在第一个程序段适合 ELF 文件的第一页时(即它在其页面内的起始偏移量 >= ELF 标头中的字节数)。在这种情况下,该段错误地向下延伸到文件的开头。这可以解释为什么如果起始地址的页内偏移量从 0x80 减少到 0x40,问题就会消失。


我正在为 ARM Cortex M0 实现一个独立的操作系统,但我的链接器有一个奇怪的问题。这是我的源文件OS.c,为了说明问题而进行了精简:

int EntryPoint (void) { return 99 ; }

这是我的链接器脚本文件OS.ld,只需将所有代码分配给从0x10080开始的区域:

MEMORY
  {
  NVM (rx) : ORIGIN = 0x10080, LENGTH = 0x1000
  }

SECTIONS
  {
  .text 0x10080 :
    {
    OS.o (.text)
    } > NVM
  }

我编译并链接它:

arm-none-eabi-gcc.exe -march=armv6-m -mthumb -c OS.c
arm-none-eabi-gcc.exe -oOS.elf -Xlinker --script=OS.ld OS.o -nostartfiles -nodefaultlibs

现在当我用readelf OS.elf -l 列出程序段时,我得到:

Elf file type is EXEC (Executable file)
Entry point 0x10080
There are 1 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00010000 0x00010000 0x0008c 0x0008c R E 0x10000

据此,唯一的程序段在 ELF 输出文件中的偏移量 0x000000 处开始,这很疯狂:该区域包含与操作系统无关的 ELF 标头信息。而物理起始地址是0x00010000,在我的硬件中是不存在的。

但奇怪的是,如果我在链接器脚本文件中将0x10080 的两个实例都更改为0x10040,它就可以工作!我明白了:

Elf file type is EXEC (Executable file)
Entry point 0x10040
There are 1 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x010040 0x00010040 0x00010040 0x0000c 0x0000c R E 0x10000

现在程序段在文件中的正确位置,长度为0x0000c 而不是0x0008c。不幸的是地址0x00010040在我的硬件中也不存在,所以这不是一个解决方案。

这是 GCC ARM 编译器中的错误吗?使用--version 运行它会得到:

arm-none-eabi-gcc.exe (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907]

【问题讨论】:

  • 我想我已经看到了很多年并且没有注意到这种细微差别,尽管使用 objcopy 或 openocd 或其他类似的东西,elf 文件工作得很好。由于您使用 gcc 调用 ld 而不是直接调用 ld ,因此您还应该包括您正在使用的 ld 版本。
  • launchpad 是否有遗留原因?并且是一个获得预建的地方,如果有什么可以去 binutils 支持这样的东西,你应该这样做。我的二进制文件是直接从源代码构建的并显示了问题(多年来,gcc 的许多主要修订版和/或 binutils 的许多版本,可能回到 1.x.x)
  • 有更简单的方法来获取您想要的入口点顺便说一句...imo...有趣的是会显示您为某些地址而不是其他地址描述的这个问题。
  • @old_timer:入口点很好,我没有问题。问题是代码段从一个不存在的地址开始。所以当我自己的软件从 ELF 文件中读取段时,它会看到一个无效的地址。我已修复我的软件以忽略此特定错误,但最好不必这样做。
  • 是的,如前所述,多年来一直看到这一点,并且同意你的看法,这很容易理解。您是否确认问题与 readelf 无关? (你自己检查过elf文件吗?)

标签: gcc arm embedded linker-scripts


【解决方案1】:

您看到的可能不是您所期望的,但仍然是正确的,恕我直言。

ELF 是为 System V 创建的。支持虚拟内存和mmap()(将文件内容映射到内存的系统调用)的操作系统。

您正在查看 ELF 程序标题(不是节标题,见下文)。程序标头是(支持虚拟内存的)操作系统的 ELF 加载器的信息,关于它应该将(完整的)ELF 文件mmap() 到它准备为进程映像的虚拟内存中的位置。然后,该操作系统将在某处分配一个(或多个)页面,调用该(虚拟)0x10000(用于该进程),映射文件并跳转到 0x10080(入口点)。

对于您的第二个示例,这将不起作用,因为您指定了(虚拟)起始地址 ELF 文件头(ELF 头 + 程序头 + 节头)的结尾,所以它不能只需将文件映射到页面边界,使操作系统更复杂(甚至不可能)做到这一点mmap() 技巧。

对于您的裸机操作系统(很可能不支持虚拟内存,至少在启动时不支持),ELF 程序头的信息可能完全不相关。

您可能更应该查看部分标题。它们描述了物理内存。

【讨论】:

  • 不,这是不对的。如果第一个区域的页面偏移量小于 ELF 文件头的大小,则它会正确映射到文件中的第二个页面,在页面内的正确偏移量处。如果需要,操作系统仍然可以执行其“映射技巧”,并且我的程序可以将段直接加载到现有内存中。
  • 我尝试加载各个部分而不是程序段。但这遗漏了操作系统应该在程序启动时从 NVM 复制到 RAM 的初始化 RAM 数据。我对此感到相当惊讶,但我找不到解决方法。
  • 顺便说一句,我已经完成了一个修复:我在链接描述文件文件中创建了一个额外的虚拟段,只是为了将 ELF 文件头增长到超过 0x80 字节。现在一切正常,但我无法摆脱我做错事的感觉。
  • 您的 M0 没有 MMU,因此 - 没有虚拟内存。我假设您别无选择,只能将各个部分加载到物理内存中。
  • 我可以使用没有混淆的不同链接器脚本来创建这个问题,或者任何类型的花哨功能都非常简单。我已经能够以某种方式加载文件并从这种奇怪的情况下运行它们,不知道此时如何。 elf 文件肯定是不正确的,标题中的偏移量没有填充它是零,它指向前面的 ELF,除了坏文件之外,没有其他方法可以感知到这一点。基地址的细微变化虽然会使这种情况消失或再次出现。
【解决方案2】:

我对 ARM Cortex-M 平台的 GNU 链接器(GNU ld (Atmel build: 508) 2.28.0.20170620)有非常相似的问题。我有引导加载程序和应用程序项目,其中来自应用程序的链接器将 ELF 标头放置在引导加载程序代码所在的闪存位置。我不是专家,但是这种修改欺骗了我的链接器,不要将 ELF 标头放在入口点地址之前的内存空间中(将尝试在您的示例中显示):

  1. 通过包含前 0x80 个字节重新定义 NVM 空间

     NVM (rx) : ORIGIN = 0x10000, LENGTH = 0x1000+0x80
    
  2. 在部分中添加该偏移量:

     SECTIONS
     {
         .text :
         {
             . += 0x80;
             OS.o (.text)
         } > NVM
     }
    

我不确定这是否适用于您的情况,但也许可以用作其他人的提示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    • 2016-11-07
    相关资源
    最近更新 更多