Related
I'm building an embeeded software for STM32 microcontroller with the toolchain GNU Tools for STM32 and I need the binary output without gaps.
The linker produces a gap between sections .text and .rodata. The problem is the alignment of the section .rodata. The issue appears by using of the GNU Tools for STM32 version 9-2020-q2-update. The previous version I had used (7-2018-q2-update) did not produced that issue.
Excerpt from the linker script (it's the same for both GNU Tools versions):
SECTIONS
{
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4); /* PaulV: change that to ALIGN(8) eliminates the gap */
} >FLASH
/* Constant data into "FLASH" Rom type memory */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
}
More details:
The version 7-2018-q2-update produces the output without gap.
The .lst file (note that section .rodata is aligned to bound 4):
K4_G1.elf: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
....
3 .text 0001a20c 08100800 08100800 00010800 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
4 .rodata 00009b54 0811aa0c 0811aa0c 0002aa0c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
and .map file (no gap between the non-empty sections .fini and .rodata):
.fini 0x000000000811aa04 0x8 c:/st/stm32cubeide_1.4.0/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.7-2018-q2-update.win32_1.5.0.202011040924/tools/bin/../lib/gcc/arm-none-eabi/7.3.1/thumb/v7e-m/fpv5/hard/crtn.o
0x000000000811aa0c . = ALIGN (0x4)
0x000000000811aa0c _etext = .
.vfp11_veneer 0x000000000811aa0c 0x0
.vfp11_veneer 0x000000000811aa0c 0x0 linker stubs
.v4_bx 0x000000000811aa0c 0x0
.v4_bx 0x000000000811aa0c 0x0 linker stubs
.iplt 0x000000000811aa0c 0x0
.iplt 0x000000000811aa0c 0x0 c:/st/stm32cubeide_1.4.0/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.7-2018-q2-update.win32_1.5.0.202011040924/tools/bin/../lib/gcc/arm-none-eabi/7.3.1/thumb/v7e-m/fpv5/hard/crtbegin.o
.rodata 0x000000000811aa0c 0x9b54
0x000000000811aa0c . = ALIGN (0x4)
*(.rodata)
.rodata 0x000000000811aa0c 0x8c Src/app_composer/init.o
The version 9-2020-q2-update produces the output with gap.
The .lst file (note that section .rodata is aligned to bound 8, but why?):
K4_G1.elf: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
...
3 .text 0001923c 08100800 08100800 00010800 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
4 .rodata 000061f0 08119a40 08119a40 00029a40 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
and .map file (there is a gap between the non-empty sections .fini and .rodata):
.fini 0x0000000008119a34 0x8 c:/st/stm32cubeide_1.4.0/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.9-2020-q2-update.win32_1.5.0.202011040924/tools/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v7e-m+dp/hard/crtn.o
.vfp11_veneer 0x0000000008119a3c 0x0
.vfp11_veneer 0x0000000008119a3c 0x0 linker stubs
.v4_bx 0x0000000008119a3c 0x0
.v4_bx 0x0000000008119a3c 0x0 linker stubs
.iplt 0x0000000008119a3c 0x0
.iplt 0x0000000008119a3c 0x0 c:/st/stm32cubeide_1.4.0/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.9-2020-q2-update.win32_1.5.0.202011040924/tools/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v7e-m+dp/hard/crtbegin.o
.rodata 0x0000000008119a40 0x61f0
0x0000000008119a40 . = ALIGN (0x4)
*(.rodata)
.rodata 0x0000000008119a40 0x96 Src/app_composer/init.o
Edit 03/16/2021
There are no sections *(.rodata) in the input object files having
alignment on the boundary 8 or greater.
Changing the section name .rodata to the name .text eliminates the
gap (The same result if I join the sections .text and .rodata content to a
single .text section):
SECTIONS
{
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
/* ... */
. = ALIGN(4);
} >FLASH
/* Constant data into "FLASH" Rom type memory */
.text : /* <-- the same name as the previous section instead of .rodata */
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
}
The source code and build settings are also the same for both variants.
What could be the reason for the problem and how could it be solved? Do I missing something?
P.S. Of cause I can change the alignment at the end of .text section to 8. That would be treating the symptoms, but I want to understand the cause.
Thanks in advance for your help!
Run objdump -h on the input object files. I suspect that you will find that the compiler is putting a minimum alignment of 8 on one of the input .rodata sections. The linker then sets the output alignment to the maximum of the input sections.
I am trying to write a simple "Hello, World!" firmware for Cortex-M0 CPU. The goal is to correctly initialize and shutdown C++ runtime so that global constructors are called before main() and global destructors are called after main().
So far I managed to get exactly half of it working - global ctors run correctly, but global destructors don't. I use gcc on Windows with the Newlib, which is supposed to initialize the runtime.
struct Test {
Test() // This is invoked correctly.
{}
~Test() // This is not invoked at all.
{}
};
Test test;
int main()
{
return 0;
}
extern void __libc_init_array();
extern void __libc_fini_array();
void reset_handler() // This is the entry point.
{
__libc_init_array(); // Test::Test().
asm volatile( "bl main" ); // Invoke 'main'.
__libc_fini_array(); // Expect Test::~Test() but nothing happens.
}
I did quite a bit of research already, and it seems that for ARM compiler should generate a section called .init_array, for global constructors, and .fini_array, for global destructors, where it put pointer to the function to be invoked. Then, the linker will merge the sections from all units together, and __libc_init_array will "walk" through that section and call corresponding functions.
This is the relevant part of the linker script:
/* Initialization functions which run before main(),
such as global constructors. */
.init_array : ALIGN( 4 ) {
/* preinit data */
PROVIDE_HIDDEN ( __preinit_array_start = . );
KEEP( *( .preinit_array ) )
PROVIDE_HIDDEN( __preinit_array_end = . );
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN ( __init_array_start = . );
KEEP( *( SORT( .init_array.* ) ) )
KEEP(*(.init_array))
PROVIDE_HIDDEN ( __init_array_end = . );
} > flash
/* Finalization functions which run after main(),
such as global destructors. */
.fini_array : ALIGN( 4 ) {
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP ( *( .fini_array.* ) )
KEEP ( *( .fini_array ) )
PROVIDE_HIDDEN (__fini_array_end = .);
} > flash
What worries me, however, is that when I dump the object file, main.o, not even an executable, I can only see the .init_array section, but no .fini_array:
Sections:
Idx Name Size VMA LMA File off Algn
0 .group 00000008 00000000 00000000 00000034 2**2
CONTENTS, READONLY, GROUP, LINK_ONCE_DISCARD
1 .group 00000008 00000000 00000000 0000003c 2**2
CONTENTS, READONLY, GROUP, LINK_ONCE_DISCARD
2 .text 0000022c 00000000 00000000 00000044 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
3 .data 00000000 00000000 00000000 00000270 2**0
CONTENTS, ALLOC, LOAD, DATA
4 .bss 00000001 00000000 00000000 00000270 2**2
ALLOC
5 .text._ZN4TestC2Ev 00000012 00000000 00000000 00000270 2**1 << Test::Test()
CONTENTS, ALLOC, LOAD, READONLY, CODE
6 .text._ZN4TestD2Ev 00000012 00000000 00000000 00000282 2**1 << Test::~Test()
CONTENTS, ALLOC, LOAD, READONLY, CODE
7 .init_array 00000004 00000000 00000000 00000294 2**2 << WHERE IS .fini_array???
CONTENTS, ALLOC, LOAD, RELOC, DATA
8 .debug_info 000012d9 00000000 00000000 00000298 2**0
CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
9 .debug_abbrev 000003a1 00000000 00000000 00001571 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
10 .debug_aranges 00000030 00000000 00000000 00001912 2**0
CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
11 .debug_ranges 00000020 00000000 00000000 00001942 2**0
CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
12 .debug_line 000002d2 00000000 00000000 00001962 2**0
CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
13 .debug_str 000009e4 00000000 00000000 00001c34 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
14 .comment 0000004d 00000000 00000000 00002618 2**0
CONTENTS, READONLY
15 .debug_frame 0000054c 00000000 00000000 00002668 2**2
CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
16 .ARM.attributes 0000002c 00000000 00000000 00002bb4 2**0
CONTENTS, READONLY
This is how I invoke the compiler:
arm-none-eabi-c++.exe -o main.elf --verbose -mcpu=cortex-m0 -mthumb --specs=nano.specs --entry reset_handler -T./../src/firmware.ld -ggdb -O0 -Wall -Wextra -Wpedantic -Werror ../src/main.cpp
And this is the log:
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 7a1ab17ae8404f635d46188cccacd8be
COLLECT_GCC_OPTIONS='-o' 'main.elf' '-v' '-mcpu=cortex-m0' '-mthumb' '-specs=nano.specs' '-e' 'reset_handler' '-T' './../src/firmware.ld' '-ggdb' '-O0' '-Wall' '-Wextra' '-Wpedantic' '-Werror' '-mfloat-abi=soft' '-march=armv6s-m'
c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/bin/as.exe -v -march=armv6s-m -mfloat-abi=soft -meabi=5
GNU assembler version 2.34.0 (arm-none-eabi) using BFD version (GNU Arm Embedded Toolchain 9-2020-q2-update) 2.34.0.20200428
COMPILER_PATH=c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/;c:/id/gcc/bin/../lib/gcc/;c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/bin/
LIBRARY_PATH=c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp/;c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/lib/thumb/v6-m/nofp/;c:/id/gcc/bin/../arm-none-eabi/lib/thumb/v6-m/nofp/;c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/;c:/id/gcc/bin/../lib/gcc/;c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/lib/;c:/id/gcc/bin/../arm-none-eabi/lib/
COLLECT_GCC_OPTIONS='-o' 'main.elf' '-v' '-mcpu=cortex-m0' '-mthumb' '-specs=nano.specs' '-e' 'reset_handler' '-T' './../src/firmware.ld' '-ggdb' '-O0' '-Wall' '-Wextra' '-Wpedantic' '-Werror' '-mfloat-abi=soft' '-march=armv6s-m'
c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/collect2.exe -plugin c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/liblto_plugin-0.dll -plugin-opt=c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/lto-wrapper.exe -plugin-opt=-fresolution=C:\Users\MAXID~1\AppData\Local\Temp\ccpOTkFN.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lg_nano -plugin-opt=-pass-through=-lc_nano -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc_nano --sysroot=c:\id\gcc\bin\../arm-none-eabi -X -o main.elf -e reset_handler c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp/crti.o c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp/crtbegin.o c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/lib/thumb/v6-m/nofp/crt0.o -Lc:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp -Lc:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/lib/thumb/v6-m/nofp -Lc:/id/gcc/bin/../arm-none-eabi/lib/thumb/v6-m/nofp -Lc:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1 -Lc:/id/gcc/bin/../lib/gcc -Lc:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/lib -Lc:/id/gcc/bin/../arm-none-eabi/lib C:\Users\MAXID~1\AppData\Local\Temp\cc1lU5Gg.o -lstdc++_nano -lm --start-group -lgcc -lg_nano -lc_nano --end-group --start-group -lgcc -lc_nano --end-group c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp/crtend.o c:/id/gcc/bin/../lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp/crtn.o -T ./../src/firmware.ld
COLLECT_GCC_OPTIONS='-o' 'main.elf' '-v' '-mcpu=cortex-m0' '-mthumb' '-specs=nano.specs' '-e' 'reset_handler' '-T' './../src/firmware.ld' '-ggdb' '-O0' '-Wall' '-Wextra' '-Wpedantic' '-Werror' '-mfloat-abi=soft' '-march=armv6s-m'
To be fair, I have zero idea of what could be wrong - I tried the default linker script, .fini_array did not appear.
The only idea which comes to mind is that my compiler was built with some kind of a flag which disabled the .fini_array section???
Any ideas will be much appreciated!
UPDATE: after further investigation, it seems that the destructors have more to do with __cxa_atexit... Will investigate further.
UPDATE2: Thanks to this wonderful discussion here https://forum.osdev.org/viewtopic.php?f=13&t=36728 I added -fno-use-cxa-atexit, and the .fini_array section appeared!
Ok, so for those who are interested, there are two ways for gcc to generate calls to global destructors. One way is via __cxa_atexit. The compiler will inject a piece of code into the global constructor, which uses __cxa_atexit to register the destructor of that object. This way, when exit is called, the runtime "knows" which destructors to invoke. This complexity is needed to deal with loading/unloading of shared libraries.
However, it is also possible to pass a -fno-cxa-atexit flag, and the compiler will put the calls to global destructors into .fini_array. The Newlib then uses this section to invoke the destructors.
More can be found here: https://forum.osdev.org/viewtopic.php?f=13&t=36728
I am trying to use a custom RAM section to be able to pass information across reboot. This section will not be erased at boot and so the variables placed in this section will be kept across reboots (if there is no alimentation loss of course).
I use GNU toolchain and a Cortex-M0 (STM32) MCU
So I added in the linker script a new memory area before RAM :
RAM_PERSIST (xrw) : ORIGIN = 0x20000000, LENGTH = 0x0040
RAM (xrw) : ORIGIN = 0x20000040, LENGTH = 0x0FD0
Then a section to go in there :
.pds :
{
KEEP(*(.pds))
} >RAM_PERSIST
Finally in the C code, I declare some data in this section :
data_t __attribute((section(".pds")) data;
I does compile but I could not upload the generated binary on my target. Using objdump I discovered that my firmware got a new section ".sec2" beginning at 0x20000000 :
> (...)/arm-none-eabi-objdump -s ./obj/firmware.hex | tail
8006d20 f8bc08bc 9e467047 f8b5c046 f8bc08bc .....FpG...F....
8006d30 9e467047 e9000008 c1000008 00127a00 .FpG..........z.
8006d40 19000000 e0930400 409c0000 400d0300 ........#...#...
8006d50 c0c62d00 30750000 ffffffff 01000000 ..-.0u..........
8006d60 04000000 ....
Contents of section .sec2:
20000000 00000000 00000000 00000000 00000000 ................
20000010 00000000 00000000 00000000 00000000 ................
20000020 00000000 00000000 00000000 00000000 ................
20000030 00000000 00000000 00000000 00000000 ................
So I think I have to tell the linker this section is not in the flash so must not be part of the firmware.
Am I right ? If so, how to do that ?
Thanks by advance.
MEMORY
{
rom : ORIGIN = 0x00000000, LENGTH = 0x40000
ram : ORIGIN = 0x20000000, LENGTH = 0x4000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
I had more control/success when I stopped using xrw, etc in the memory definition and instead went with control over .text, .bss, .data, etc. and if you then further want a specific object somewhere you add that. etc...
I did achieve what I wanted by adding NOLOAD attibute to my custom section :
.pds (NOLOAD): { KEEP(*(.pds)) } >RAM
Here is the NOLOAD description (gcc documentation) :
(NOLOAD)
The (NOLOAD) directive will mark a section to not be loaded at run time. The linker will process the section normally, but will mark
it so that a program loader will not load it into memory. For example,
in the script sample below, the ROM section is addressed at memory
location 0 and does not need to be loaded when the program is run.
The contents of the ROM section will appear in the linker output file
as usual.
SECTIONS {
ROM 0 (NOLOAD) : { ... }
...
}
I found a similar post which helped me, I add a link here for reference : GCC (NOLOAD) directive loads memory into section anyway
this is the problem:
When I link my scripts in C, using ld, when I generate elf32-i386 files as output format in ld, putting it as OUTPUT_FORMAT() in the ld script, I dont have any error, but if I try to put in this last OUTPUT_FORMAT() "binary" or try to output a file with .bin extension, I get a mixture of errors like:
kernel.o: In function `k_main':
kernel.c:(.text+0xe): undefined reference to `_GLOBAL_OFFSET_TABLE_'
kernelutils.o: In function `k_clear_screen':
kernelutils.c:(.text+0xc): undefined reference to `_GLOBAL_OFFSET_TABLE_'
kernelutils.o: In function `k_clear_screen_front':
kernelutils.c:(.text+0x56): undefined reference to `_GLOBAL_OFFSET_TABLE_'
kernelutils.o: In function `k_printf':
kernelutils.c:(.text+0xa0): undefined reference to `_GLOBAL_OFFSET_TABLE_'
kernelutils.o: In function `k_sleep_3sec':
kernelutils.c:(.text+0x152): undefined reference to `_GLOBAL_OFFSET_TABLE_'
kernelmalloc.o:kernelmalloc.c:(.text+0xc): more undefined references to `_GLOBAL_OFFSET_TABLE_' follow
This not only happens when compiling specific scripts, all scripts that try to use ld to link, or gcc since this calls ld, die in the attempt of get a binary with .bin extension.
When showing the symbols of one of the executables (kernel.o in the output of above) I see that the symbol _GLOBAL_OFFSET_TABLE_ isnt defined, and the most scary part, all the functions that returned error in the error output of above have their symbols removed, this is the nm output:
cristian#mymethodman:~/Desktop/kernel/0.0.3/Archivos$ nm kernel.o
U _GLOBAL_OFFSET_TABLE_
U k_clear_screen
U k_clear_screen_front
00000000 T k_main
U k_malloc
U k_printf
U k_sleep_3sec
00000000 T __x86.get_pc_thunk.bx
How I can solve this? I will leave the linker script below to ensure it isn a problem of the .ld file, with both "to get elf" and "to get binary" versions. Thanks in advance!
Ld scripts:
To get binary:
ENTRY(loader)
OUTPUT_FORMAT(binary)
SECTIONS {
/* The kernel will live at 3GB + 1MB in the virtual
address space, which will be mapped to 1MB in the
physical address space. */
. = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000) {
*(.text)
*(.rodata*)
}
.data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
*(.data)
}
.bss : AT(ADDR(.bss) - 0xC0000000) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
To get ELF:
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
/* The kernel will live at 3GB + 1MB in the virtual
address space, which will be mapped to 1MB in the
physical address space. */
. = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000) {
*(.text)
*(.rodata*)
}
.data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
*(.data)
}
.bss : AT(ADDR(.bss) - 0xC0000000) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
As yo ucan see between both only changes the OUTPUT_FORMAT() line.
Your toolchain probably defaults to generating position-independent executables (PIE). Try compiling with gcc -fno-pie.
If you want to keep PIE for security reasons, you'll need a more complicated linker script and something that performs the initial relocation (such as a dynamic linker, but simpler constructions are possible as well).
Where can I find the actual linker script and settings gcc uses?
Things I've tried:
For concreteness, let's consider a small program:
empty.c
int main(void)
{
return 0;
}
build it statically, and look at the result:
$ gcc -static -o empty empty.c
$ readelf -W -l empty
Elf file type is EXEC (Executable file)
Entry point 0x400f4e
There are 6 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0bf581 0x0bf581 R E 0x200000
LOAD 0x0bfeb0 0x00000000006bfeb0 0x00000000006bfeb0 0x001d80 0x0042d8 RW 0x200000
NOTE 0x000190 0x0000000000400190 0x0000000000400190 0x000044 0x000044 R 0x4
TLS 0x0bfeb0 0x00000000006bfeb0 0x00000000006bfeb0 0x000020 0x000058 R 0x10
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x0bfeb0 0x00000000006bfeb0 0x00000000006bfeb0 0x000150 0x000150 R 0x1
Section to Segment mapping:
Segment Sections...
00 .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table
01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs
02 .note.ABI-tag .note.gnu.build-id
03 .tdata .tbss
04
05 .tdata .init_array .fini_array .jcr .data.rel.ro .got
Note the various sections, grouped into segments, and placed into memory regions of various permissions.
Now let's attempt to get as much information as possible on how it did this linking.
$ gcc -static -o empty empty.c -Wl,--verbose
GNU ld (GNU Binutils for Ubuntu) 2.24
Supported emulations:
elf_x86_64
elf32_x86_64
elf_i386
i386linux
elf_l1om
elf_k1om
i386pep
i386pe
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
"elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rela.dyn :
{
*(.rela.init)
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
*(.rela.fini)
*(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
*(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
*(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
*(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
*(.rela.ctors)
*(.rela.dtors)
*(.rela.got)
*(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
*(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
*(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
*(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
*(.rela.ifunc)
}
.rela.plt :
{
*(.rela.plt)
PROVIDE_HIDDEN (__rela_iplt_start = .);
*(.rela.iplt)
PROVIDE_HIDDEN (__rela_iplt_end = .);
}
.init :
{
KEEP (*(SORT_NONE(.init)))
}
.plt : { *(.plt) *(.iplt) }
.text :
{
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
}
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
.eh_frame_hdr : { *(.eh_frame_hdr) }
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) }
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table
.gcc_except_table.*) }
/* These sections are generated by the Sun/Oracle C++ compiler. */
.exception_ranges : ONLY_IF_RO { *(.exception_ranges
.exception_ranges*) }
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
/* Exception handling */
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) }
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) }
/* Thread Local Storage sections */
.tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
. = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
.got.plt : { *(.got.plt) *(.igot.plt) }
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
_edata = .; PROVIDE (edata = .);
. = .;
__bss_start = .;
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we don't
pad the .data section. */
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
.lbss :
{
*(.dynlbss)
*(.lbss .lbss.* .gnu.linkonce.lb.*)
*(LARGE_COMMON)
}
. = ALIGN(64 / 8);
. = SEGMENT_START("ldata-segment", .);
.lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.lrodata .lrodata.* .gnu.linkonce.lr.*)
}
.ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.ldata .ldata.* .gnu.linkonce.l.*)
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
. = ALIGN(64 / 8);
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3 */
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF Extension. */
.debug_macro 0 : { *(.debug_macro) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}
==================================================
... <snip searching and linking actual object files>
But the script, while long, is missing most of the import information previously listed.
How does it know which sections to gather into different load segments?
There is no PHDRS command, and while the use of SEGMENT_START suggests there are some standard segments for this system defined somewhere else, none of the sections are listed with an associated segment.
Furthermore, how does it know where to load these segments, or what permissions these memory regions have?
There is no MEMORY command. And again, if there are some standard memory regions for this system defined somewhere else, none of the sections list which memory region to use.
When I've seen default linker scripts before for microcontrollers, they were incredibly detailed. This output however suggests there are more scripts and settings somewhere.
Where are these other linker script definitions and settings stored?
Well, I know that this is an old question, but I also found it frustrating that there is no precise info about options that are used during the linking process. This answer shows my journey to find them.
First of all, I was looking into official docs https://gcc.gnu.org/onlinedocs/ - I searched the GCC Manual and GCC Internals Manual. The only meaningful information that I found is that gcc uses an internal tool called collect2 to invoke the linker. According to https://gcc.gnu.org/onlinedocs/gccint/Collect2.html "The program collect2 works by linking the program once and looking through the linker output file for symbols with particular names indicating they are constructor functions". So it's used to make linking possible.
Next thing that I tried is getting through source code. You can browse code here https://code.woboq.org/gcc/gcc/collect2.c.html . The problem is that it wasn't really helpful. But I noticed that collect2 fork_execute function to invoke ld. You can deep dive into fork_execute to find out that it will fork (execute a new program in the forked program) and wait for it to finish. Because both forks and execs are system calls (to put it quickly - system calls are the way/functions the application uses to communicate with a system). I decided to give it a try.
So I made the simple program that doesn't require any compilation (it's already compiled to object file - so everything that gcc have to do is linking).
[Alex#Normandy tmp]$ gcc hello.c.s -o hello_gcc
[Alex#Normandy tmp]$ ./hello_gcc
Hello, World!
Then I use strace with following options:
-o forked.log save the output to forked.log
-s 1024 variables shorter than 1024 chars are not truncated (default 32 was not enough)
-f - enables strace on forked processes
-e trace=/exec - filter system calls so only ones starting with exec are shown
The final output was following.
[Alex#Normandy tmp]$ strace -o forked.log -s 1024 -f -e trace=/exec gcc hello.c.s -o hello_gcc
[Alex#Normandy tmp]$ grep 'ld' forked.log
2153 execve("/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2", ["/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2", "--build-id", "--no-add-needed", "--eh-frame-hdr", "--hash-style=gnu", "-m", "elf_x86_64", "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2", "-o", "hello_gcc", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o", "-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5", "-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64", "-L/lib/../lib64", "-L/usr/lib/../lib64", "-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../..", "/tmp/ccyl36jf.o", "-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed", "-lc", "-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o"], 0x17b9da0 /* 61 vars */) = 0
2154 execve("/usr/bin/ld", ["/usr/bin/ld", "--build-id", "--no-add-needed", "--eh-frame-hdr", "--hash-style=gnu", "-m", "elf_x86_64", "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2", "-o", "hello_gcc", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o", "-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5", "-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64", "-L/lib/../lib64", "-L/usr/lib/../lib64", "-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../..", "/tmp/ccyl36jf.o", "-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed", "-lc", "-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o", "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o"], 0x7fff14226a98 /* 61 vars */) = 0
So used ld command was
/usr/bin/ld --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o hello_gcc /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. /tmp/ccyl36jf.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o
So what the f*** was made? Well, all options can be found in the manual. Here is decomposed output.
/usr/bin/ld - linker program
--build-id - add build-id to binary. In my system default it is sha1.
--no-add-needed - it is depracaceted name for --no-copy-dt-needed-entries - it is connected with DT_NEEDED tags inside ELF, if I get that correctly it means that DT_NEEDED tag won't be copied from input libraries.
--eh-frame-hdr - "Request creation of ".eh_frame_hdr" section and ELF "PT_GNU_EH_FRAME" segment header." Whatever that means.
--hash-style=gnu - "Set the type of linker's hash table(s)." Default is sysv, but there is a newer format gnu. Binary can also have a hash table(s) in both formats.
-m elf_x86_64 - linkers emulates (makes elf type binary for x86_64)
-dynamic-linker /lib64/ld-linux-x86-64.so.2 - set name of expected dynamic linker
-o hello_gcc - set output binary
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o - code that is run before main of actual program
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o- code that is run before main of actual program
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o - code that is run before main of actual program
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 - additional library search path
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 - additional library search path
-L/lib/../lib64 - additional library search path
-L/usr/lib/../lib64 - additional library search path
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. - additional library search path
/tmp/ccyl36jf.o - this is actual program (binary object) with it's main function
-lgcc - -l option - "Add the archive or object file specified by namespec to the list of files to link." In that case it is gcc.
--as-needed - enable "as-needed" mode that checks if on particular point following library (namespace?) is needed
-lgcc_s - add gcc_s note that only if it's really needed at this moment.
--no-as-needed - disable "as-needed" mode that checks if on particular point following library (namespace?) is needed
-lc- standard C namespace/library
-lgcc - this lib should be already set. There might be something between this and previous usage of this option.
--as-needed - set "as-needed mode. There might be something between this and previous usage of this option.
-lgcc_s - already described. There might be something between this and previous usage of this option.
--no-as-needed -- disable "as-needed mode".
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o - additional code that run when program finish
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o - additional code that run when program finish.
More about: crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o - they are startup, initialization, constructor, destructor and finalization files (according to Building Embedded Linux Systems By Karim Yaghmour).
Probably simpler way
During writing this answer I also "discovered" that you can invoke gcc with -v option and it will return COLLECT_GCC_OPTIONS, that is identical to invoked ld
COLLECT_GCC_OPTIONS='-v' '-o' 'hello_gcc' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o hello_gcc /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. hello_gcc.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o
Still, if you want to be sure for 100% how ld was invoked - the strace is your best bet.
Lastly, note that I used Enterprise Linux v7 and v8 system to check if I'm right. Both of them uses the x86_64 arch, and the results might be different on different architectures.
On my Ubuntu system, the linker scripts are located at:
/lib/x86_64-linux-gnu/ldscripts
The base script appears to be chosen based on the target architecture, such as elf_x86_64, and the for each base architecture there are several variants.
I am not sure, but the variant appears to be chosen based on certain linker options.