-2

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.

Feihu Liu
  • 23
  • 6
  • 1
    Did you try to add `volatile` to your variables? Perhaps the optimizer sees the assignment as useless because the code never uses the stored value. – the busybee Sep 06 '22 at 09:13
  • @thebusybee `volatile` also works. But is this the **correct** way for fixing this problem? – Feihu Liu Sep 06 '22 at 10:01
  • @thebusybee When I add `*(volatile unsigned int*)(0x00100000) = *TTB_BASE;` before end of `main` to use the value of TTB_BASE, I still got the same problem, and the value written to address 0x00100000 is wrong. – Feihu Liu Sep 06 '22 at 10:22
  • `Image$$TTB` is only a symbol, not a variable. It does not contain any data, and has only the address. If you place other sections after this one you will write over those sections. It is not a correct way of using linker scripts. – 0___________ Sep 06 '22 at 10:25
  • @0___________ No, `Image$$TTB` is clearly a global variable to the compiler. Its location is allocated in the linker script, but it could be defined in a separate module. – the busybee Sep 06 '22 at 10:26
  • @FeihuLiu Would you mind to [edit] your question and add the additional experimental sources you tried, including the effects you see? -- Optimally you provide links to the [Compiler Explorer](https://godbolt.org/). You don't need the linker script, because the compiled object code should suffice to see the result. – the busybee Sep 06 '22 at 10:29
  • @thebusybee it is a symbol only and it has an address only. I work with linker scripts all the time as it is part of my daytime job. It is not a **variable** and there is no real object behind it. – 0___________ Sep 06 '22 at 10:37
  • @thebusybee Please refer to [link](https://godbolt.org/z/WY1rW8hGs) – Feihu Liu Sep 06 '22 at 10:39
  • @0___________ Ah yes, the linker script does not provide space. – the busybee Sep 06 '22 at 12:07
  • I test some gcc versions including x86, x86-64, arm, and found that all tested versions have such problem. And it is not related to linker script. The problem exists for any external variables. – Feihu Liu Sep 06 '22 at 12:20
  • Yes, I did the same. Very strange, and if you comment anything but the assignment, it appears. Perhaps you found a bug. -- Oh well. However, since I think my answer has some value, I'll keep it. Unfortunately I have some work to do now. – the busybee Sep 06 '22 at 12:25
  • @thebusybee Thanks for your help. It would be better for me to post a more general question. – Feihu Liu Sep 06 '22 at 14:06

2 Answers2

2

Your external variable has no side effects visible to the compiler, and as such it is allowed to optimize the assignment out.

The standard compliant way to avoid this is to add the qualifier volatile. It tells the compiler that this variable has side effects. Therefore it cannot optimize the assignment out.

extern volatile unsigned int Image$$TTB;

You need this qualifier also, if you have multiple threads like interrupt service routines, which share variables with each other or the main thread.


Addition:

As others pointed out in comments, there is an error in the linker script. You don't reserve space for the variable.

the busybee
  • 10,755
  • 3
  • 13
  • 30
  • It seems that even if *side effects* are available, the problem still exists. Please refer to my **update**. – Feihu Liu Sep 06 '22 at 10:28
  • @FeihuLiu: In your update, you did not add `volatile` where it is needed, in `extern volatile unsigned int Image$$TTB;`. It is the write to `image$$TTB` that was being omitted, and therefore it is `image$$TTB` that needs to be `volatile`. (And then pointers to it also need to include `volatile`, so that is needed as well.) – Eric Postpischil Sep 06 '22 at 14:00
  • @EricPostpischil Yes, I know how to make it work, but I'm curious why the updated code does not work. – Feihu Liu Sep 06 '22 at 14:35
  • @FeihuLiu: What is the value you observe stored in location 0x00100000 by the updated code? You did not show enough assembly to tell that, as it is stored from `r2`, which is loaded from `[r3, #-96]` when `r3` is pointing to 0x16002c + 32, so `[r3, #-96]` is at 0x15ffec, which you did not show. – Eric Postpischil Sep 06 '22 at 15:23
  • @EricPostpischil In the updated code, Image$$TTB was set to 0x00170000 because the previous is not suitable for my debug environment. When 0x00100000 is stored, r3 is pointing to 0x170040 + 32. The statement `ldr r3, [pc, #36]` means to get the content stored in the memory address [pc+36]. Please refer to my [new post](https://stackoverflow.com/questions/73624548/does-external-variables-always-need-to-be-volatile-when-complied-with-gcc) for a clearer statement of the question. – Feihu Liu Sep 06 '22 at 15:42
0

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
Feihu Liu
  • 23
  • 6