When an ELF file needs to perform actions before main begins executing, this is encoded in a section called .init_array
as an array of function pointers to be called.
For some reason, this section is marked as writable. But there does not appear to be any reason for anyone to write to that section. Why is this section not read-only?
But there does not appear to be any reason for anyone to write to that section.
Let's look at at the section details of a well-known system program:
$ readelf -SW /usr/bin/cat
There are 31 section headers, starting at offset 0x9218:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
[ 2] .note.gnu.property NOTE 0000000000000338 000338 000030 00 A 0 0 8
[ 3] .note.gnu.build-id NOTE 0000000000000368 000368 000024 00 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000038c 00038c 000020 00 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003b0 0003b0 000024 00 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003d8 0003d8 000660 18 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000a38 000a38 00033c 00 A 0 0 1
[ 8] .gnu.version VERSYM 0000000000000d74 000d74 000088 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000e00 000e00 000090 00 A 7 1 8
[10] .rela.dyn RELA 0000000000000e90 000e90 000288 18 A 6 0 8
[11] .rela.plt RELA 0000000000001118 001118 000528 18 AI 6 25 8
[12] .init PROGBITS 0000000000002000 002000 00001b 00 AX 0 0 4
[13] .plt PROGBITS 0000000000002020 002020 000380 10 AX 0 0 16
[14] .plt.got PROGBITS 00000000000023a0 0023a0 000010 10 AX 0 0 16
[15] .plt.sec PROGBITS 00000000000023b0 0023b0 000370 10 AX 0 0 16
[16] .text PROGBITS 0000000000002720 002720 003d82 00 AX 0 0 16
[17] .fini PROGBITS 00000000000064a4 0064a4 00000d 00 AX 0 0 4
[18] .rodata PROGBITS 0000000000007000 007000 000de8 00 A 0 0 32
[19] .eh_frame_hdr PROGBITS 0000000000007de8 007de8 0000c4 00 A 0 0 4
[20] .eh_frame PROGBITS 0000000000007eb0 007eb0 000368 00 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000009a90 008a90 000008 08 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000009a98 008a98 000008 08 WA 0 0 8
[23] .data.rel.ro PROGBITS 0000000000009aa0 008aa0 000148 00 WA 0 0 32
[24] .dynamic DYNAMIC 0000000000009be8 008be8 0001f0 10 WA 7 0 8
[25] .got PROGBITS 0000000000009dd8 008dd8 000220 08 WA 0 0 8
[26] .data PROGBITS 000000000000a000 009000 000068 00 WA 0 0 16
[27] .bss NOBITS 000000000000a080 009068 000140 00 WA 0 0 32
[28] .gnu_debugaltlink PROGBITS 0000000000000000 009068 000049 00 0 0 1
[29] .gnu_debuglink PROGBITS 0000000000000000 0090b4 000034 00 0 0 4
[30] .shstrtab STRTAB 0000000000000000 0090e8 00012f 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
The writable sections are:
[21] .init_array INIT_ARRAY 0000000000009a90 008a90 000008 08 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000009a98 008a98 000008 08 WA 0 0 8
[23] .data.rel.ro PROGBITS 0000000000009aa0 008aa0 000148 00 WA 0 0 32
[24] .dynamic DYNAMIC 0000000000009be8 008be8 0001f0 10 WA 7 0 8
[25] .got PROGBITS 0000000000009dd8 008dd8 000220 08 WA 0 0 8
[26] .data PROGBITS 000000000000a000 009000 000068 00 WA 0 0 16
[27] .bss NOBITS 000000000000a080 009068 000140 00 WA 0 0 32
We know why someone might want to write to .data
(writable data) and .bss
(statically
initialized writable data), but why would anyone want to write to any of the
.init_array
, .fini_array
, .data.rel.ro
, .dynamic
or .got
sections?
Nobody normally does, but in a DSO or ordinary program (dynamically linked, PIE) the dynamic linker wants the ability to:
.got
, to do dynamic symbol resolution..init_array
, .fini_array
, .dynamic
and .data.rel.ro
(if it exists) to do dynamic relocations. In the case of .init_array
and .fini_array
, that means dynamic relocations on the references these arrays contain to module initialisation and finalisation functions.That's why these sections are writable. But it doesn't follow that you can write a program that writes to any of them. (At least not with default linkage).
Let's see some more information about /usr/bin/cat
, this time the program
headers:
$ readelf -lW /usr/bin/cat
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x3ac0
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0002d8 0x0002d8 R 0x8
INTERP 0x000318 0x0000000000000318 0x0000000000000318 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x001640 0x001640 R 0x1000
LOAD 0x002000 0x0000000000002000 0x0000000000002000 0x0044b1 0x0044b1 R E 0x1000
LOAD 0x007000 0x0000000000007000 0x0000000000007000 0x001218 0x001218 R 0x1000
LOAD 0x008a90 0x0000000000009a90 0x0000000000009a90 0x0005d8 0x000730 RW 0x1000
DYNAMIC 0x008be8 0x0000000000009be8 0x0000000000009be8 0x0001f0 0x0001f0 RW 0x8
NOTE 0x000338 0x0000000000000338 0x0000000000000338 0x000030 0x000030 R 0x8
NOTE 0x000368 0x0000000000000368 0x0000000000000368 0x000044 0x000044 R 0x4
GNU_PROPERTY 0x000338 0x0000000000000338 0x0000000000000338 0x000030 0x000030 R 0x8
GNU_EH_FRAME 0x007de8 0x0000000000007de8 0x0000000000007de8 0x0000c4 0x0000c4 R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x008a90 0x0000000000009a90 0x0000000000009a90 0x000570 0x000570 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .data.rel.ro .dynamic .got
You see that final memory segment GNU_RELRO
? "RELRO" there stands for something like "relocated readonly".
It is marked R
( = readonly). It is not a real (LOAD
) segment;
it's a virtual segment. You see it has the same image base address, 0x8a90. as LOAD
segment #05, which is RW
(read/write), but LOAD
segment #5 is 0x730 bytes long while GNU_RELRO
is 0x570 bytes, 0x1C0 bytes shorter.
The GNU_RELRO
segment is just an initial sub-segment of LOAD
segment #5 that has been marked readonly
to the kernel by an mprotect
call. So it doesn't
count at runtime that LOAD
segment #5 is all RW
: the initial 0x570 bytes of it are readonly, because
they're inside the GNU_RELRO
segment. The final 0x1C0 bytes of LOAD
segment #5 are still RW
.
This is the means by which dynamic linker can edit sections that
are destined to be readonly in the process. It does the edits on those sections, and as soon
as it's finished it mprotect
s them all, so no further writing is possible.
The Section to Segment mapping
tells us that the sections mapped into LOAD
segment #5 are:
05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss
and the ones mapped into GNU_RELRO
(#12) are:
12 .init_array .fini_array .data.rel.ro .dynamic .got
The LOAD
segment #5 sections that are left writable are .data
and .bss
.
The static linker co-operates in this mechanism, per it's default linker script,
by laying out sections in the image so that the all the ones the GNU_RELRO
segment needs to span are consecutive.
So if you were to write a program that tried to re-write its own .init_array
,
the dynamic linker would relocate the entries in that array as usual, but when
your program attempted to write to it (even in a pre-main initialisation function) it would segfault with a memory protection error.
That's per the default behaviour of the linker. If for some reason you really
wanted to write a program that could itself write to sections that get
GNU_RELRO
protection by default, you can link it with the options -Wl,-z,norelro
,
and then the GNU_RELRO
segment just won't exist.
Optional reading. The rest is only interesting if you'd like to see all
that unfolding live in gdb
Here are the source files of a C program that wants to decide at runtime which
pre-main initialisation functions will be registered in the .init_array
,
so it needs to write to the .init_array
. It's a silly program - the same functional
effect would be achieved without touching the .init_array
-
and it could be coded more concisely in just one source file, but splitting it up
yields a more helpful linkage map.
$ tail -n +1 *.h *.c
==> decls.h <==
#ifndef DECLS_H
#define DECLS_H
typedef void (*pfunc_t)();
__attribute__((constructor(101),visibility("hidden")))
extern void init(int argc, char *argv[]);
__attribute__((visibility("hidden")))
extern void foo_init();
__attribute__((visibility("hidden")))
extern void bar_init();
__attribute__((visibility("hidden")))
extern void default_opt_init();
__attribute__((section(".init_array"),visibility("hidden")))
extern pfunc_t opt_init;
#endif
==> bar_init.c <==
#include "decls.h"
#include <stdio.h>
void bar_init() {
printf("%s()\n",__func__);
}
==> default_opt_init.c <==
#include "decls.h"
#include <stdio.h>
void default_opt_init() {
printf("%s()\n",__func__);
}
==> foo_init.c <==
#include "decls.h"
#include <stdio.h>
void foo_init() {
printf("%s()\n",__func__);
}
==> init.c <==
#define GNU_SOURCE
#include "decls.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
extern pfunc_t __init_array_start;
extern pfunc_t __init_array_end;
static char const * which_initor(pfunc_t pinitor) {
assert(pinitor);
if (pinitor == &default_opt_init) {
return "default_opt_init";
}
if (pinitor == (pfunc_t)&init) {
return "init";
}
if (pinitor == &foo_init) {
return "foo_init";
}
if (pinitor == &bar_init) {
return "bar_init";
}
return "<Unidentified>";
}
static void report_init_array(void)
{
printf(".init_array starts at 0x%lx and ends at 0x%lx\n",
(size_t)&__init_array_start,(size_t)&__init_array_end);
size_t ninitors = &__init_array_end - &__init_array_start;
printf("There are %lu entries in .init_array\n",ninitors);
printf("Which are {\n");
unsigned i = 0;
for (pfunc_t *ppinitor = &__init_array_start;
ppinitor < &__init_array_end; ++ppinitor,++i) {
printf("[%u] 0x%lx (%s)\n",
i,(size_t)*ppinitor,which_initor(*ppinitor));
}
printf("}\n");
}
static void insert_opt_initor(char const *name, pfunc_t pinitor)
{
printf("Setting .init_array[%ld] = %s\n",
&opt_init - &__init_array_start,name);
opt_init = pinitor;
report_init_array();
}
void init(int argc, char *argv[])
{
printf("%s(argc=%d argv=0x%p)\n",__func__,argc,(void *)argv);
report_init_array();
if (argc != 2) {
return;
}
if (0 == strcmp(argv[1],"foo_init")) {
insert_opt_initor("foo_init",&foo_init);
return;
}
if (0 == strcmp(argv[1],"bar_init")) {
insert_opt_initor("bar_init",&bar_init);
return;
}
return;
}
==> main.c <==
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("%s(argc=%d argv=0x%p)\n",__func__,argc,(void *)argv);
return 0;
}
==> opt_init.c <==
#include "decls.h"
pfunc_t opt_init = &default_opt_init;
The program has one pre-main initialisation function init
with
maximum initialisation priority = 101, which means the address of
init
is registered first in the .init_array
. init
receives the commandline
arguments argc, argv
before main
does. (Because any GNU initialization function can receive the argc, argv
. Nothing to do with initialisation priority.)
The program registers a second initialisation function pointer opt_init
in
the .init_array
which takes lower (default) priority than 101 and is initialised to point to
the function default_opt_init
. This function will therefore be the second
pre-main initialiser to run after init
by default. But if the name
"foo_init" or "bar_init" is passed to init
in argv[1]
then the
address of the function with that name will be written into the .init_array
at the
address opt_init
and will be run instead of default_opt_init
.
So the first initialisation function decides based on the commandline arguments
whether the second initialisation function will be default_opt_init
, foo_init
or bar_init
either by writing the address of (foo|bar)_init
in the
opt_init
slot in .init_array
, or else leaving .init_array
untouched.
Or that's the plan.
Compile and link:
$ gcc --version | head -n1
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
$ ls *.c
bar_init.c default_opt_init.c foo_init.c init.c main.c opt_init.c
$ $ gcc -g -c *.c -Wall -Wextra -pedantic
$ ls *.o
bar_init.o default_opt_init.o foo_init.o init.o main.o opt_init.o
$ gcc -o prog *.o -Wl,-Map=mapfile
The mapfile shows:
$ grep -A4 -P '(^|\W).init_array' mapfile
.init_array 0x0000000000003d90 0x18
0x0000000000003d90 PROVIDE (__init_array_start = .)
*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))
.init_array.00101
0x0000000000003d90 0x8 init.o
*(.init_array EXCLUDE_FILE(*crtend?.o *crtend.o *crtbegin?.o *crtbegin.o) .ctors)
.init_array 0x0000000000003d98 0x8 /usr/lib/gcc/x86_64-linux-gnu/13/crtbeginS.o
.init_array 0x0000000000003da0 0x8 opt_init.o
0x0000000000003da0 opt_init
0x0000000000003da8 PROVIDE (__init_array_end = .)
.fini_array 0x0000000000003da8 0x8
that the .init_array
output section starts at offset 0x3d90
( = __init_array_start
) in the image and has 3
0x8 byte (pointer-size) input sections coming from:
init.o
(ours)/usr/lib/gcc/x86_64-linux-gnu/13/crtbeginS.o
(not ours, C runtime boilerplate)opt_init.o
(ours)ending at 0x3da8
( = __init_array_end
), which is also the
start of the .fini_array
.
init.o
provides our:
__attribute__((constructor(101),visibility("hidden")))
void init(int argc, char *argv[]);
and opt_init.o
provides our:
__attribute__((section(".init_array"),visibility("hidden")))
pfunc_t opt_init = &default_opt_init;
init
is sorted into first position in the array
because I gave it maximum initialisation priority 101 and the
linker sorts the output section:
SORT_BY_INIT_PRIORITY(.init_array.*)
Entries with no specified initialisation priority get the default 65535 (lowest priority).
Now let's run the program under gdb
, giving argument "foo_init" so that the second
initialisation function to run will be foo_init
instead of default_opt_init
:
$ gdb --args prog "foo_init"
...[cut preamble]...
Reading symbols from prog...
We'll have initial breakpoints on all of:
_start
- entry point of programinit
- our top-priority initialisation functioninsert_opt_initor
- we try to write into the .init_array
main
.(gdb) b _start
Breakpoint 1 at 0x10c0
(gdb) b init
Breakpoint 2 at 0x1237: file init.c, line 53.
(gdb) b insert_opt_initor
Breakpoint 3 at 0x148c: file init.c, line 46.
(gdb) b main
Breakpoint 4 at 0x14eb: file main.c, line 5.
Before we run, let's see the unrelocatad addresses of our pre-registered initialisation
functions init
and default_opt_init
and the .init_array
itself.
(gdb) p/x &init
$2 = 0x1224
(gdb) p/x &default_opt_init
$3 = 0x11d2
(gdb) p/x &__init_array_start
$4 = 0x3d90
All as per the mapfile. Run:
(gdb) r
Starting program: /home/imk/develop/so/scrap/prog foo_init
Breakpoint 1.2, 0x00007ffff7fe4540 in _start () from /lib64/ld-linux-x86-64.so.2
Entered our program. By now __init_array_start
has already been relocated:
(gdb) p/x &__init_array_start
$2 = 0x555555557d90
&__init_array_start
is a pointer-to-pointer-to-function. Let's see the
current contents of elements #0 and #2:
(gdb) p/x ((void (**)())(&__init_array_start))[0]
$9 = 0x1224
(gdb) p/x ((void (**)())(&__init_array_start))[2]
$10 = 0x11d2
They're still holding the unrelocated addresses we've just seen for our init
and default_opt_init
.
Let's put watchpoints on elements #0 and #2:
(gdb) watch ((void (**)())(&__init_array_start))[0]
Hardware watchpoint 5: ((void (**)())(&__init_array_start))[0]
(gdb) watch ((void (**)())(&__init_array_start))[2]
Hardware watchpoint 6: ((void (**)())(&__init_array_start))[2]
and carry on:
(gdb) c
Continuing.
Hardware watchpoint 5: ((void (**)())(&__init_array_start))[0]
Old value = (void (*)()) 0x1224
New value = (void (*)()) 0x555555555224 <init>
elf_dynamic_do_Rela (skip_ifunc=<optimised out>, lazy=0, nrelative=5, relsize=336, reladdr=93824992232888, scope=<optimised out>, map=0x7ffff7ffe2e0) at ./elf/do-rel.h:123
warning: 123 ./elf/do-rel.h: No such file or directory
Now we're executing in the dynamic linker at elf_dynamic_do_Rela
, doing dynamic relocations in
our .init_array
and have just written element #0, setting it to the runtime address of init
.
(gdb) c
Continuing.
Hardware watchpoint 6: ((void (**)())(&__init_array_start))[2]
Old value = (void (*)()) 0x11d2
New value = (void (*)()) 0x5555555551d2 <default_opt_init>
elf_dynamic_do_Rela (skip_ifunc=<optimised out>, lazy=0, nrelative=5, relsize=336, reladdr=93824992232888, scope=<optimised out>, map=0x7ffff7ffe2e0) at ./elf/do-rel.h:123
123 in ./elf/do-rel.h
And that's the write to element #2, setting it to the runtime address of
default_opt_init
.
Now let's put a breakpoint on mprotect
, to catch the
GNU_RELRO
segment being set up:
(gdb) b mprotect
Breakpoint 7 at 0x7ffff7feadb0: file ../sysdeps/unix/syscall-template.S, line 117.
(gdb) c
Continuing.
Breakpoint 7, __GI_mprotect () at ../sysdeps/unix/syscall-template.S:117
warning: 117 ../sysdeps/unix/syscall-template.S: No such file or directory
(gdb) info reg
rax 0x1000 4096
rbx 0x0 0
rcx 0x555555557d90 93824992247184
rdx 0x1 1
rsi 0x1000 4096
rdi 0x555555557000 93824992243712
...[cut]...
That's GNU_RELRO
being enacted. Remember rcx = 0x555555557d90
is the address
&__init_array_start
. rax = 0x1000
is the size ( = 1 page) to be protected.
rdi = 0x555555557000
is the nearest preceding page boundary where protection
can start. There'll more mprotect
calls to follow that are irrelevant to us, so
we'll stop watching them:
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y <MULTIPLE>
breakpoint already hit 1 time
1.1 y 0x00005555555550c0 <_start>
1.2 y 0x00007ffff7fe4540 <_start>
2 breakpoint keep y 0x0000555555555237 in init at init.c:53
3 breakpoint keep y 0x000055555555548c in insert_opt_initor at init.c:46
4 breakpoint keep y 0x00005555555554eb in main at main.c:5
5 hw watchpoint keep y ((void (**)())(&__init_array_start))[0]
breakpoint already hit 1 time
6 hw watchpoint keep y ((void (**)())(&__init_array_start))[2]
breakpoint already hit 1 time
7 breakpoint keep y 0x00007ffff7feadb0 in mprotect at ../sysdeps/unix/syscall-template.S:117
breakpoint already hit 1 time
(gdb) del 7
and carry on:
(gdb) c
Continuing.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1.1, 0x00005555555550c0 in _start ()
We've left the dynamic linker and come back to our program:
(gdb) c
Continuing.
Breakpoint 2.1, init (argc=2, argv=0x7fffffffdd98) at init.c:53
53 printf("%s(argc=%d argv=0x%p)\n",__func__,argc,(void *)argv);
Now stopped at our init
:
(gdb) c
Continuing.
init(argc=2 argv=0x0x7fffffffdd98)
.init_array starts at 0x555555557d90 and ends at 0x555555557da8
There are 3 entries in .init_array
Which are {
[0] 0x555555555224 (init)
[1] 0x5555555551a0 (<Unidentified>)
[2] 0x5555555551d2 (default_opt_init)
}
Breakpoint 3, insert_opt_initor (name=0x555555556077 "foo_init", pinitor=0x5555555551fb <foo_init>) at init.c:46
46 &opt_init - &__init_array_start,name);
There's the first part of init
's console output and we've stopped at insert_opt_initor
,
where we'll try to replace &default_opt_init
with &foo_init
in the
the .init_array
:
(gdb) c
Continuing.
Setting .init_array[2] = foo_init
Program received signal SIGSEGV, Segmentation fault.
0x00005555555554c9 in insert_opt_initor (name=0x555555556077 "foo_init", pinitor=0x5555555551fb <foo_init>) at init.c:47
47 opt_init = pinitor;
Segfault, because the .init_array
is now in GNU_RELRO
.
We can avoid that death by relinking the program to suppress creation of
the GNU_RELRO
segment:
$ gcc -o prog *.o -Wl,-z,norelro
Then with no arguments the program runs by default:
./prog
init(argc=1 argv=0x0x7ffccf47d1d8)
.init_array starts at 0x5ea5ab5d6388 and ends at 0x5ea5ab5d63a0
There are 3 entries in .init_array
Which are {
[0] 0x5ea5ab5d4224 (init)
[1] 0x5ea5ab5d41a0 (<Unidentified>)
[2] 0x5ea5ab5d41d2 (default_opt_init)
}
default_opt_init()
main(argc=1 argv=0x0x7ffccf47d1d8)
And with this arg:
./prog "foo_init"
init(argc=2 argv=0x0x7ffc27cdde88)
.init_array starts at 0x635c204ab388 and ends at 0x635c204ab3a0
There are 3 entries in .init_array
Which are {
[0] 0x635c204a9224 (init)
[1] 0x635c204a91a0 (<Unidentified>)
[2] 0x635c204a91d2 (default_opt_init)
}
Setting .init_array[2] = foo_init
.init_array starts at 0x635c204ab388 and ends at 0x635c204ab3a0
There are 3 entries in .init_array
Which are {
[0] 0x635c204a9224 (init)
[1] 0x635c204a91a0 (<Unidentified>)
[2] 0x635c204a91fb (foo_init)
}
foo_init()
main(argc=2 argv=0x0x7ffc27cdde88)
And with this arg:
$ ./prog "bar_init"
init(argc=2 argv=0x0x7ffc85007218)
.init_array starts at 0x6099f0b20388 and ends at 0x6099f0b203a0
There are 3 entries in .init_array
Which are {
[0] 0x6099f0b1e224 (init)
[1] 0x6099f0b1e1a0 (<Unidentified>)
[2] 0x6099f0b1e1d2 (default_opt_init)
}
Setting .init_array[2] = bar_init
.init_array starts at 0x6099f0b20388 and ends at 0x6099f0b203a0
There are 3 entries in .init_array
Which are {
[0] 0x6099f0b1e224 (init)
[1] 0x6099f0b1e1a0 (<Unidentified>)
[2] 0x6099f0b1e1a9 (bar_init)
}
bar_init()
main(argc=2 argv=0x0x7ffc85007218)
In each case running to completion.