You are not logged in.

#1 2025-03-29 20:46:18

Brocellous
Member
Registered: 2017-11-27
Posts: 152

Is the .init section actually used?

While poking around in some short utility I was writing, I noticed something that got me curious:

$ cat hello.c
#include <stdio.h>

int main(int argc, char **argv) {
    puts("Hello, world!");
}
$ cc -o ./hello hello.c
$ readelf -e ./hello
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1040
  Start of program headers:          64 (bytes into file)
  Start of section headers:          13496 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         14
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.pr[...] NOTE             0000000000000350  00000350
       0000000000000040  0000000000000000   A       0     0     8
  [ 2] .note.gnu.bu[...] NOTE             0000000000000390  00000390
       0000000000000024  0000000000000000   A       0     0     4
  [ 3] .interp           PROGBITS         00000000000003b4  000003b4
       000000000000001c  0000000000000000   A       0     0     1
  [ 4] .gnu.hash         GNU_HASH         00000000000003d0  000003d0
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000000003f0  000003f0
       00000000000000a8  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000000498  00000498
       000000000000008d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000000526  00000526
       000000000000000e  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000000538  00000538
       0000000000000030  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000000568  00000568
       00000000000000c0  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000000628  00000628
       0000000000000018  0000000000000018  AI       5    23     8
  [11] .init             PROGBITS         0000000000001000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000001020  00001020
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         0000000000001040  00001040
       000000000000011e  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000001160  00001160
       000000000000000d  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         0000000000002000  00002000
       0000000000000012  0000000000000000   A       0     0     4
  [16] .eh_frame_hdr     PROGBITS         0000000000002014  00002014
       0000000000000024  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000002038  00002038
       000000000000007c  0000000000000000   A       0     0     8
  [18] .note.ABI-tag     NOTE             00000000000020b4  000020b4
       0000000000000020  0000000000000000   A       0     0     4
  [19] .init_array       INIT_ARRAY       0000000000003dd0  00002dd0
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000003dd8  00002dd8
       0000000000000008  0000000000000008  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000003de0  00002de0
       00000000000001e0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000003fc0  00002fc0
       0000000000000028  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000003fe8  00002fe8
       0000000000000020  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000004008  00003008
       0000000000000010  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000004018  00003018
       0000000000000008  0000000000000000  WA       0     0     1
  [26] .comment          PROGBITS         0000000000000000  00003018
       000000000000001b  0000000000000001  MS       0     0     1
  [27] .symtab           SYMTAB           0000000000000000  00003038
       0000000000000240  0000000000000018          28     6     8
  [28] .strtab           STRTAB           0000000000000000  00003278
       0000000000000127  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  0000339f
       0000000000000116  0000000000000000           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)

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x0000000000000310 0x0000000000000310  R      0x8
  INTERP         0x00000000000003b4 0x00000000000003b4 0x00000000000003b4
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000640 0x0000000000000640  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x000000000000016d 0x000000000000016d  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x00000000000000d4 0x00000000000000d4  R      0x1000
  LOAD           0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0
                 0x0000000000000248 0x0000000000000250  RW     0x1000
  DYNAMIC        0x0000000000002de0 0x0000000000003de0 0x0000000000003de0
                 0x00000000000001e0 0x00000000000001e0  RW     0x8
  NOTE           0x0000000000000350 0x0000000000000350 0x0000000000000350
                 0x0000000000000040 0x0000000000000040  R      0x8
  NOTE           0x0000000000000390 0x0000000000000390 0x0000000000000390
                 0x0000000000000024 0x0000000000000024  R      0x4
  NOTE           0x00000000000020b4 0x00000000000020b4 0x00000000000020b4
                 0x0000000000000020 0x0000000000000020  R      0x4
  GNU_PROPERTY   0x0000000000000350 0x0000000000000350 0x0000000000000350
                 0x0000000000000040 0x0000000000000040  R      0x8
  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                 0x0000000000000024 0x0000000000000024  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0
                 0x0000000000000230 0x0000000000000230  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .note.gnu.property .note.gnu.build-id .interp .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame .note.ABI-tag 
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id 
   09     .note.ABI-tag 
   10     .note.gnu.property 
   11     .eh_frame_hdr 
   12     
   13     .init_array .fini_array .dynamic .got 

The executable program segment has 4 sections, .init, .plt, .text, and .fini as expected, but the entry point according to the elf header aligns with the start of the .text section (and the _start symbol), effectively skipping .init. So .init will never get run, right? There is code there:

$ objdump --disassemble -j.init -M intel ./hello
[...]
0000000000001000 <_init>:
    1000:	f3 0f 1e fa          	endbr64
    1004:	48 83 ec 08          	sub    rsp,0x8
    1008:	48 8b 05 c1 2f 00 00 	mov    rax,QWORD PTR [rip+0x2fc1]        # 3fd0 <__gmon_start__@Base>
    100f:	48 85 c0             	test   rax,rax
    1012:	74 02                	je     1016 <_init+0x16>
    1014:	ff d0                	call   rax
    1016:	48 83 c4 08          	add    rsp,0x8
    101a:	c3                   	ret

Apparently something to do with gprof. The elf specification seems to say that the .init section should get run:

.init This section holds executable instructions that contribute to the process initialization code. When a program starts to run, the system executes the code in this section before calling the main program entry point (called main for C programs).

So I thought maybe it gets run before jumping to the entry point somehow? But I tried creating a simple executable that prints hello in init:

$ cat ./hello.asm
bits 64
section .init exec align=4 # match the section flags given by gcc to hello.c
	mov rax, 1
	mov rdi, 1
	mov rsi, msg_init
	mov rdx, len_init
	syscall

section .text
global _start
_start:
	mov rax, 1
	mov rdi, 1
	mov rsi, msg_text
	mov rdx, len_text
	syscall

	mov rax, 60
	mov rdi, 0
	syscall

section .rodata
msg_init: db "hello init", 10, 0
len_init: equ $-msg_init
msg_text: db "hello text", 10, 0
len_text: equ $-msg_text
$ nasm -felf64 ./hello.asm && ld ./hello.o -o ./hello
$ ./hello
hello text

No dice. How is this supposed to work? Seems like it doesn't.

Last edited by Brocellous (2025-03-29 20:47:40)

Offline

#2 2025-03-30 08:45:56

seth
Member
Registered: 2012-09-03
Posts: 63,021

Offline

#3 2025-03-30 09:42:26

ReDress
Member
From: Nairobi
Registered: 2024-11-30
Posts: 133

Re: Is the .init section actually used?

Brocellous wrote:
$ cat ./hello.asm
bits 64
section .init exec align=4 # match the section flags given by gcc to hello.c
	mov rax, 1
	mov rdi, 1
	mov rsi, msg_init
	mov rdx, len_init
	syscall

section .text
global _start
_start:
	mov rax, 1
	mov rdi, 1
	mov rsi, msg_text
	mov rdx, len_text
	syscall

	mov rax, 60
	mov rdi, 0
	syscall

section .rodata
msg_init: db "hello init", 10, 0
len_init: equ $-msg_init
msg_text: db "hello text", 10, 0
len_text: equ $-msg_text
$ nasm -felf64 ./hello.asm && ld ./hello.o -o ./hello
$ ./hello
hello text

No dice. How is this supposed to work? Seems like it doesn't.

Maybe try a much more simpler example. Like loading a certain value into a certain register then checking the value of this register in your _start section. No idea how this works but quite possibly the code in your example requires initialization, access to certain routines etc - which might not be available or are not allowed in that section.

Offline

#4 2025-03-30 10:44:23

Brocellous
Member
Registered: 2017-11-27
Posts: 152

Re: Is the .init section actually used?

seth wrote:

I am (lightly) familiar with the constructor/destructor attributes. But this source says:

https://maskray.me wrote:

The generic ABI says: If an object contains both DT_INIT and DT_INIT_ARRAY entries, the function referenced by the DT_INIT entry is processed before those referenced by the DT_INIT_ARRAY entry for that object. If an object contains both DT_FINI and DT_FINI_ARRAY entries, the functions referenced by the DT_FINI_ARRAY entry are processed before the one referenced by the DT_FINI entry for that object.

That doesn't sound to me like DT_INIT was superseded or replaced with DT_INIT_ARRAY, it sounds like they should both run. For completeness sake, both are present in the .dynamic section of the ./hello binary:

$ readelf -d ./hello

Dynamic section at offset 0x2df8 contains 24 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x401000
 0x000000000000000d (FINI)               0x401164
 0x0000000000000019 (INIT_ARRAY)         0x403de0
 0x000000000000001b (INIT_ARRAYSZ)       16 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x403df0
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x4003d0
 0x0000000000000005 (STRTAB)             0x400480
 0x0000000000000006 (SYMTAB)             0x4003f0
 0x000000000000000a (STRSZ)              126 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x403fe8
 0x0000000000000002 (PLTRELSZ)           24 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x4005a0
 0x0000000000000007 (RELA)               0x400540
 0x0000000000000008 (RELASZ)             96 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400510
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x4004fe
 0x0000000000000000 (NULL)               0x0

I don't understand the linker script well enough to add such a section to my toy assembly program though.

ReDress wrote:

No idea how this works but quite possibly the code in your example requires initialization, access to certain routines etc - which might not be available or are not allowed in that section.

I don't think that's the case here, it just directly invokes a write syscall. It doesn't really get much simpler than that.

Offline

#5 2025-03-30 12:07:59

ReDress
Member
From: Nairobi
Registered: 2024-11-30
Posts: 133

Re: Is the .init section actually used?

Brocellous wrote:

I don't think that's the case here, it just directly invokes a write syscall. It doesn't really get much simpler than that.

Though, none of the compiler generated _init sections that you posted or @seth's link posted make syscalls

Offline

Board footer

Powered by FluxBB