GNU Linker: ELF has a LOAD segment with RWX permissions. Embedded ARM project
Asked Answered
P

4

17

I have updated my arm-none-eabi GCC and the associated tools and rebuilt an embedded project I develop.

$ arm-none-eabi-ld --version
GNU ld (GNU Binutils) 2.39

Suddenly, I'm getting the warning /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.

This warning seems to be newly introduced. I haven't changed the source / linkerscript lately. (EDIT: I checked an old ELF file created with a previous version. It didn't print the warning during linking but has the same issue). I develop for an STM32F407 microcontroller. The memory configuration in my linker script is the following:

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

Looking into the linked ELF I see:

$ 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 

Indeed, the first segment is flagged as RWE. It contains the sections .vectors, .text, .ARM and .flashcrc.

The .vectors section and the .text section contain the Vector table and the program code. I added another section in my linkerscript called .flashcrc

    .flashcrc : ALIGN(4)
    {
        KEEP(*(.flashcrc))
        KEEP(*(.flashcrc.*))
        . = ALIGN(4);
    } >FLASH =0xFF

I use this section in the source code to position a const struct in there, that contains CRC checksums. These checksums are calculated and patched into the ELF later by a spearate python script. The struct is easier to find in the ELF, if it resides in its own section.

Removing this section or simply relocating it to RAM like this:

    .flashcrc : ALIGN(4)
    {
        KEEP(*(.flashcrc))
        KEEP(*(.flashcrc.*))
        . = ALIGN(4);
    } >RAM AT >FLASH =0xFF

removed the "W" flag from the segment and the warning is gone.

I don't understand why the ELF file contains an "writable" flag for a section that is located in FLASH which is marked in the linkerscript as non writable. I tried using (xr!w) in the MEMORY definition, but it didn't change anything.

How do I convince the linker that the segment is not writable to silence this warning? Why don't the flags given in the MEMORY part of the linker script have any impact?


Weirdly enough this doesn't happen with the .vectors section which contains a const array of function pointers. So this section is basically identical to the .flashcrc section.


EDIT2: Today I found a little more time to play around. The struct in the .flashcrc section is defined in code (globally) like this:

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,
};

The crc values are patched into the ELF after linking. I had to make the struct volatile. If it isn't volatile, the compiler optimizes away the access to the struct because the elements are all 0 from its perspective as it doesn't know that these are patched after linking.

It turns out, that the warning disappears, if the volatile keyword is removed. For some reason the volatile keyword tricks the compiler/linker into thinking that this should be writable.

Is there another way to prevent the compiler optimizing away the access to this struct but not using volatile?

Pfister answered 20/8, 2022 at 20:28 Comment(10)
Can you just hide the definition of that structure in a separate implementation file? The compiler won't be able to "reach through" an extern declaration in your header to get the values out.Subtrahend
Yes. I think this would be possible. Not sure what will happen if any kind of linker optimization is activated though. I will probably do as you suggest. Nevertheless, I'm interested why this is happening. If I move a regular variable (not const) into the .flashcrc section and access it both reading/writing, the linker does not complain although the FLASH memory is marked as non writable. Do these flags have any effect?Pfister
Well, they don't have an effect on compilation, if that's what you're asking. You're right that link-time optimizations might take some of this information into account, though. In a more traditional just-a-dumb-linker mode, though, it's certainly not looking into the compiled code to see if there happen to be writes that go into a read-only area.Subtrahend
I'm more confused why the linker is even able to link it. The FLASH meory is clearly marked as not writable in the linker script. But it creates a segment there with a writable flag. Shouldn't it throw an error?Pfister
Did you ever find a solution? I'm having a similar issue but with static C++ class instances and not const data.Kingston
@Kingston I "solved" the problem by moving my data to a different translation unit. Therefore, I don't need the volatile keyword anymore and the compiler does not mark the segement as "writable". For some reason GCC thinks volatile stuff should be writable and propagates that info down into the load segement of the elf file... So no real solution for that besides getting rid of "volatile" in the executable segement.Pfister
Known issue: Linker warning when compiling with GCC 12 #1029 - the new binutils package (ld) is the culprit. Recommended ways to avoid the warning globally or on a per-project basis with cmake options are provided in the link.Bayberry
@DavidC.Rankin Thanks for that update! I will have a look at the linked issue.Pfister
Thanks @DavidC.Rankin. Perhaps your comment should be made an answer so it can be accepted?Glucoside
@CraigB - feel free to use the info in my comment to write up an answer if you like. Even though this question is nearly two years old, it will likely be relevant for another few years as distros s l o w l y update the version of gcc they ship. Otherwise, I'll circle-back and add one later, but I'm happy to let you do it :)Bayberry
P
3

Prior to version 13.1.0, GCC would place const volatile objects in a writeable section (named .data by default) instead of a read-only section (.rodata) like it does with all other const objects. Here's the patch where it was changed.

The linker sets the attributes of the output .flashcrc section automatically based on the input sections that make it up. You can override it like this:

.flashcrc (READONLY) : ALIGN(4)
{
    KEEP(*(.flashcrc))
    KEEP(*(.flashcrc.*))
    . = ALIGN(4);
} >FLASH =0xFF

See the Output Section Attributes and Output Section Type sections of the ld documentation for more detail. If you need compatibility with older toolchains, note that the READONLY type was added to ld relatively recently.

Likewise, the flags on the segments are set based on the sections they contain, so you end up with RWX for the one that covers flash. The attributes you specified for the different regions within the MEMORY command don't impact this. Per the documentation, those attributes "specify whether to use a particular memory region for an input section which is not explicitly mapped in the linker script."

I would also like to point out that the flags in the elf file have no bearing on whether memory is actually writable or executable on typical embedded platforms. You got a warning that was in some ways a false positive. It complained about RWX memory that is RX in reality. It's possible to get a false negative as well. If you were to put some code in ITCM without reconfiguring the MPU after loading, that memory would be RWX but you wouldn't get that now-desirable warning since it's RX according to the elf file.

Proofread answered 18/4 at 6:51 Comment(0)
D
4

I just ran into the same issue, just with different code. ST officially recommends a solution where you simply add "(READONLY)" for each section that goes only into flash memory, e.g. change

 .preinit_array :
 {
   …
 } >FLASH

to

 .preinit_array (READONLY) :
 {
   …
 } >FLASH

This worked for me.

Reference: https://wiki.st.com/stm32mcu/wiki/STM32CubeIDE:STM32CubeIDE_errata_1.15.x#General_issues Issue #169316 in section 3.1 (General issues).

Dreg answered 6/4 at 2:7 Comment(2)
If anyone has a link to more information about what the READONLY section attribute does then please share it. The closest I could find was this (I was expecting it here)Hexahedron
@Hexahedron That ld documentation is from 1998. Here's the latest. The readonly type was added in the last few years. It marks the output section as read-only instead of determining it automatically based on the attributes of the input sections (apparently no input results in writeable). That influences whether or not the segment the section ends up in is marked as read-only. Segment attributes mean nothing on a microcontroller, but on a "real" system the loader will see that in the ELF file and make the memory read-only.Proofread
P
3

Prior to version 13.1.0, GCC would place const volatile objects in a writeable section (named .data by default) instead of a read-only section (.rodata) like it does with all other const objects. Here's the patch where it was changed.

The linker sets the attributes of the output .flashcrc section automatically based on the input sections that make it up. You can override it like this:

.flashcrc (READONLY) : ALIGN(4)
{
    KEEP(*(.flashcrc))
    KEEP(*(.flashcrc.*))
    . = ALIGN(4);
} >FLASH =0xFF

See the Output Section Attributes and Output Section Type sections of the ld documentation for more detail. If you need compatibility with older toolchains, note that the READONLY type was added to ld relatively recently.

Likewise, the flags on the segments are set based on the sections they contain, so you end up with RWX for the one that covers flash. The attributes you specified for the different regions within the MEMORY command don't impact this. Per the documentation, those attributes "specify whether to use a particular memory region for an input section which is not explicitly mapped in the linker script."

I would also like to point out that the flags in the elf file have no bearing on whether memory is actually writable or executable on typical embedded platforms. You got a warning that was in some ways a false positive. It complained about RWX memory that is RX in reality. It's possible to get a false negative as well. If you were to put some code in ITCM without reconfiguring the MPU after loading, that memory would be RWX but you wouldn't get that now-desirable warning since it's RX according to the elf file.

Proofread answered 18/4 at 6:51 Comment(0)
S
2

I guess the author already figured out the solution, but I hope my answer helps someone else.

Our team ran into the same problem when we updated the ARM GCC toolchain from 10 to 12. As pointed out in the section The executable segment warnings from this article, the new linker warns you if you have data and code residing in the same memory region (aka segment).

The memory storing data must be writable (W), and code memory must be executable (X). Both must be readable (R). Thus, a segment that stores both data and code sections is RWX, which leaves your program vulnerable to attacks like buffer overflows.

I noticed that if you don't enforce attributes on a segment, it uses the ones set by the sections you assigned to it.

The solution proposed in that article did not work for me. What worked was splitting the memory into code and data segments in the linker script (lscript.ld). Instead of placing all sections in the same segment (defined by the MEMORY command) like this:

/* 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 = .;
}

We separate the memory into two different segments, one writable for data sections, and another executable for code sections:

MEMORY
{
   ps7_ddr_0_code (rx) : ORIGIN = 0x00100000, LENGTH = 0x1FF80000
   ps7_ddr_0_data (rw) : ORIGIN = 0x20080000, LENGTH = 0x1FF80000
}

Then, each SECTION must go into the segment according to its needs:

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

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

_end = .;
}

The good thing is that the linker tells you if you place a section into a segment that has different requirements.

Superannuation answered 28/2 at 10:30 Comment(2)
Thakns for the answer. To be honest, I lost track of the issue. At some point I simply accepted the warnings because I knew everything was alright. I'm still confused, why I can place something that is rw into a memory region marked as (rx) without the linker complaining. But whatever... As you've said, the solution is to split the regions.Pfister
Addon: My initial problem was: Making a const variable volatile somehow changes it to be writable. I didn't want it to be writable just to force the compiler to actually access it because I was patching the ELF file afterwards.Pfister
C
0

Fixed by adding (READONLY) to all sections in flash, e.g.:

FLASH (rx) : ORIGIN = 0x08000000, LENGTH = (1024K - 16K)

.section : { ... } >FLASH

Become:

FLASH (rx) : ORIGIN = 0x08000000, LENGTH = (1024K - 16K)

.section (READONLY) : { ... } >FLASH

If you also had this warning (like me):

missing .note.GNU-stack section implies executable stack

you shall also add "-z noexecstack" flag in Linker Settings

Calf answered 16/5 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.