elf

.text section not part of segment even though size, offset and physical addresses match


I'm writing a compiler in Rust and right now trying to generate an elf. I have

My program header has p_vaddr and p_paddr set to 0x400000 and p_filesz and p_memsz set to text_len + data_len.

readelf prints:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] .text             PROGBITS         0000000000400000  00000138
       0000000000000053  0000000000000000  AX       0     0     16
  [ 1] .data             PROGBITS         0000000000400053  0000018b
       0000000000000004  0000000000000000  WA       0     0     16
  [ 2] .shstrtab         STRTAB           0000000000000000  0000018f
       0000000000000016  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),
  R (retain), D (mbind), l (large), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000138 0x0000000000400000 0x0000000000400000
                 0x0000000000000057 0x0000000000000057  R E    0x1000

But then:

 Section to Segment mapping:
  Segment Sections...
   00     .data

Why should segment 00 only contain .data? objdump also generates nothing besides:

main:     file format elf64-x86-64

This is my code:

impl Program {
    fn generate_elf(mut self) -> Vec<u8> {
        let mut vec = Vec::new();

        let bin = |slice: &[&str]| {
            slice
                .iter()
                .map(|x| x.as_bytes().to_vec())
                .flatten()
                .collect::<Vec<u8>>()
        };

        let sections = [".text\0", ".data\0", ".shstrtab\0"];
        let mut sections_bin = bin(&sections);
        let mut n = 0;
        let mut next_section = || {
            let res = bin(&sections[0..n as usize]).len() as u32;
            n += 1;
            res
        };

        let text_len = self.text.len() as u64;
        let data_len = self.data.len() as u64;

        let elf_header_size = size_of::<Elf64Header>() as u64;
        let elf_program_header_size = size_of::<Elf64ProgramHeader>() as u64;
        let elf_section_header_size = size_of::<Elf64SectionHeader>() as u64;

        let mut head = elf_header_size + elf_program_header_size + 3 * elf_section_header_size;

        let elf_header = Elf64Header {
            e_ident: [0x7F, b'E', b'L', b'F', 2, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0],
            e_type: 2,
            e_machine: 0x3E,
            e_version: 1,
            e_entry: 0x400000,
            e_phoff: elf_header_size,
            e_shoff: elf_header_size + elf_program_header_size,
            e_flags: 0,
            e_ehsize: elf_header_size as u16,
            e_phentsize: elf_program_header_size as u16,
            e_phnum: 1,
            e_shentsize: elf_section_header_size as u16,
            e_shnum: 3,
            e_shstrndx: 2,
        };
        let program_header = Elf64ProgramHeader {
            p_type: 1,
            p_flags: 1 | 4,
            p_offset: head,
            p_vaddr: 0x400000,
            p_paddr: 0x400000,
            p_filesz: text_len + data_len,
            p_memsz: text_len + data_len,
            p_align: 0x1000,
        };
        let text_section_header = Elf64SectionHeader {
            sh_name: next_section(),
            sh_type: 1,
            sh_flags: 2 | 4,
            sh_addr: 0x400000,
            sh_offset: head,
            sh_size: text_len,
            sh_link: 0,
            sh_info: 0,
            sh_addralign: 0x10,
            sh_entsize: 0,
        };
        head += text_section_header.sh_size;
        let data_section_header = Elf64SectionHeader {
            sh_name: next_section(),
            sh_type: 1,
            sh_flags: 1 | 2,
            sh_addr: 0x400000 + text_section_header.sh_size,
            sh_offset: head,
            sh_size: data_len,
            sh_link: 0,
            sh_info: 0,
            sh_addralign: 0x10,
            sh_entsize: 0,
        };
        head += data_section_header.sh_size;
        let strings_section_header = Elf64SectionHeader {
            sh_name: next_section(),
            sh_type: 3,
            sh_flags: 0,
            sh_addr: 0,
            sh_offset: head,
            sh_size: sections_bin.len() as u64,
            sh_link: 0,
            sh_info: 0,
            sh_addralign: 1,
            sh_entsize: 0,
        };

        extend(&mut vec, elf_header);
        extend(&mut vec, program_header);
        extend(&mut vec, text_section_header);
        extend(&mut vec, data_section_header);
        extend(&mut vec, strings_section_header);

        vec.append(&mut self.text);
        vec.append(&mut self.data);
        vec.append(&mut sections_bin);

        vec
    }
}

and this is the whole readelf output:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400000
  Start of program headers:          64 (bytes into file)
  Start of section headers:          120 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           64 (bytes)
  Number of section headers:         3
  Section header string table index: 2

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] .text             PROGBITS         0000000000400000  00000138
       0000000000000053  0000000000000000  AX       0     0     16
  [ 1] .data             PROGBITS         0000000000400053  0000018b
       0000000000000004  0000000000000000  WA       0     0     16
  [ 2] .shstrtab         STRTAB           0000000000000000  0000018f
       0000000000000016  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),
  R (retain), D (mbind), l (large), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000138 0x0000000000400000 0x0000000000400000
                 0x0000000000000057 0x0000000000000057  R E    0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .data

There is no dynamic section in this file.

There are no relocations in this file.
No processor specific unwind information to decode

No version information found in this file.

I also tried creating two program headers, one for .text and one for .data. The .data one showed .data as a section but the .text one did not.

Edit: Thanks to @Employed Russian's comment i changed the code to make the segments pass

const PAGE_SIZE: u64 = 0x1000;

impl Elf64ProgramHeader {
    fn assert_valid(&self) {
        assert_eq!((self.p_vaddr - self.p_offset) % PAGE_SIZE, 0);
        assert_eq!((self.p_paddr - self.p_offset) % PAGE_SIZE, 0);
    }
}

which sadly didn't fix the issue. I also tried use to different segments again, one for .text and one for .data, which also didn't change anything. I then aligned all sections to 0x10 - which changed nothing - and then even to PAGE_SIZE, still with no success.

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400000
  Start of program headers:          64 (bytes into file)
  Start of section headers:          176 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         2
  Size of section headers:           64 (bytes)
  Number of section headers:         3
  Section header string table index: 2

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] .text             PROGBITS         0000000000400000  00001000
       0000000000001000  0000000000000000  AX       0     0     4096
  [ 1] .data             PROGBITS         0000000000401000  00002000
       0000000000001000  0000000000000000  WA       0     0     4096
  [ 2] .shstrtab         STRTAB           0000000000000000  00003000
       0000000000001000  0000000000000000           0     0     4096
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),
  R (retain), D (mbind), l (large), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000001000 0x0000000000400000 0x0000000000400000
                 0x0000000000001000 0x0000000000001000  R E    0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x0000000000001000 0x0000000000001000  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00
   01

There is no dynamic section in this file.

There are no relocations in this file.
No processor specific unwind information to decode

No version information found in this file.

main:     file format elf64-x86-64

Edit 2: I had some stupid bugs in the first version, like LOAD 2 having the wrong offset. I fixed that:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0] .text             PROGBITS         0000000000400000  00001000
       0000000000001000  0000000000000000  AX       0     0     4096
  [ 1] .data             PROGBITS         0000000000401000  00002000
       0000000000001000  0000000000000000  WA       0     0     4096
  [ 2] .shstrtab         STRTAB           0000000000000000  00003000
       0000000000000016  0000000000000000           0     0     0
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),
  R (retain), D (mbind), l (large), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000001000 0x0000000000400000 0x0000000000400000
                 0x0000000000001000 0x0000000000001000  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000401000 0x0000000000401000
                 0x0000000000001000 0x0000000000001000  RW     0x1000

 Section to Segment mapping:
  Segment Sections...
   00
   01     .data

And now .data even shows up in the section to segment mapping. When trying to start the elf i still get segmentation fault, and gdb reports:

#0  0x0000000000000001 in ?? ()
>>> info files
Symbols from "/home/[name]/dev/rust/toylang/main".
Local core dump file:
        `/home/[name]/dev/rust/toylang/core', file type elf64-x86-64.
        0x0000000000400000 - 0x0000000000401000 is load1
        0x0000000000401000 - 0x0000000000402000 is load2
        0x00007f1a35b8e000 - 0x00007f1a35b92000 is load3
        0x00007f1a35b92000 - 0x00007f1a35b94000 is load4
        0x00007ffcda9ac000 - 0x00007ffcda9ce000 is load5
        0xffffffffff600000 - 0xffffffffff601000 is load6
        While running this, GDB does not access memory from...
Local exec file:
        `/home/[name]/dev/rust/toylang/main', file type elf64-x86-64.
warning: Cannot find section for the entry point of /home/[name]/dev/rust/toylang/main.
        Entry point: 0x400000
        0x0000000000401000 - 0x0000000000402000 is .data
        0x00007f1a35b92120 - 0x00007f1a35b92170 is .hash in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92170 - 0x00007f1a35b921d4 is .gnu.hash in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b921d8 - 0x00007f1a35b92340 is .dynsym in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92340 - 0x00007f1a35b923dc is .dynstr in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b923dc - 0x00007f1a35b923fa is .gnu.version in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92400 - 0x00007f1a35b92438 is .gnu.version_d in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92438 - 0x00007f1a35b92558 is .dynamic in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92560 - 0x00007f1a35b92570 is .rodata in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92570 - 0x00007f1a35b92580 is .vvar__vdso_rng_data in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92580 - 0x00007f1a35b92770 is .vvar__vdso_data in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92770 - 0x00007f1a35b927c4 is .note in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b927c4 - 0x00007f1a35b92818 is .eh_frame_hdr in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92818 - 0x00007f1a35b92978 is .eh_frame in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b92980 - 0x00007f1a35b93791 is .text in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b93791 - 0x00007f1a35b93863 is .altinstructions in system-supplied DSO at 0x7f1a35b92000
        0x00007f1a35b93863 - 0x00007f1a35b9389f is .altinstr_replacement in system-supplied DSO at 0x7f1a35b92000

but it seems like it loaded something to 0x400000:

>>> x/16x 0x400000
0x400000:       0x48f78b48      0x000001b8      0x00000000      0x01bf4800
0x400010:       0x00000000      0x48000000      0x000001ba      0x00000000
0x400020:       0xc3050f00      0x003cb848      0x00000000      0xbf480000
0x400030:       0x00000000      0x00000000      0x48c3050f      0x000041bf

which somewhat resembles my assembly:

│*       │                         ┊                         │        ┊        │
│00001000│ 48 8b f7 48 b8 01 00 00 ┊ 00 00 00 00 00 48 bf 01 │H××Hו⋄⋄┊⋄⋄⋄⋄⋄Hו│
│00001010│ 00 00 00 00 00 00 00 48 ┊ ba 01 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄H┊ו⋄⋄⋄⋄⋄⋄│
│00001020│ 00 0f 05 c3 48 b8 3c 00 ┊ 00 00 00 00 00 00 48 bf │⋄••×H×<⋄┊⋄⋄⋄⋄⋄⋄H×│
│00001030│ 00 00 00 00 00 00 00 00 ┊ 0f 05 c3 48 bf 41 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊••×H×A⋄⋄│
│00001040│ 00 00 00 00 00 50 e8 40 ┊ 00 00 00 58 50 e8 2d 00 │⋄⋄⋄⋄⋄P×@┊⋄⋄⋄XP×-⋄│
│00001050│ 00 00 58 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄X⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
│00001060│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
│*       │                         ┊                         │        ┊        │

Edit 3: The code is at https://github.com/Einfachirgendwa1/toylang/blob/c1d67f285fe82e4483a97be7fc955012fada4d54/src/elf.rs

The output of readelf -Wl is:

Elf file type is EXEC (Executable file)
Entry point 0x400000
There are 2 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x001000 0x0000000000400000 0x0000000000400000 0x001000 0x001000 R E 0x1000
  LOAD           0x002000 0x0000000000401000 0x0000000000401000 0x001000 0x001000 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00
   01     .data

Thank you for your time!

Edit 4: I changed my code to also run on stable. Here's a standalone elf.rs which saves a simple binary to output:

use std::{fs::File, io::Write, slice};

const PAGE_SIZE: u64 = 0x1000;
const SECTION_ALIGNMENT: u64 = PAGE_SIZE;

fn main() {
    // Tries to generate an elf out of:
    // .text:
    //      b8 3c 00 00 00          mov    $0x3c,%eax
    //      bf 00 00 00 00          mov    $0x0,%edi
    //      0f 05                   syscall
    //
    // .data:
    //      "test"

    let elf = generate_elf(
        vec![
            0xb8, 0x3c, 0x0, 0x0, 0x0, 0xbf, 0x0, 0x0, 0x0, 0x0, 0x0f, 0x05,
        ],
        vec![b'T', b'E', b'S', b'T'],
    );

    File::create("output").unwrap().write_all(&elf).unwrap();
}

struct Aligned {
    required_padding: u64,
    padding: Vec<u8>,
}

fn align(number: u64, alignment: u64) -> Aligned {
    let required_padding = alignment - number % alignment;
    Aligned {
        required_padding,
        padding: vec![0; required_padding as usize],
    }
}

fn align_vector(vec: &mut Vec<u8>, alignment: u64) -> u64 {
    vec.append(&mut align(vec.len() as u64, alignment).padding);
    vec.len() as u64
}

fn growing_subslice<'a, T, A, F>(vec: &'a [T], f: F) -> impl FnMut() -> A + 'a
where
    T: 'a,
    F: Fn(&'a [T]) -> A + 'a,
{
    let mut n = 0;
    move || {
        let res = f(&vec[0..n]);
        n += 1;
        res
    }
}

pub fn extend<A: Clone, B>(vec: &mut Vec<A>, b: B) {
    unsafe {
        vec.extend_from_slice(slice::from_raw_parts(
            &b as *const B as *const A,
            size_of::<B>(),
        ))
    }
}

pub fn generate_elf(mut text: Vec<u8>, mut data: Vec<u8>) -> Vec<u8> {
    let mut p_vaddr = 0x400000;

    let mut vec = Vec::new();

    let as_vec_u8 = |slice: &[&str]| {
        slice
            .iter()
            .flat_map(|x| x.as_bytes().to_vec())
            .collect::<Vec<u8>>()
    };

    let sections = [".text\0", ".data\0", ".shstrtab\0"];
    let mut sections_bin = as_vec_u8(&sections);
    let mut next_section = growing_subslice(&sections, |slice| as_vec_u8(slice).len() as u32);

    let text_len = align_vector(&mut text, SECTION_ALIGNMENT);
    let data_len = align_vector(&mut data, SECTION_ALIGNMENT);

    let elf_header_size = size_of::<Elf64Header>() as u64;
    let elf_program_header_size = size_of::<Elf64ProgramHeader>() as u64;
    let elf_section_header_size = size_of::<Elf64SectionHeader>() as u64;

    let headers = elf_header_size + 2 * elf_program_header_size + 3 * elf_section_header_size;
    let mut header_padding = align(headers, PAGE_SIZE);
    let header_len = headers + header_padding.required_padding;

    let elf_header = Elf64Header {
        e_ident: [0x7F, b'E', b'L', b'F', 2, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0],
        e_type: 2,
        e_machine: 0x3E,
        e_version: 1,
        e_entry: p_vaddr,
        e_phoff: elf_header_size,
        e_shoff: elf_header_size + 2 * elf_program_header_size,
        e_flags: 0,
        e_ehsize: elf_header_size as u16,
        e_phentsize: elf_program_header_size as u16,
        e_phnum: 2,
        e_shentsize: elf_section_header_size as u16,
        e_shnum: 3,
        e_shstrndx: 2,
    };

    let text_program_header = Elf64ProgramHeader {
        p_type: 1,
        p_flags: 1 | 4,
        p_offset: header_len,
        p_vaddr,
        p_paddr: p_vaddr,
        p_filesz: text_len,
        p_memsz: text_len,
        p_align: PAGE_SIZE,
    };
    text_program_header.assert_valid();
    p_vaddr += text_len;

    let data_program_header = Elf64ProgramHeader {
        p_type: 1,
        p_flags: 2 | 4,
        p_offset: header_len + text_len,
        p_vaddr,
        p_paddr: p_vaddr,
        p_filesz: data_len,
        p_memsz: data_len,
        p_align: PAGE_SIZE,
    };
    data_program_header.assert_valid();

    let text_section_header = Elf64SectionHeader {
        sh_name: next_section(),
        sh_type: 1,
        sh_flags: 2 | 4,
        sh_addr: 0x400000,
        sh_offset: header_len,
        sh_size: text_len,
        sh_link: 0,
        sh_info: 0,
        sh_addralign: SECTION_ALIGNMENT,
        sh_entsize: 0,
    };
    let data_section_header = Elf64SectionHeader {
        sh_name: next_section(),
        sh_type: 1,
        sh_flags: 1 | 2,
        sh_addr: 0x400000 + text_section_header.sh_size,
        sh_offset: header_len + text_len,
        sh_size: data_len,
        sh_link: 0,
        sh_info: 0,
        sh_addralign: SECTION_ALIGNMENT,
        sh_entsize: 0,
    };
    let strings_section_header = Elf64SectionHeader {
        sh_name: next_section(),
        sh_type: 3,
        sh_flags: 0,
        sh_addr: 0,
        sh_offset: header_len + text_len + data_len,
        sh_size: sections_bin.len() as u64,
        sh_link: 0,
        sh_info: 0,
        sh_addralign: 0x0,
        sh_entsize: 0,
    };

    extend(&mut vec, elf_header);
    extend(&mut vec, text_program_header);
    extend(&mut vec, data_program_header);
    extend(&mut vec, text_section_header);
    extend(&mut vec, data_section_header);
    extend(&mut vec, strings_section_header);

    vec.append(&mut header_padding.padding);
    vec.append(&mut text);
    vec.append(&mut data);
    vec.append(&mut sections_bin);

    vec
}

#[repr(C)]
struct Elf64Header {
    e_ident: [u8; 16],
    e_type: u16,
    e_machine: u16,
    e_version: u32,
    e_entry: u64,
    e_phoff: u64,
    e_shoff: u64,
    e_flags: u32,
    e_ehsize: u16,
    e_phentsize: u16,
    e_phnum: u16,
    e_shentsize: u16,
    e_shnum: u16,
    e_shstrndx: u16,
}

#[derive(Debug)]
#[repr(C)]
struct Elf64ProgramHeader {
    p_type: u32,
    p_flags: u32,
    p_offset: u64,
    p_vaddr: u64,
    p_paddr: u64,
    p_filesz: u64,
    p_memsz: u64,
    p_align: u64,
}

impl Elf64ProgramHeader {
    fn assert_valid(&self) {
        assert_eq!((self.p_vaddr - self.p_offset) % PAGE_SIZE, 0);
        assert_eq!((self.p_paddr - self.p_offset) % PAGE_SIZE, 0);
    }
}

#[repr(C)]
struct Elf64SectionHeader {
    sh_name: u32,
    sh_type: u32,
    sh_flags: u64,
    sh_addr: u64,
    sh_offset: u64,
    sh_size: u64,
    sh_link: u32,
    sh_info: u32,
    sh_addralign: u64,
    sh_entsize: u64,
}

The binary itself works, but objdump still doesn't show the source code when using objdump -d and .text also doesn't show up in the section to segment mapping of readelf.


Solution

  • The issue is that section at index 0 is reserved and should be of SHT_NULL type.

    After applying this diff to your edit#4:

    $ diff -u elf.rs.orig elf.rs
    --- elf.rs.orig 2024-11-29 22:09:07.785140746 +0000
    +++ elf.rs      2024-11-29 22:06:47.622954606 +0000
    @@ -1,4 +1,5 @@
     use std::{fs::File, io::Write, slice};
    +use std::mem::size_of;
    
     const PAGE_SIZE: u64 = 0x1000;
     const SECTION_ALIGNMENT: u64 = PAGE_SIZE;
    @@ -75,7 +76,7 @@
                 .collect::<Vec<u8>>()
         };
    
    -    let sections = [".text\0", ".data\0", ".shstrtab\0"];
    +    let sections = ["\0", ".text\0", ".data\0", ".shstrtab\0"];
         let mut sections_bin = as_vec_u8(&sections);
         let mut next_section = growing_subslice(&sections, |slice| as_vec_u8(slice).len() as u32);
    
    @@ -86,7 +87,7 @@
         let elf_program_header_size = size_of::<Elf64ProgramHeader>() as u64;
         let elf_section_header_size = size_of::<Elf64SectionHeader>() as u64;
    
    -    let headers = elf_header_size + 2 * elf_program_header_size + 3 * elf_section_header_size;
    +    let headers = elf_header_size + 2 * elf_program_header_size + 4 * elf_section_header_size;
         let mut header_padding = align(headers, PAGE_SIZE);
         let header_len = headers + header_padding.required_padding;
    
    @@ -103,8 +104,8 @@
             e_phentsize: elf_program_header_size as u16,
             e_phnum: 2,
             e_shentsize: elf_section_header_size as u16,
    -        e_shnum: 3,
    -        e_shstrndx: 2,
    +        e_shnum: 4,
    +        e_shstrndx: 3,
         };
    
         let text_program_header = Elf64ProgramHeader {
    @@ -132,6 +133,18 @@
         };
         data_program_header.assert_valid();
    
    +    let null_section_header = Elf64SectionHeader {
    +        sh_name: next_section(),
    +        sh_type: 0,
    +        sh_flags: 0,
    +        sh_addr: 0,
    +        sh_offset: 0,
    +        sh_size: 0,
    +        sh_link: 0,
    +        sh_info: 0,
    +        sh_addralign: 0,
    +        sh_entsize: 0,
    +    };
         let text_section_header = Elf64SectionHeader {
             sh_name: next_section(),
             sh_type: 1,
    @@ -172,6 +185,8 @@
         extend(&mut vec, elf_header);
         extend(&mut vec, text_program_header);
         extend(&mut vec, data_program_header);
    +
    +    extend(&mut vec, null_section_header);
         extend(&mut vec, text_section_header);
         extend(&mut vec, data_section_header);
         extend(&mut vec, strings_section_header);
    

    Using rustc elf.rs && ./elf && readelf -WSl output:

    There are 4 section headers, starting at offset 0xb0:
    
    Section Headers:
      [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
      [ 1] .text             PROGBITS        0000000000400000 001000 001000 00  AX  0   0 4096
      [ 2] .data             PROGBITS        0000000000401000 002000 001000 00  WA  0   0 4096
      [ 3] .shstrtab         STRTAB          0000000000000000 003000 000017 00      0   0  0
    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),
      R (retain), D (mbind), l (large), p (processor specific)
    
    Elf file type is EXEC (Executable file)
    Entry point 0x400000
    There are 2 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
      LOAD           0x001000 0x0000000000400000 0x0000000000400000 0x001000 0x001000 R E 0x1000
      LOAD           0x002000 0x0000000000401000 0x0000000000401000 0x001000 0x001000 RW  0x1000
    
     Section to Segment mapping:
      Segment Sections...
       00     .text
       01     .data
    
    $ objdump -d output
    
    output:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000400000 <.text>:
      400000:       b8 3c 00 00 00          mov    $0x3c,%eax
      400005:       bf 00 00 00 00          mov    $0x0,%edi
      40000a:       0f 05                   syscall
            ...