Search code examples
cgcclinkerarm

Cannot write to variable defined in LD linker script when compiling with -Os option


I am writing a program to run bare metal. I am trying to write to a variable from a custom linker script. The code runs perfectly when compiled with -O0 options but not as expected when compiled with -Os option.

The code I used is as follows.

main.c:

#define TTB_BASE (&Image$$TTB)
extern unsigned int Image$$TTB;

int main ()
{
    *TTB_BASE = 56326;

    unsigned int *ttb=TTB_BASE+16;

    for (int i = 0; i < 8; i++ )
    {
        *ttb++ = 1;
    }
    *(volatile unsigned int*)(0x00100000) = *TTB_BASE;
}

When compiled with -Os option, the code *TTB_BASE = 56326; is seems to be optimized out and the value is not stored to the address TTB_BASE.

The linker script used:

ARMCA7.ld

ENTRY(main)
SECTIONS
{
    . = 0x00160000;
    .text : { *(.text*) }
    .data : { *(.data .data.* .gnu.linkonce.d*) }
    .bss (NOLOAD) : {
        . = ALIGN(16);
        *(.bss .bss.*)
        *(COMMON)
    }
    . = 0x00170000;
    .ttb :
    {
        Image$$TTB = .;
    }
}

Compiling statement:

arm-none-eabi-gcc -mcpu=cortex-a7 -Os -std=gnu99 -c -o main.o main.c
arm-none-eabi-gcc -mcpu=cortex-a7 -Os -T ARMCA7.ld -nostartfiles -Xlinker --gc-sections -Wl,-Map,"main.map" -o main.elf main.o

Disassembly got:

Address : Opcode         Statement
-------   ------         ---------
 8                        unsigned int *ttb=TTB_BASE+16;
                      main:
00160000: 24 30 9f e5   ldr     r3, [pc, #36]   ; 0x16002c <main+44>
12                            *ttb++ = 1;
00160004: 01 10 a0 e3   mov     r1, #1
00160008: 20 20 83 e2   add     r2, r3, #32
0016000c: 04 10 83 e4   str     r1, [r3], #4
10                        for (int i = 0; i < 8; i++ )
00160010: 02 00 53 e1   cmp     r3, r2
00160014: fc ff ff 1a   bne     0x16000c <main+12>
14                        *(volatile unsigned int*)(0x00100000) = *TTB_BASE;
00160018: 60 20 13 e5   ldr     r2, [r3, #-96]  ; 0xffffffa0
0016001c: 01 36 a0 e3   mov     r3, #1048576    ; 0x100000
15                    }
00160020: 00 00 a0 e3   mov     r0, #0
14                        *(volatile unsigned int*)(0x00100000) = *TTB_BASE;
00160024: 00 20 83 e5   str     r2, [r3]
15                    }
00160028: 1e ff 2f e1   bx      lr
0016002c: 40 00 17 00   andseq  r0, r7, r0, asr #32

It can be seen that nothing is written to the address TTB_BASE which is 0x00170000 defined in the linker script, and the value written to address 0x00100000 is wrong.

Is there some bug in the code or linker script?

Note:

If I change the first two lines of main.c to the following code,

#define TTB_BASE (Image$$TTB)
extern unsigned int Image$$TTB[];

The code can be correctly compiled and the address TTB_BASE is written correctly. However, as I seen from other questions, both approach should be correct.

gcc version used:

arm-none-eabi-gcc --version
arm-none-eabi-gcc.exe (Arm GNU Toolchain 11.3.Rel1) 11.3.1 20220712
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I also test the code with arm-none-eabi-gcc.exe 10.3.1 20210621 (release) and got the same problem.


Solution

  • I found that the question posted is related to variables references other than linker script. Please refer to this new post for further discussion.

    For a conslusion, the real reason why the code does not work is as follows. &Image$$TTB+16 is Undefined Behavior in C.

    That is, the following declaration will be needed if you want to change the address regions defined in linker script file.

    extern unsigned int Image$$TTB[];
    

    The following is another example code that has the same bug. Note that it should be compiled with -O1 or above. You can try it at Compiler Explorer.

    #include <stdio.h>
    unsigned int var;
    volatile unsigned int valid_address[1024];
    int main ()
    {
        var = 56326;
        unsigned int *ttb=&var;
        ttb += 16;
        for (int i = 0; i < 8; i++ )
        {
            *ttb++ = 1;
        }
        valid_address[0] = var;
        printf("Value is: %d", valid_address[0]);
    }
    

    If you execute this code, you might get the following output:

    Value is: 0