The __libc_init_array function from stdlib takes care to call all initializers or C++ constructors, registered to preinit_array and init_array. Inbetween preinit and init, it calls an extern _init function. The code looks as simple as:
#include <sys/types.h>
/* These magic symbols are provided by the linker. */
extern void (*__preinit_array_start []) (void) __attribute__((weak));
extern void (*__preinit_array_end []) (void) __attribute__((weak));
extern void (*__init_array_start []) (void) __attribute__((weak));
extern void (*__init_array_end []) (void) __attribute__((weak));
extern void _init (void);
void __libc_init_array (void)
{
size_t count;
size_t i;
count = __preinit_array_end - __preinit_array_start;
for (i = 0; i < count; i++)
__preinit_array_start[i] ();
_init ();
count = __init_array_end - __init_array_start;
for (i = 0; i < count; i++)
__init_array_start[i] ();
}
Also see: understanding the __libc_init_array.
If a custom startup code is implemented, it is required to perform this initialization, either by linking to 'init.o' or by implementing something similar to the code snippet above.
If at least building for arm-none-eabi ARMv7e target with newlib-nano specs, then the _init method gets linked in from crti.o and crtn.o, which provides just some empty stubs for _init and _fini. Did some search through all other stdlib objects for arm-none-eabi and found no other objects that will append sections to .init, which would be obsolete anyway. Here some disassembly of crti.o and crtn.o:
$ ./bin/arm-none-eabi-objdump.exe -j .init -D ./lib/gcc/arm-none-eabi/10.2.1/thumb/v7/nofp/crt?.o
./lib/gcc/arm-none-eabi/10.2.1/thumb/v7/nofp/crti.o: file format elf32-littlearm
Disassembly of section .init:
00000000 <_init>:
0: b5f8 push {r3, r4, r5, r6, r7, lr}
2: bf00 nop
./lib/gcc/arm-none-eabi/10.2.1/thumb/v7/nofp/crtn.o: file format elf32-littlearm
Disassembly of section .init:
00000000 <.init>:
0: bcf8 pop {r3, r4, r5, r6, r7}
2: bc08 pop {r3}
4: 469e mov lr, r3
6: 4770 bx lr
If somebody wants to use __libc_init_array in combination with linker option nostartfiles for this specific ARM target, it would be acceptable to provide an own _init stub method, to let the linker pass, as long as no other initialization code is emitted to section .init, other than this from crti.o and crtn.o. A stub could look like:
extern "C" void _init(void) {;}
The special functions _init and _fini are some historic left-overs to control constructors and destructors. However, they are obsolete, and their use can lead to unpredictable results. No modern library should make use of these anymore, and make use of the GCC function attributes constructor and destructor instead, which add methods to those tables inside .preinit_array, .init_array and .fini_array sections.
If it is known that there was some initialization code emitted to .init (even if this is obsolete today), then a _init(void) function should be provided, that will be running this initialization code by calling the start address of the .init section.