3

由于 C++ 编译时间大大缩短,我最近添加了使用 Clang 而不是 arm-none-eabi-gcc 工具链为 ARM Cortex-M4 微控制器编译项目的选项。整个过程运行得非常顺利,我很快就有了 ELF 和 HEX 文件。直到昨天晚上我才注意到 ELF 文件实际上差异很大......

在我继续之前,让我们检查一下 GCC 生成的 ELF 以获得某种基线。

GCC 的 ELF 包含以下部分(除了调试内容)

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .vector_table     PROGBITS        08000000 010000 000010 00   A  0   0  4
      [ 2] .version          NOBITS          08000010 010010 000010 00  WA  0   0  1
      [ 3] .text             PROGBITS        08000020 010020 000138 00  AX  0   0  4
      [ 4] .rodata           PROGBITS        08000158 010158 000000 00  WA  0   0  1
      [ 5] .data             PROGBITS        20000000 010158 000000 00  WA  0   0  1
      [ 6] .data2            PROGBITS        10000000 010158 000000 00   W  0   0  1
      [ 7] .bss              NOBITS          20000000 020000 00038c 00  WA  0   0 512
      [ 8] .bss2             PROGBITS        2000038c 010158 000000 00   W  0   0  1
      [ 9] ._user_heap_stack NOBITS          2000038c 020000 000e04 00  WA  0   0  1

但是尽管 .data 和 .bss 标有“A”(alloc)标志,但仅加载以下内容

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x010000 0x08000000 0x08000000 0x00158 0x00158 RWE 0x10000
      LOAD           0x000000 0x20000000 0x20000000 0x00000 0x01190 RW  0x10000

     Section to Segment mapping:
      Segment Sections...
       00     .vector_table .version .text 
       01     .bss ._user_heap_stack 

到目前为止,一切都很好。

当我尝试从 Clang 生成的 ELF 创建二进制文件时,问题出现了。这些文件的大小(256MB)与我的预期相去甚远。现在,如果您不熟悉 ARM 微控制器,它们通常包含位于不同地址位置的 FLASH 和 RAM 存储器(例如,如上所示,FLASH 为 0x0800'0000,RAM 为 0x2000'0000)。所以我已经对发生了什么有些怀疑......我检查了我的链接器脚本并在每个单独进入 RAM 的部分上放置了一个 NOLOAD 指令。问题解决了?

嗯...不是真的。事实上,我的二进制文件变得更大了。

我们来看看 Clang 的 ELF。尽管我使用 -fno-unwind-tables 和 -gc-sections 进行编译,但 Clang 似乎并没有删除展开部分(ARM.exidx),这让我有点烦恼,但好吧,我可以忍受那些 16B。

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .vector_table     PROGBITS        08000000 001000 000010 00   A  0   0  4
      [ 2] .version          PROGBITS        08000010 001010 000010 00   A  0   0  1
      [ 3] .text             PROGBITS        08000020 001020 000334 00  AX  0   0  4
      [ 4] .rodata           PROGBITS        08000354 001354 000000 00  AX  0   0  1
      [ 5] .ARM.exidx        ARM_EXIDX       08000354 001354 000010 00  AL  3   0  4
      [ 6] .preinit_array    PROGBITS        08000364 001364 000000 00   A  0   0  1
      [ 7] .init_array       INIT_ARRAY      08000364 001364 000004 04  WA  0   0  4
      [ 8] .fini_array       FINI_ARRAY      08000368 001368 000004 04  WA  0   0  4
      [ 9] .data             PROGBITS        20000000 002000 000000 00  WA  0   0  1
      [10] .data2            PROGBITS        10000000 002000 000000 00  WA  0   0  1
      [11] .bss              NOBITS          20000000 002000 0001ac 00  WA  0   0 512
      [12] .bss2             PROGBITS        200001ac 002000 000000 00  WA  0   0  1
      [13] ._user_heap_stack PROGBITS        200001ac 002000 000e04 00  WA  0   0  1

现在这是它变得有趣的地方,我不知道发生了什么。什么是 GNU_RELRO 和 GNU_STACK 以及它是如何结束的?为什么 GNU_STACK 位于地址 0。此条目是否有可能使我的二进制文件膨胀?

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x001000 0x08000000 0x08000000 0x00364 0x00364 R E 0x1000
      LOAD           0x001364 0x08000364 0x08000364 0x00008 0x00008 RW  0x1000
      GNU_RELRO      0x001364 0x08000364 0x08000364 0x00008 0x00c9c R   0x1
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0
      EXIDX          0x001354 0x08000354 0x08000354 0x00010 0x00010 R   0x4

     Section to Segment mapping:
      Segment Sections...
       00     .vector_table .version .text .rodata .ARM.exidx 
       01     .preinit_array .init_array .fini_array 
       02     .preinit_array .init_array .fini_array 
       03     
       04     .rodata .ARM.exidx 

进一步的问题:

  • 尽管以前在链接描述文件中没有 NOLOAD 指令,但 GCC 如何能够删除所有 RAM 部分?
  • 在 Clang 的 ELF 上运行大小将我在链接描述文件中定义的最小堆栈大小计入 .data 部分,而在 GCC 的 ELF 上则没有。那个怎么样?我的链接器脚本包含一个看起来像这样的部分
    ._user_heap_stack (NOLOAD) :
    {
      . = ALIGN(8);
      PROVIDE ( end = . );
      PROVIDE ( _end = . );
      . = . + _Min_Heap_Size;
      . = . + _Min_Stack_Size;
      . = ALIGN(8);
    } >RAM
  • 但据我所知,这应该只“检查”至少有足够的 RAM 来覆盖我定义的最小堆和堆栈大小。该部分实际上不包含任何内容,那么它如何计入.data?

我知道在实际创建二进制文件时可以使用 objcopy 删除不需要的部分,但我真的很想了解 GCC 和 Clang 之间的那些细微差别。

/edit 我刚刚注意到我的 ._user_heap_stack 部分具有不同的类型,具体取决于编译器(NOBITS 与 PROGBITS)。猜猜这解释了为什么它被计入.data ...

/edit 现在是@ ​​LLVM 上的(潜在)错误 https://bugs.llvm.org/show_bug.cgi?id=46299

/edit 并从 lld 10.0.1 关闭 https://bugs.llvm.org/show_bug.cgi?id=46225

4

0 回答 0