GNU链接器:ELF有一个具有RWX权限的LOAD段。嵌入式ARM项目

GNA*_*GNA 14 ld binutils arm-none-eabi-gcc

我已经更新了我的arm-none-eabi GCC 和相关工具,并重建了我开发的嵌入式项目。

$ arm-none-eabi-ld --version
GNU ld (GNU Binutils) 2.39
Run Code Online (Sandbox Code Playgroud)

突然,我收到警告 /usr/lib/gcc/arm-none-eabi/12.1.0/../../../../arm-none-eabi/bin/ld: warning: my_elf_file.elf has a LOAD segment with RWX permissions

这个警告似乎是新引入的。我最近没有更改源/链接描述文件。(编辑:我检查了使用先前版本创建的旧 ELF 文件。它在链接过程中没有打印警告,但有相同的问题)。我为 STM32F407 微控制器进行开发。我的链接器脚本中的内存配置如下:

MEMORY
{
    FLASH (xr)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K    
    CCM (rw)    : ORIGIN = 0x10000000, LENGTH = 64K
}
Run Code Online (Sandbox Code Playgroud)

查看链接的 ELF 我看到:

$ readelf -l my_elf_file.elf

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

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x010000 0x08000000 0x08000000 0x2220c 0x2220c RWE 0x10000
  LOAD           0x040000 0x10000000 0x0802220c 0x003e8 0x00d30 RW  0x10000
  LOAD           0x050000 0x20000000 0x080225f4 0x00c1c 0x00c1c RW  0x10000
  LOAD           0x000c20 0x20000c20 0x08023210 0x00000 0x01e70 RW  0x10000
  LOAD           0x002a90 0x20002a90 0x08023210 0x00000 0x08580 RW  0x10000

 Section to Segment mapping:
  Segment Sections...
   00     .vectors .text .ARM .flashcrc 
   01     .ccmdata .ccmbss 
   02     .data 
   03     .bss 
   04     .heap_stack 
Run Code Online (Sandbox Code Playgroud)

事实上,第一段被标记为 RWE。它包含 .vectors、.text、.ARM 和 .flashcrc 部分。

.vectors 部分和.text 部分包含向量表和程序代码。我在链接描述文件中添加了另一个名为 .flashcrc 的部分

    .flashcrc : ALIGN(4)
    {
        KEEP(*(.flashcrc))
        KEEP(*(.flashcrc.*))
        . = ALIGN(4);
    } >FLASH =0xFF
Run Code Online (Sandbox Code Playgroud)

我在源代码中使用此部分在其中放置一个 const 结构,其中包含 CRC 校验和。这些校验和稍后由一个单独的 python 脚本计算并修补到 ELF 中。如果该结构位于其自己的部分中,则更容易在 ELF 中找到该结构。

删除此部分或简单地将其重新定位到 RAM,如下所示:

    .flashcrc : ALIGN(4)
    {
        KEEP(*(.flashcrc))
        KEEP(*(.flashcrc.*))
        . = ALIGN(4);
    } >RAM AT >FLASH =0xFF
Run Code Online (Sandbox Code Playgroud)

从段中删除“W”标志,警告就消失了。

我不明白为什么 ELF 文件包含位于 FLASH 中的部分的“可写”标志,该标志在链接描述文件中标记为不可写。我尝试(xr!w)在 MEMORY 定义中使用,但它没有改变任何东西。

如何说服链接器该段不可写以消除此警告?为什么链接器脚本的 MEMORY 部分中给出的标志没有任何影响?


奇怪的是,包含函数指针常量数组的 .vectors 部分不会发生这种情况。所以该部分与 .flashcrc 部分基本相同。


EDIT2: 今天我找到了更多的时间来玩。该部分中的结构.flashcrc在代码中(全局)定义如下:

volatile const struct flash_crcs __attribute__((section(".flashcrc"))) crcs_in_flash = {
    .start_magic = 0xA8BE53F9UL,
    .crc_section_ccm_data = 0UL,
    .crc_section_text = 0UL,
    .crc_section_data = 0UL,
    .crc_section_vectors = 0UL,
    .end_magic = 0xFFA582FFUL,
};
Run Code Online (Sandbox Code Playgroud)

crc 值在链接后被修补到 ELF 中。我必须使结构变得不稳定。如果它不是易失性的,编译器会优化对结构的访问,因为从它的角度来看,元素都是 0,因为它不知道这些元素在链接后已修补。

事实证明,如果volatile删除关键字,警告就会消失。由于某种原因, volatile 关键字欺骗编译器/链接器认为它应该是可写的。

是否有另一种方法可以防止编译器优化对此结构的访问但不使用易失性?

小智 0

我想作者已经找到了解决方案,但我希望我的回答对其他人有帮助。

当我们将 ARM GCC 工具链从 10 更新到 12 时,我们的团队遇到了同样的问题。正如本文中的可执行段警告部分所指出的,如果您的数据和代码驻留在同一内存区域中,新的链接器会向您发出警告(又名段)。

存储数据的存储器必须是可写的(W),并且代码存储器必须是可执行的(X)。两者都必须可读 (R)。因此,存储数据和代码部分的段是 RWX,这使得您的程序容易受到缓冲区溢出等攻击。

我注意到,如果您不对段强制执行属性,它将使用您分配给它的部分设置的属性。

那篇文章中提出的解决方案对我来说不起作用。有效的方法是将内存分成链接描述文件(lscript.ld)中的代码和数据段。不要将所有部分放置在同一段(由 MEMORY 命令定义)中,如下所示:

/* ps7_ddr_0 is RWX because we omit the attributes and we 
   put both .data and .text there */
MEMORY
{
   ps7_ddr_0 : ORIGIN = 0x100000, LENGTH = 0x3FF00000
}

SECTIONS
{
.text : {
   KEEP (*(.vectors))
   *(.boot)
   *(.text)
   *(.text.*)
} > ps7_ddr_0

.data : {
   __data_start = .;
   *(.data)
   *(.data.*)
   __data_end = .;
} > ps7_ddr_0

_end = .;
}
Run Code Online (Sandbox Code Playgroud)

我们将内存分为两个不同的段,一个用于数据部分可写,另一个用于代码部分可执行:

MEMORY
{
   ps7_ddr_0_code (rx) : ORIGIN = 0x00100000, LENGTH = 0x1FF80000
   ps7_ddr_0_data (rw) : ORIGIN = 0x20080000, LENGTH = 0x1FF80000
}
Run Code Online (Sandbox Code Playgroud)

然后,每个SECTION都要根据自己的需要进入段:

SECTIONS
{
.text : {
   KEEP (*(.vectors))
   *(.boot)
   *(.text)
   *(.text.*)
} > ps7_ddr_0_code

.data : {
   __data_start = .;
   *(.data)
   *(.data.*)
   __data_end = .;
} > ps7_ddr_0_data

_end = .;
}
Run Code Online (Sandbox Code Playgroud)

好处是链接器会告诉您是否将一个节放入具有不同要求的段中。