ARM架构的GCC——ELF输出文件段错位

5
编辑补充:我现在已经将这个问题同时发布到GNU ARM嵌入式工具链网站上,因为我相当确定这是一个链接器的错误。
此外,我注意到当第一个程序段适合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版本。 - old_timer
Launchpad存在是为了遗留原因吧?那里可以获得预构建的软件包,如果需要的话,你应该获取binutils支持来解决这样的问题。我的二进制文件直接从源代码构建,并显示了这个问题(很多年来,可能有许多重大版本的GCC和/或许多版本的binutils,可能追溯到1.x.x)。 - old_timer
顺便说一下,有更简单的方法来获取您想要的入口点... 在我看来... 仍然有趣的是,它会显示您描述的问题对于某些地址而言,但对于其他地址则不会。 - old_timer
@old_timer:入口点没问题,我没有问题。问题是代码段从不存在的地址开始。因此,当我的软件从ELF文件中读取该段时,它会看到一个无效的地址。我已经修复了我的软件以忽略这个特定的错误,但最好不要这样做。 - TonyK
@old_timer:是的,我确实检查了ELF文件。我的软件需要读取它,这就是问题出现的地方。 - TonyK
显示剩余2条评论
3个回答

2
你看到的可能不是你所期望的,但在我看来仍然是正确的。
ELF 是为 System V 创建的。这是一个支持虚拟内存和 mmap()(将文件内容映射到内存中的系统调用)的操作系统。
你正在查看 ELF 程序头(而不是节头,详见下文)。程序头提供给(支持虚拟内存的)操作系统的 ELF 加载器有关它应该在哪里将(完整的)ELF 文件映射到进程镜像所准备的虚拟内存的信息。此操作系统将分配一个(或多个)页面,在某个位置称其为(虚拟)0x10000(对于该进程),映射文件并跳转到 0x10080(入口点)。
对于你的第二个示例,这将无法工作,因为你指定了(虚拟)起始地址 ELF 文件头(ELF 头 + 程序头 + 节头)之前,因此它无法将文件映射到页面边界,使得操作系统执行其 mmap() 技巧变得更加复杂(甚至不可能)。
对于你的裸机操作系统(很可能在启动时不支持虚拟内存),ELF 程序头的信息可能完全无关紧要。
相反,你应该看节头。它们描述了物理内存。

1
不,这不对。如果第一个区域的页面偏移小于 ELF 文件头的大小,则它将被正确映射到文件中的第二个页面,在页面内的正确偏移处。操作系统仍然可以使用其“映射技巧”,如果需要,我的程序可以直接将段加载到现有内存中。 - TonyK
我尝试加载单独的部分而不是程序段。但这会遗漏掉操作系统在程序启动时应该从NVM复制到RAM的初始化RAM数据。我对此感到相当惊讶,但我找不到解决方法。 - TonyK
顺便说一下,我临时修复了这个问题:我在链接脚本文件中创建了一个额外的虚拟段,仅用于将ELF文件头增加到0x80字节以上。现在一切都正常了,但我无法摆脱做了不好的事情的感觉。 - TonyK
你的M0没有MMU,因此也没有虚拟内存。我认为你只能将各个部分加载到物理内存中,别无选择。 - mfro
1
我可以使用不同的链接脚本创建此问题,这些脚本没有任何混淆或花哨的功能,非常简单。我已经能够以某种方式从这种奇怪的情况中加载文件并运行它们,但目前还不确定如何做到的。ELF文件肯定是不正确的,在头部中的偏移量未填充,为零,指向ELF文件的前面,没有其他方式可以感知这个文件除了是一个坏文件。尽管基地址的微小更改会使其消失或再次出现。 - old_timer
显示剩余9条评论

1

我在ARM Cortex-M平台上遇到了一个与GNU链接器非常相似的问题 (GNU ld (Atmel build: 508) 2.28.0.20170620)。我有引导程序和应用程序项目,应用程序的链接器会将ELF头文件放置在闪存位置,而这个位置正是引导程序代码所在的位置。虽然我不是专家,但这种修改使我的链接器无法在入口地址之前的内存空间中放置ELF头文件(将尝试在您的示例上展示):

  1. redefine NVM space by including first 0x80 bytes

     NVM (rx) : ORIGIN = 0x10000, LENGTH = 0x1000+0x80
    
  2. in sections part add that offset:

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

我不确定这对你有用,但也许可以作为其他人的提示。


0

适用于ARM GCC

arm-none-eabi-ld --help | grep page
  -n, --nmagic                Do not page align data
  -N, --omagic                Do not page align data, do not make text readonly
  -z common-page-size=SIZE    Set common page size to SIZE
  -z max-page-size=SIZE       Set maximum page size to SIZE

如果你想让程序头部的起始地址与入口地址匹配,只需设置 LDFLAGS += -z max-page-size=0x80LDFLAGS += -n


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接