# Demistifying Firmware Linker Scripts

**URL:** <https://community.memfault.com/t/demistifying-firmware-linker-scripts/49>\
**Category:** Blog\
**Created:** [June 25, 2019, 4:56pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49 "2019-06-25T16:56:54Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![discobot](https://yyz2.discourse-cdn.com/flex030/user_avatar/community.memfault.com/discobot/32/1_2.png) [@discobot](https://community.memfault.com/u/discobot)\
**Post date:** [June 25, 2019, 4:56pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/1 "2019-06-25T16:56:54Z")

</div>

This is the second post in our “Zero to main()” series.

[Last time](https://interrupt.memfault.com/blog/zero-to-main-1), we talked about bootstrapping a C environment on an MCU before invoking our `main` function. One thing we took for granted was the fact that functions and data end up in the right place in our binary. Today, we’re going to dig into how that happens by learning about memory regions and linker scripts.

* * *
This is a companion discussion topic for the original entry at [https://interrupt.memfault.com/blog/how-to-write-linker-scripts-for-firmware](https://interrupt.memfault.com/blog/how-to-write-linker-scripts-for-firmware)

---

<div class="post-metadata">

**Author:** ![zakaria1193](https://avatars.discourse-cdn.com/v4/letter/z/96bed5/32.png) [@zakaria1193](https://community.memfault.com/u/zakaria1193)\
**Post date:** [June 27, 2019, 9:44am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/2 "2019-06-27T09:44:15Z")

</div>

simple and insightful, thanks for these articles.

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [June 28, 2019, 3:21pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/3 "2019-06-28T15:21:46Z")

</div>

You’re welcome! Thanks for reading 🙂

---

<div class="post-metadata">

**Author:** ![jthemis](https://avatars.discourse-cdn.com/v4/letter/j/9de053/32.png) [@jthemis](https://community.memfault.com/u/jthemis)\
**Post date:** [March 3, 2020, 9:31pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/4 "2020-03-03T21:31:32Z")

</div>

Very well written, but my question relates to the demystification. The “COMMON” section still just magically appears.  
You’ll note that the `.bss` section also includes `*(COMMON)` . This is a special input section where the compiler puts global unitialized variables that go beyond file scope.

Where is this documented? I am still curious by some linker script content. I now understand 99% (reading a script) of a script but coming up with one is a still a huge challenge. I guess it is like makefiles, you never create one from scratch.

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [March 4, 2020, 1:58am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/5 "2020-03-04T01:58:20Z")

</div>

@jthemis - `COMMON` is documented in the `ld` docs: [http://www.scoberlin.de/content/media/http/informatik/gcc\_docs/ld\_3.html#SEC24](http://www.scoberlin.de/content/media/http/informatik/gcc_docs/ld_3.html#SEC24)

---

<div class="post-metadata">

**Author:** ![rookie](https://avatars.discourse-cdn.com/v4/letter/r/e47c2d/32.png) [@rookie](https://community.memfault.com/u/rookie)\
**Post date:** [June 5, 2020, 12:34am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/6 "2020-06-05T00:34:12Z")

</div>

Hello, in this example will .sbss point to the start of RAM location ie 0x20000000, since that is the first item defined in the RAM section? Is it correct that \_end will be used as the start of stack, as it grows “downward” in ARM?  
I was reading about stack overflow protection, where it was suggested that the stack be placed at the “top” of RAM, to prevent bss and data section corruption and also generate a hardfault. I am actually not sure what the implication of top or bottom is(in terms of the physical address), but does that mean that the stack section should be defined “first”, ie before bss? Can you may be help me understand?

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [June 5, 2020, 12:55am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/7 "2020-06-05T00:55:07Z")

</div>

> [@rookie](#):
>
> Is it correct that \_end will be used as the start of stack, as it grows “downward” in ARM?

In our linker script, you are correct.

> [@rookie](#):
>
> I was reading about stack overflow protection, where it was suggested that the stack be placed at the “top” of RAM, to prevent bss and data section corruption and also generate a hardfault. I am actually not sure what the implication of top or bottom is(in terms of the physical address), but does that mean that the stack section should be defined “first”, ie before bss? Can you may be help me understand?

You could indeed put the stack right at the start of RAM to generate a fault when you overflow it. In that case you would add the stack prior to the `bss` section in your linker script. An alternative is to use your MPU to memory protect the end of your stack, or to write a pattern at the end of your stack and verify it has not been overridden when you context switch.

---

<div class="post-metadata">

**Author:** ![rookie](https://avatars.discourse-cdn.com/v4/letter/r/e47c2d/32.png) [@rookie](https://community.memfault.com/u/rookie)\
**Post date:** [June 5, 2020, 1:13am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/8 "2020-06-05T01:13:28Z")

</div>

Thank you for the quick response! Yes I will also look at enabling the MPU (your MPU blogpost is on my reading list!). In my readings in blogs and posts, it seems that the method of writing a pattern (like gcc stack canary?) would add additional overhead, and an alternative (“zero overhead”) was to change the position of stack. I will explore all options! Thanks again.

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [June 5, 2020, 5:23am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/9 "2020-06-05T05:23:10Z")

</div>

Right, adding a stack canary has additional overhead. Depending on when you check the canary, you trade overhead for how quickly the overflow is detected. E.g.

- Check at before every function returns (i.e. the GCC approach): quickly detects overflows, but high overhead (you can read more about it at [https://lwn.net/Articles/584225/](https://lwn.net/Articles/584225/)).
- Check at every scheduler tick (i.e. the FreeRTOS approach): lower overhead, but you won’t know which function led to the overflow exactly.

Changing the position of the stack is a good approach!

---

<div class="post-metadata">

**Author:** ![bare\_metal\_newbie](https://avatars.discourse-cdn.com/v4/letter/b/e79b87/32.png) [@bare\_metal\_newbie](https://community.memfault.com/u/bare_metal_newbie)\
**Post date:** [June 8, 2021, 4:08am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/10 "2021-06-08T04:08:03Z")

</div>

I am trying to use custom linker script on my beaglebone black rev C, Ubuntu 18.04.2 LTS, armv7l. Following are the 3 files, namely, the C code, the linker script and the script to compile and execute:  
1 of 3) main.c:

```auto
const char msg[] = "Hello World !\n";
unsigned int msg_size = sizeof(msg);

const char func_msg[] = "func(): I was called!\n";
unsigned int func_msg_size = sizeof(func_msg);

void func(void)
{
    // write syscall arg1 is the file descriptor. 1 is STDOUT.
    asm volatile (
            "mov %r0, $1"
    );

    // write syscall arg2 is the buf.
    asm volatile (
            "ldr %r1, =func_msg"
    );

    // write syscall arg3 is the size_t count.
    asm volatile (
            "ldr %r2, =func_msg_size"
    );

    // Specify the type of system call (4 is the write syscall) in r7.
    asm volatile (
            "mov %r7, $4"
    );

    // Invoke the syscall.
    asm volatile (
            "swi $0"
    );
}

void nri_main(void)
{
        // write syscall arg1 is the file descriptor. 1 is STDOUT.
        asm volatile (
                "mov %r0, $1"
        );

        // write syscall arg2 is the buf.
        asm volatile (
                "ldr %r1, =msg"
        );

        // write syscall arg3 is the size_t count.
        asm volatile (
                "ldr %r2, =msg_size"
        );

        // Specify the type of system call (4 is the write syscall) in r7.
        asm volatile (
                "mov %r7, $4"
        );

        // Invoke the syscall.
        asm volatile (
                "swi $0"
        );

        // Specify the return value. Let's say 32.
        asm volatile (
                "mov %r0, $32"
        );

        // Specify the type of system call (1 is the exit syscall) in r7.
        // Source: By randomly searching online.
        asm volatile (
                "mov %r7, $1"
        );

        // Invoke the syscall.
        asm volatile (
                "swi $0"
        );
}

```

2 of 3) nri.ld:

```auto
ENTRY(nri_main)
STACK_SIZE = 0x2000; /* 8 kB */

SECTIONS
{
    . = 0x10000;
    .text : {
        KEEP(*(.stack))
        *(.text)
    }

    . = 0x8000000;
    .data : { *(.data) }

    .stack (NOLOAD) :
    {
        . = ALIGN(8);
        . = . + STACK_SIZE;
        . = ALIGN(8);
    }
}

```

3 of 3) pcal.sh:

```auto
#!/bin/bash

# Exit on failure of any command.
set -e

rm -f main.elf main.map

# Disable ASLR
if [[$(cat /proc/sys/kernel/randomize_va_space) != 0]]; then
echo "pcal.sh: ASLR is enabled. Disabling it."
sudo -u root bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
else
echo "pcal.sh: ASLR is disabled. No action required."
fi

cpp -nostdlib -nostartfiles -nodefaultlibs -static -fno-exceptions -fstack-usage -Wstack-usage=8192 main.c -o main.i
gcc -nostdlib -nostartfiles -nodefaultlibs -static -fno-exceptions -fstack-usage -Wstack-usage=8192 -S main.i -o main.s
as main.s -o main.o
ld -static --gc-section -T nri.ld -Map=main.map main.o -o main.elf

echo "Now you can ./main.elf and then echo \$? to see if 32 return value gets printed."

rm -f main.i main.s main.o

```

I end up with segmentation fault, as shown below:  
$ ./pcal.sh  
pcal.sh: ASLR is disabled. No action required.  
Now you can ./main.elf and then echo $? to see if 32 return value gets printed.  
$ ./main.elf  
func(): I was called!  
Segmentation fault

I believe it is something to do with incorrect placement of stack since I wanted to learn how to correctly setup stack on a Linux environment where, if I’m not mistaken, it is not as flexible as bare metal. Please help!

---

<div class="post-metadata">

**Author:** ![iereyesfi](https://avatars.discourse-cdn.com/v4/letter/i/73ab20/32.png) [@iereyesfi](https://community.memfault.com/u/iereyesfi)\
**Post date:** [June 28, 2021, 9:20pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/11 "2021-06-28T21:20:04Z")

</div>

Terrific explanation, I hope you could help me to solve another issue.

I need to create a new section in the RAM, to save all the global variables of type uint16 (allready did it), how can I tell the compiler and/or linker to save the uint16 global variables in that especific memory section?

Thank you.

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [June 28, 2021, 9:35pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/12 "2021-06-28T21:35:12Z")

</div>

Off the top of my head, there’s no way to tell the linker “put variables with this type in this location”, but you could create a macro:

```auto
#define MY_U16 (uint16_t __attribute__ ((section(".yoursection")))

```

and use that to define your variables:

```auto
static MY_U16 foo = 0;

```

---

<div class="post-metadata">

**Author:** ![shrs](https://avatars.discourse-cdn.com/v4/letter/s/b9bd4f/32.png) [@shrs](https://community.memfault.com/u/shrs)\
**Post date:** [August 26, 2021, 12:45pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/13 "2021-08-26T12:45:52Z")

</div>

Your post is very good, simple, easy to understand.  
Can you also add a section on how to map the reset vector to the appropriate handler function? I have seen, for example, in some projects, it is defined as:  
reset\_handler = \_start  
ENTRY(\_start)

where \_start is defined in a .c file somewhere.

---

<div class="post-metadata">

**Author:** ![RyanEdward](https://yyz2.discourse-cdn.com/flex030/user_avatar/community.memfault.com/ryanedward/32/139_2.png) [@RyanEdward](https://community.memfault.com/u/RyanEdward)\
**Post date:** [November 11, 2021, 9:25am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/14 "2021-11-11T09:25:39Z")

</div>

@francois You demonstrated with a linker script that only defines `MEMORY`, calling `arm-none-eabi-objdump -t <elf-file>` will produce ‘SYMBOL TABLE: no symbols’  
When I tried with my own ‘MEMORY only’ linker script I still got `.text`, `.bss`, `.data` and `.comment` sections.

I compile with: `arm-none-eabi-gcc -nostdlib -mcpu=cortex-m0plus test.c -c -o test.o`

I link with: `arm-none-eabi-ld -T test.ld test.o -o test.elf`

Do you know what is going on? Thanks

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [November 11, 2021, 5:54pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/15 "2021-11-11T17:54:47Z")

</div>

> [@RyanEdward](#):
>
> Do you know what is going on? Thanks

The only obvious thing I see is that you invoke `ld` directly, whereas I use `gcc` to do the linking. Might end up passing different flags under the hood?

---

<div class="post-metadata">

**Author:** ![RyanEdward](https://yyz2.discourse-cdn.com/flex030/user_avatar/community.memfault.com/ryanedward/32/139_2.png) [@RyanEdward](https://community.memfault.com/u/RyanEdward)\
**Post date:** [November 11, 2021, 8:39pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/16 "2021-11-11T20:39:20Z")

</div>

I was thinking that too, however to no success.  
My current workaround is:  
`SECTIONS { /DISCARD/ : { *(*) } } `

---

<div class="post-metadata">

**Author:** ![isoment01](https://avatars.discourse-cdn.com/v4/letter/i/e0b2c6/32.png) [@isoment01](https://community.memfault.com/u/isoment01)\
**Post date:** [November 29, 2021, 6:27am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/17 "2021-11-29T06:27:59Z")

</div>

Hi, thank you for the explanation. But, what if I keep empty inside of section like  
SECTIONS  
{  
.intvecs : {} \> VECTORS  
.text : {} \> FLASH0 | FLASH1  
.const : {} \> FLASH0 | FLASH1  
.cinit : {} \> FLASH0 | FLASH1  
.pinit : {} \> FLASH0 | FLASH1  
.bss : {} \> RAM  
.data : {} \> RAM  
.sysmem : {} \> RAM  
}  
what will happens ?

---

<div class="post-metadata">

**Author:** ![ben](https://avatars.discourse-cdn.com/v4/letter/b/df788c/32.png) [@ben](https://community.memfault.com/u/ben)\
**Post date:** [November 27, 2022, 3:55pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/18 "2022-11-27T15:55:15Z")

</div>

If you want to reserve a section of memory, how could you carve it out so that a new section data section is created from the original block?

For example, the fpga/board designers suddenly say, ok here is another 4k memory block at this address. How could you 1) add it to the existing memory pool so that the linker uses it as it pleases… or add it as a separate section, that is not available for linker’s general use but rather is used by the programmer via:

```auto
char initialized_array[100] __attribute__ ((section ("NEWBLOCK"))) = { 0 };
int non_init_data __attribute__ ((section ("NEWBLOCK")));

```

---

<div class="post-metadata">

**Author:** ![foolong](https://avatars.discourse-cdn.com/v4/letter/f/e79b87/32.png) [@foolong](https://community.memfault.com/u/foolong)\
**Post date:** [August 3, 2024, 5:51am UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/19 "2024-08-03T05:51:01Z")

</div>

The article states that

> [the NOLOAD property] is the only section property used in modern linker scripts.

This is not true. As of GCC 11 (used by the current STM32 toolchain) will emit a warning  
“warning: file.elf has a LOAD segment with RWX permissions”, if the READONLY property isn’t specified for certain sections. This caused a lot of churn when users upgraded to the a new IDE and toolchain release, since perfectly working projects suddenly showed these new warnings.

---

<div class="post-metadata">

**Author:** ![francois](https://avatars.discourse-cdn.com/v4/letter/f/77aa72/32.png) [@francois](https://community.memfault.com/u/francois)\
**Post date:** [August 12, 2024, 7:51pm UTC](https://community.memfault.com/t/demistifying-firmware-linker-scripts/49/20 "2024-08-12T19:51:37Z")

</div>

Thanks for calling that out @foolong. IMO this is a bug in the ST toolchain, they should disable this warning. The READONLY property does nothing on these chips. You could imagine a future where a header is added to the firmware (or ELF files are supported directly by bootloaders) and the bootloader will setup write protection on those sections of flash, but AFAIK no MCU toolchain does this today.
