Issue
I am in the process of compiling a program for an ARM Cortex-M4 microcontroller without the STM32 IDE. I use the arm-none-eabi toolchain with newlib libc, a linker script I adapted for my specific microcontroller and some startup code I took from ST.
After countless hours of debugging I found that a specific memory area must be zero-initialized. Otherwise, 0x200006E8
...0x2000071c
would contain random data which the CPU would access at some point, resulting in a Hard Fault.
readelf -S
shows that this corresponds to the .got
and .got.plt
sections:
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .isr_vector PROGBITS 08000000 001000 00018c 00 A 0 0 1
[ 2] .text PROGBITS 0800018c 00118c 00c4c4 00 AX 0 0 4
[ 3] .rodata PROGBITS 0800c650 00d650 0000a4 00 A 0 0 4
[ 4] .ARM.extab PROGBITS 0800c6f4 00d6f4 000000 00 A 0 0 1
[ 5] .ARM ARM_EXIDX 0800c6f4 00d6f4 000008 00 AL 2 0 4
[ 6] .preinit_array PREINIT_ARRAY 0800c6fc 00e71c 000000 04 WA 0 0 1
[ 7] .init_array INIT_ARRAY 0800c6fc 00d6fc 000004 04 WA 0 0 4
[ 8] .fini_array FINI_ARRAY 0800c700 00d700 000004 04 WA 0 0 4
[ 9] .data PROGBITS 20000000 00e000 0006e8 00 WA 0 0 8
[10] .got PROGBITS 200006e8 00e6e8 000028 00 WA 0 0 4
[11] .got.plt PROGBITS 20000710 00e710 00000c 04 WA 0 0 4
[12] .sram2 PROGBITS 2000c000 00e71c 000000 00 W 0 0 1
[13] .bss NOBITS 2000071c 00e71c 000500 00 WA 0 0 4
The startup code only zero-initializes the .bss
section, and does not mention got
or plt
. The linker script also has no reference to either got
or plt
.
This means that there are no start and end labels for the GOT (like for .bss
) which could be used by the startup code to initialize it, and I really don't want to hard-code the addresses.
I use the following flags to compile with GCC:
-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -fno-common -ffunction-sections -fdata-sections -Wl,--gc-sections -specs=nano.specs -ffreestanding -Wall -O0 -ggdb
and to link:
-T link.ld -lc -lgcc
To my understanding, GOT is only needed for dynamic linking.
Why is it inserted although the linker script does not specify it?
I tried to remove the .got
and .got.plt
sections with objcopy
, but this has no effect on the problem.
I am confused why the GOT should be initialized with zeroes in order to work, and not with some addresses. Is it possible that during the linking stages the GOT got inserted "into" the .bss
somehow, i.e, the GOT section should really be part of .bss
?
The GOT is right before the .bss
section (which is initialized as expected).
Ideally, I would like to just zero-initialize the .bss
section and not make any modifications to the startup code. The GOT should either be not used at all or be statically populated.
Any ideas on what could be going on here are highly appreciated.
Solution
Maybe this is stuff you already know, I cannot tell from the question...
unsigned int x;
void fun ( void )
{
x=5;
}
MEMORY {
one : ORIGIN = 0x000, LENGTH = 256
two : ORIGIN = 0x100, LENGTH = 256
}
SECTIONS {
.text : { *(.text) } > one
.bss : { *(.bss) } > two
}
arm-none-eabi-gcc -O2 -c -mcpu=cortex-m4 so.c -o so.o
arm-none-eabi-ld -Tso.ld so.o -o so.elf
arm-none-eabi-objdump -D so.elf
Disassembly of section .text:
00000000 <fun>:
0: 4b01 ldr r3, [pc, #4] ; (8 <fun+0x8>)
2: 2205 movs r2, #5
4: 601a str r2, [r3, #0]
6: 4770 bx lr
8: 00000100 andeq r0, r0, r0, lsl #2
Disassembly of section .bss:
00000100 <x>:
100: 00000000 andeq r0, r0, r0
IMO this is the normal/typical. (meaning, optimize and no pic/pie stuff, etc)(will get to gc-sections in a bit). And you can see that the compiler generates a value in the pool for the linker to fill in the address of x.
Note, I never use these as I do a lot of this bare-metal embedded stuff, but man gcc and look for the -fPIC -fpic and PIE to see the differences, it is an interesting read. In this case for this code the four combinations produce the same thing.
Disassembly of section .text:
00000000 <fun>:
0: 4b03 ldr r3, [pc, #12] ; (10 <fun+0x10>)
2: 4a04 ldr r2, [pc, #16] ; (14 <fun+0x14>)
4: 447b add r3, pc
6: 589b ldr r3, [r3, r2]
8: 2205 movs r2, #5
a: 601a str r2, [r3, #0]
c: 4770 bx lr
e: bf00 nop
10: 00000008 andeq r0, r0, r8
14: 00000000 andeq r0, r0, r0
It is a double indirect or let us say it adds a level of indirection. Now note the problem
4: 447b add r3, pc
the value in the pool is not the address of the got but the relative offset to the got. And the compiler is going to generate this for every function, which is the whole deal with using a got in the first place that you do not want to use fixed addresses in the pools of every function for addressing data.
Now position independence will also, mostly, in theory, you can try it, make the binary so that it is position independent and yes as with all data accesses this makes the code bigger. So only use pic/pie if you are absolutely needing it, esp for resource limited mcus. Every byte you save is a win. The clock cycle penalties are often measurable.
If you were using position independence you can do two things with it or both, move the code, move the data, or move both. If you move the code, then as with how it built for this target, you have to keep the got relative to the code. If pic is meant for shared libraries then this implies a ram based system, an operating system loading programs into ram. Where we are not on a ram based system unless you build for ram and copy and jump. As shown above (and the linker does not change/fix this) the code to got address relationship has to be fixed (for this compiler, version, target, example, etc)(if it happens in one example then it can happen to you).
But if you want to move the data then you have to update the got which means it has to be in ram. So got would need to be in ram but the binary is assumed to be in flash so if you want to run the binary from a different location (flash or ram) then you have to move the got a relative distance away. Then IF You want to move the data (binary moving or not) then you have to go to the got itself and add the relative change to the address to each entry. And yes in both cases you would need to know where the got is and how big.
If you do not move anything then the got is ready to go...from a build perspective. If you link it for ram though in an mcu then it will not be populated unless you do that in the bootstrap as you would for .data or .bss and that means like .data or .bss you have to add variables to the linker script (for this toolchain) or some other tricks you can do.
And it is basically some variation on this:
MEMORY {
one : ORIGIN = 0x000, LENGTH = 256
two : ORIGIN = 0x200, LENGTH = 256
}
SECTIONS {
.text : { *(.text) } > one
__LAB0__ = .;
.bss : {
__LAB1__ = .;
*(.bss)
__LAB2__ = .;
} > two AT > one
__LAB3__ = .;
.got :
{
__LAB4__ = .;
*(.got)
__LAB5__ = .;
} > two AT > one
}
.align
.word __LAB1__
.word __LAB2__
.word __LAB3__
.word __LAB4__
.word __LAB5__
I put variables inside and outside because while one would think they should always be the same, the gnu linker does not. You also want some aligns in there and adjust your copy loops (in asm, do not bootstrap C in C) (never use memset or memcpy, just do it right, use asm to bootstrap C) so you can for example ldm/stm two or four registers at a time, so align on a 32 or 64 bit boundary and then copy some multiple of 32 or 64. (can use exact addresses if next address is not equal to end then break the loop)
Anyway the same way you already isolated the size and start of bss and how you handle .data you would just mimic the .data linker script and copy code for .got if you want it in ram.
If you ended up with .got because of some other, already built thing you are trying to link in, then put the .got in flash in your linker script. (got.plt as well). Then of course use readelf and objdump to confirm things are where they should be and the addresses are correct, etc. before you try to run it.
So your question has some confusing holes.
-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -fno-common -ffunction-sections -fdata-sections -Wl,--gc-sections -specs=nano.specs -ffreestanding -Wall -O0 -ggdb
This looks borrowed. the sections ones are so that you can do -gc-sections in the linker, but then you specified a separate linker command line
-T link.ld -lc -lgcc
The -lc is very very scary, makes me shudder. But okay you can ask more so questions I guess...-lgcc won't work as is from the linker you need to do that from gcc or add a path, just how gnu works.
-O0 does not mate up with -gc-sections, either you want to make it smaller or you want to make it bigger, pick one. Or is the -O0 for some industry thing to avoid optimizations intentionally for safety reasons.
In order to actually have things removed you do the combination of the -sections options in the compile and -gc-sections in the link.
arm-none-eabi-gcc -O2 -c -ffunction-sections -fdata-sections -mcpu=cortex-m4 so.c -o so.o
arm-none-eabi-ld -Tso.ld -gc-sections -print-gc-sections so.o -o so.elf
arm-none-eabi-objdump -D so.elf
so.elf: file format elf32-littlearm
You want the gc-print in there so you can see what is being removed, one might think hey this did a good job and then maybe after bricking or being confused as to what is happening find that there is a bug.
ENTRY(fun)
MEMORY {
one : ORIGIN = 0x000, LENGTH = 256
two : ORIGIN = 0x100, LENGTH = 256
}
SECTIONS {
.text : { *(.text) } > one
.bss : { *(.bss) } > two
}
The linker follows all the code paths and data paths and if it does not get hits then it removes things, so you have to be very careful with items that you want to keep but have otherwise not referenced by name.
In any case with that fixed.
Disassembly of section .text:
00000000 <fun>:
0: 4b01 ldr r3, [pc, #4] ; (8 <fun+0x8>)
2: 2205 movs r2, #5
4: 601a str r2, [r3, #0]
6: 4770 bx lr
8: 00000100 andeq r0, r0, r0, lsl #2
Disassembly of section .bss:
00000100 <x>:
100: 00000000 andeq r0, r0, r0
It removed our unused data and function saving a noticeable amount of space consumed by our program. And it is optimized saving more space and running faster.
You did not provide enough information to completely answer, but there were some hints as we covered in comments. YOUR code may not have been creating the .got but some code you linked may have. In that case make the linker put the .got items in flash and you are done with them. If you can build without pic/pie then do that instead. Re-evaluate all of your command line options -fno-common seems borrowed and scary for example. No need to bloat the code with debugger stuff, unless you need that, but then you still want to build for release and debug that as release and debug are (can be) different binaries and can have different results, esp bare metal.
From what we can derive from your question, if you need to initialize .got then you need to wrap it in the linker with variables just like .data and do a copy loop just like .data. Use .data as the reference, cut and paste and change the names. If you have to change .got runtime. If you do not then put .got in the flash and it is done, no init. If possible to re-build whatever is creating the .got then that is even better (if you have no need for position independence), smaller and faster and easier to deal with. Only specify position independence if you plan to use it and have added the runtime code (not generated by the tools, this is on you) to do the work to use it.
A proper got will not be all zeros, not for this platform. As you likely already understand it is filled with addresses, and needs to be in order to work. Unless, well the library is built so that you have to magically somehow figure out where everything is and then fill in the got and plt yourself which makes my head hurt, you are not doing dynamic libraries here right?
Answered By - old_timer Answer Checked By - Marie Seifert (WPSolving Admin)