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.
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