// SPDX-License-Identifier: LGPL-2.1-or-later
// Write an ELF64 relocatable object file (.o) from an ObjFile.
// Targets: Linux x86-64 and AArch64.
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include "obj.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

// ---------------------------------------------------------------------------
// ELF64 constants and structures
// ---------------------------------------------------------------------------
#define EI_NIDENT    16
#define ELFCLASS64    2
#define ELFDATA2LSB   1
#define EV_CURRENT    1
#define ELFOSABI_NONE 0
#define ET_REL        1
#define EM_X86_64    62
#define EM_AARCH64  183

#define SHT_NULL        0
#define SHT_PROGBITS    1
#define SHT_SYMTAB      2
#define SHT_STRTAB      3
#define SHT_RELA        4
#define SHT_NOBITS      8
#define SHT_INIT_ARRAY 14
#define SHT_FINI_ARRAY 15

// SHF_WRITE/ALLOC/EXECINSTR/MERGE/INFO_LINK/TLS come from obj.h — shared
// with asm.c, which needs them to parse GAS .pushsection flag strings.

#define STB_LOCAL  0
#define STB_GLOBAL 1
#define STB_WEAK   2
#define STT_NOTYPE  0
#define STT_OBJECT  1
#define STT_FUNC    2
#define STT_SECTION 3
#define STT_TLS     6
#define STT_IFUNC   10
#define STV_DEFAULT 0
#define SHN_UNDEF   0

#define ELF64_ST_INFO(b, t) (uint8_t)(((b)<<4)|((t)&0xf))
#define ELF64_R_INFO(s, t)  (((uint64_t)(s)<<32)|((uint64_t)(t)&0xffffffffULL))

// ---------------------------------------------------------------------------
// String table builder
// ---------------------------------------------------------------------------
typedef struct {
    char *data;
    size_t len, cap;
} Strtab;

static void strtab_init(Strtab *t) {
    t->cap = 256;
    t->data = malloc(t->cap);
    t->len = 0;
    t->data[t->len++] = '\0';
}

static uint32_t strtab_add(Strtab *t, const char *s) {
    size_t n = strlen(s) + 1;
    if (t->len + n > t->cap) {
        while (t->cap < t->len + n) t->cap *= 2;
        t->data = realloc(t->data, t->cap);
    }
    uint32_t idx = (uint32_t)t->len;
    memcpy(t->data + t->len, s, n);
    t->len += n;
    return idx;
}

// ---------------------------------------------------------------------------
// Low-level write helpers (always little-endian)
// ---------------------------------------------------------------------------
static void w8(FILE *f, uint8_t v) { fputc(v, f); }
static void w16(FILE *f, uint16_t v) {
    w8(f, (uint8_t)v);
    w8(f, v >> 8);
}
static void w32(FILE *f, uint32_t v) {
    w16(f, (uint16_t)v);
    w16(f, (uint16_t)(v >> 16));
}
static void w64(FILE *f, uint64_t v) {
    w32(f, (uint32_t)v);
    w32(f, (uint32_t)(v >> 32));
}
static void wi64(FILE *f, int64_t v) { w64(f, (uint64_t)v); }
static void wbuf(FILE *f, const void *buf, size_t n) { fwrite(buf, 1, n, f); }
static void wzeros(FILE *f, uint64_t n) {
    uint8_t z[64];
    memset(z, 0, sizeof(z));
    while (n >= 64) {
        fwrite(z, 1, 64, f);
        n -= 64;
    }
    if (n) fwrite(z, 1, (size_t)n, f);
}

static void write_ehdr(FILE *f, uint8_t em,
                       uint64_t shoff, uint16_t shnum, uint16_t shstrndx) {
    uint8_t ident[EI_NIDENT] = {
        0x7f, 'E', 'L', 'F', ELFCLASS64, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE,
        0, 0, 0, 0, 0, 0, 0, 0};
    wbuf(f, ident, EI_NIDENT);
    w16(f, ET_REL);
    w16(f, em);
    w32(f, EV_CURRENT);
    w64(f, 0); // e_entry
    w64(f, 0); // e_phoff
    w64(f, shoff);
    w32(f, 0); // e_flags
    w16(f, 64); // e_ehsize
    w16(f, 0); // e_phentsize
    w16(f, 0); // e_phnum
    w16(f, 64); // e_shentsize
    w16(f, shnum);
    w16(f, shstrndx);
}

static void write_shdr(FILE *f, uint32_t name, uint32_t type, uint64_t flags,
                       uint64_t offset, uint64_t size, uint32_t link,
                       uint32_t info, uint64_t align, uint64_t entsize) {
    w32(f, name);
    w32(f, type);
    w64(f, flags);
    w64(f, 0); // sh_addr=0
    w64(f, offset);
    w64(f, size);
    w32(f, link);
    w32(f, info);
    w64(f, align);
    w64(f, entsize);
}

static void write_sym(FILE *f, uint32_t name, uint8_t info, uint8_t other,
                      uint16_t shndx, uint64_t value, uint64_t size) {
    w32(f, name);
    w8(f, info);
    w8(f, other);
    w16(f, shndx);
    w64(f, value);
    w64(f, size);
}

static uint64_t align16(uint64_t x) { return (x + 15) & ~(uint64_t)15; }

// Align `x` up to `n` (n must be a power of two, matching ELF's
// sh_addralign semantics -- 0/1 both mean "no constraint").
static uint64_t align_n(uint64_t x, uint64_t n) {
    return n > 1 ? (x + n - 1) & ~(n - 1) : x;
}

// ---------------------------------------------------------------------------
// ELF symbol array builder (locals first, then globals/weaks)
// ---------------------------------------------------------------------------
typedef struct {
    uint32_t name;
    uint8_t info, other;
    uint16_t shndx;
    uint64_t value, size;
} ESym;

typedef struct {
    ESym *data;
    int len, cap;
} ESymArr;

static void esym_push(ESymArr *a, ESym s) {
    if (a->len == a->cap) {
        a->cap = a->cap ? a->cap * 2 : 16;
        a->data = realloc(a->data, (size_t)a->cap * sizeof(ESym));
    }
    a->data[a->len++] = s;
}

// ---------------------------------------------------------------------------
// Main ELF writer
// ---------------------------------------------------------------------------
int elf_write(ObjFile *obj, const char *path) {
#ifdef __aarch64__
    uint8_t em = EM_AARCH64;
#else
    uint8_t em = EM_X86_64;
#endif

    Strtab symstrtab, shstrtab;
    strtab_init(&symstrtab);
    strtab_init(&shstrtab);
    bool has_tdata = obj->data_tls.len > 0;
    bool has_debug = objfile_has_debug(obj);
    bool has_init_array = obj->init_array.len > 0;
    bool has_fini_array = obj->fini_array.len > 0;
    bool has_rela_text = obj->text_reloc_count > 0;
    bool has_rela_data = obj->data_reloc_count > 0;
    bool has_rela_rodata = obj->rodata_reloc_count > 0;
    bool has_rela_init = obj->init_array_reloc_count > 0;
    bool has_rela_fini = obj->fini_array_reloc_count > 0;
    bool has_rela_debug_info = has_debug && obj->debug_has_low_pc;
    bool has_rela_debug_aranges = has_debug && obj->debug_has_aranges_addr;
    bool has_rela_debug_line = has_debug && obj->debug_has_line_addr;

    // -----------------------------------------------------------------------
    // Dynamically-registered sections (GAS .section/.pushsection with a name
    // outside the built-in set, e.g. the kernel's __ex_table). Each gets one
    // PROGBITS section header, plus a .rela.<name> header if it has relocs.
    // -----------------------------------------------------------------------
    int nextra = obj->extra_sec_count;
    uint32_t *shn_extra = nextra ? calloc((size_t)nextra, sizeof(uint32_t)) : NULL;
    uint32_t *shn_extra_rela = nextra ? calloc((size_t)nextra, sizeof(uint32_t)) : NULL;
    int *sh_extra_idx = nextra ? calloc((size_t)nextra, sizeof(int)) : NULL;
    int *sh_extra_rela_idx = nextra ? calloc((size_t)nextra, sizeof(int)) : NULL;
    bool *extra_has_relocs = nextra ? calloc((size_t)nextra, sizeof(bool)) : NULL;
    uint64_t *extra_off = nextra ? calloc((size_t)nextra, sizeof(uint64_t)) : NULL;
    uint64_t *extra_size = nextra ? calloc((size_t)nextra, sizeof(uint64_t)) : NULL;
    uint64_t *extra_rela_off = nextra ? calloc((size_t)nextra, sizeof(uint64_t)) : NULL;
    uint64_t *extra_rela_size = nextra ? calloc((size_t)nextra, sizeof(uint64_t)) : NULL;
    for (int i = 0; i < nextra; i++)
        extra_has_relocs[i] = obj->extra_secs[i].reloc_count > 0;

    // Section name strings
    uint32_t shn_empty = strtab_add(&shstrtab, "");
    uint32_t shn_text = strtab_add(&shstrtab, ".text");
    uint32_t shn_data = strtab_add(&shstrtab, ".data");
    uint32_t shn_bss = strtab_add(&shstrtab, ".bss");
    uint32_t shn_rodata = strtab_add(&shstrtab, ".rodata");
    uint32_t shn_tdata = strtab_add(&shstrtab, ".tdata");
    uint32_t shn_note = strtab_add(&shstrtab, ".note.GNU-stack");
    uint32_t shn_debug_line = strtab_add(&shstrtab, ".debug_line");
    uint32_t shn_rela_debug_line = strtab_add(&shstrtab, ".rela.debug_line");
    uint32_t shn_debug_info = strtab_add(&shstrtab, ".debug_info");
    uint32_t shn_debug_abbrev = strtab_add(&shstrtab, ".debug_abbrev");
    uint32_t shn_debug_aranges = strtab_add(&shstrtab, ".debug_aranges");
    uint32_t shn_init_array = strtab_add(&shstrtab, ".init_array");
    uint32_t shn_fini_array = strtab_add(&shstrtab, ".fini_array");
    uint32_t shn_rela_txt = strtab_add(&shstrtab, ".rela.text");
    uint32_t shn_rela_dat = strtab_add(&shstrtab, ".rela.data");
    uint32_t shn_rela_rod = strtab_add(&shstrtab, ".rela.rodata");
    uint32_t shn_rela_init = strtab_add(&shstrtab, ".rela.init_array");
    uint32_t shn_rela_fini = strtab_add(&shstrtab, ".rela.fini_array");
    uint32_t shn_rela_debug_info = strtab_add(&shstrtab, ".rela.debug_info");
    uint32_t shn_rela_debug_aranges = strtab_add(&shstrtab, ".rela.debug_aranges");
    uint32_t shn_symtab = strtab_add(&shstrtab, ".symtab");
    uint32_t shn_strtab = strtab_add(&shstrtab, ".strtab");
    uint32_t shn_shstrtab = strtab_add(&shstrtab, ".shstrtab");
    (void)shn_empty;
    for (int i = 0; i < nextra; i++) {
        shn_extra[i] = strtab_add(&shstrtab, obj->extra_secs[i].name);
        if (extra_has_relocs[i]) {
            char relname[300];
            snprintf(relname, sizeof(relname), ".rela%s", obj->extra_secs[i].name);
            shn_extra_rela[i] = strtab_add(&shstrtab, relname);
        }
    }

    // -----------------------------------------------------------------------
    // Section layout (computed before the symbol table so extra sections'
    // header indices are known when building each STT_SECTION symbol, and
    // when mapping user symbols defined in an extra section to their shndx):
    // 0=NULL, 1=.text, 2=.data, 3=.bss, 4=.rodata, 5=.tdata(if tls),
    // 6=.note.GNU-stack (5 if no tls)
    // then optional .debug_line, .debug_info, .debug_abbrev, .debug_aranges
    // then optional .init_array, .fini_array
    // then any dynamically-registered sections (PROGBITS)
    // then optional .rela.text, .rela.data, .rela.rodata
    // then any dynamically-registered sections' .rela.<name>
    // then optional .rela.debug_info, .rela.debug_aranges, .rela.debug_line
    // then optional .rela.init_array, .rela.fini_array
    // then .symtab, .strtab, .shstrtab
    // -----------------------------------------------------------------------
    int shidx = has_tdata ? 7 : 6;
    int sh_debug_line_idx = has_debug ? shidx++ : -1;
    int sh_debug_info_idx = has_debug ? shidx++ : -1;
    int sh_debug_abbrev_idx = has_debug ? shidx++ : -1;
    int sh_debug_aranges_idx = has_debug ? shidx++ : -1;
    int sh_init_array_idx = has_init_array ? shidx++ : -1;
    int sh_fini_array_idx = has_fini_array ? shidx++ : -1;
    for (int i = 0; i < nextra; i++) sh_extra_idx[i] = shidx++;
    int sh_rela_text_idx = has_rela_text ? shidx++ : -1;
    int sh_rela_data_idx = has_rela_data ? shidx++ : -1;
    int sh_rela_rodata_idx = has_rela_rodata ? shidx++ : -1;
    for (int i = 0; i < nextra; i++)
        sh_extra_rela_idx[i] = extra_has_relocs[i] ? shidx++ : -1;
    int sh_rela_debug_info_idx = has_rela_debug_info ? shidx++ : -1;
    int sh_rela_debug_aranges_idx = has_rela_debug_aranges ? shidx++ : -1;
    int sh_rela_debug_line_idx = has_rela_debug_line ? shidx++ : -1;
    __attribute__((unused)) int sh_rela_init_idx = has_rela_init ? shidx++ : -1;
    __attribute__((unused)) int sh_rela_fini_idx = has_rela_fini ? shidx++ : -1;
    int sh_symtab_idx = shidx++;
    int sh_strtab_idx = shidx++;
    int sh_shstrtab_idx = shidx++;
    int nshdr = shidx;
    (void)sh_rela_text_idx;
    (void)sh_rela_data_idx;
    (void)sh_rela_rodata_idx;
    (void)sh_debug_line_idx;
    (void)sh_debug_aranges_idx;
    (void)sh_debug_info_idx;
    (void)sh_debug_abbrev_idx;
    (void)sh_rela_debug_info_idx;
    (void)sh_rela_debug_aranges_idx;
    (void)sh_rela_debug_line_idx;

    // -----------------------------------------------------------------------
    // Build ELF symbol table (locals first, then globals)
    // -----------------------------------------------------------------------
    int *sym_map = calloc((size_t)(obj->sym_count + 1), sizeof(int));
    ESymArr ea = {NULL, 0, 0};

    // Index 0: null symbol
    ESym null_sym = {0};
    esym_push(&ea, null_sym);

    // Section symbols
    ESym ssym = {0};
    ssym.info = ELF64_ST_INFO(STB_LOCAL, STT_SECTION);
    ssym.shndx = 1;
    esym_push(&ea, ssym); // .text
    ssym.shndx = 2;
    esym_push(&ea, ssym); // .data
    ssym.shndx = 3;
    esym_push(&ea, ssym); // .bss
    ssym.shndx = 4;
    esym_push(&ea, ssym); // .rodata
    if (has_tdata) {
        ssym.shndx = 5;
        esym_push(&ea, ssym); // .tdata
    }
    ssym.shndx = has_tdata ? 6 : 5;
    esym_push(&ea, ssym); // .note.GNU-stack
    for (int i = 0; i < nextra; i++) {
        ssym.shndx = (uint16_t)sh_extra_idx[i];
        esym_push(&ea, ssym);
    }

    // A user symbol's section header index: built-in fixed slots, a
    // dynamically-registered section's assigned index, or SHN_UNDEF.
#define USER_SYM_SHNDX(os)                                                 \
    ((os)->section == SEC_TEXT       ? 1                                   \
        : (os)->section == SEC_DATA   ? 2                                  \
        : (os)->section == SEC_BSS    ? 3                                  \
        : (os)->section == SEC_RODATA ? 4                                  \
        : (os)->section == SEC_TDATA  ? (has_tdata ? 5 : SHN_UNDEF)         \
        : ((os)->section >= SEC_NUM && (os)->section - SEC_NUM < nextra)   \
            ? (uint16_t)sh_extra_idx[(os)->section - SEC_NUM]              \
            : SHN_UNDEF)

    // Local user symbols
    for (int i = 0; i < obj->sym_count; i++) {
        ObjSym *os = &obj->syms[i];
        if (os->bind != SB_LOCAL) continue;
        sym_map[i] = ea.len;
        ESym es = {0};
        es.name = strtab_add(&symstrtab, os->name);
        es.info = ELF64_ST_INFO(STB_LOCAL,
                                os->type == ST_FUNC ? STT_FUNC : os->type == ST_OBJECT ? STT_OBJECT
                                    : os->type == ST_TLS                               ? STT_TLS
                                    : os->type == ST_GNU_IFUNC                         ? STT_IFUNC
                                                                                       : STT_NOTYPE);
        es.other = os->visibility;
        es.shndx = USER_SYM_SHNDX(os);
        es.value = os->offset;
        es.size = os->size;
        esym_push(&ea, es);
    }
    int first_global = ea.len;

    // Global/weak user symbols
    for (int i = 0; i < obj->sym_count; i++) {
        ObjSym *os = &obj->syms[i];
        if (os->bind == SB_LOCAL) continue;
        sym_map[i] = ea.len;
        ESym es = {0};
        es.name = strtab_add(&symstrtab, os->name);
        uint8_t bind = os->bind == SB_WEAK ? STB_WEAK : STB_GLOBAL;
        es.info = ELF64_ST_INFO(bind,
                                os->type == ST_FUNC ? STT_FUNC : os->type == ST_OBJECT ? STT_OBJECT
                                    : os->type == ST_TLS                               ? STT_TLS
                                    : os->type == ST_GNU_IFUNC                         ? STT_IFUNC
                                                                                       : STT_NOTYPE);
        es.other = os->visibility;
        es.shndx = USER_SYM_SHNDX(os);
        es.value = os->offset;
        es.size = os->size;
        esym_push(&ea, es);
    }
#undef USER_SYM_SHNDX

    // -----------------------------------------------------------------------
    // File offsets (data section bytes, then relocation tables, then the
    // symbol/string/section-name tables). Mirrors the header-index layout
    // above: extra-section data lands after .init_array/.fini_array, extra-
    // section relocs land after .rela.rodata.
    // -----------------------------------------------------------------------
    uint64_t text_off = align16(64); // 64 = sizeof ELF header
    uint64_t text_size = obj->text.len;
    uint64_t data_off = align16(text_off + text_size);
    uint64_t data_size = obj->data.len;
    uint64_t rodata_off = align16(data_off + data_size);
    uint64_t rodata_size = obj->rodata.len;
    uint64_t tdata_off = align16(rodata_off + rodata_size);
    uint64_t tdata_size = (uint64_t)obj->data_tls.len;
    uint64_t note_off = align16(has_tdata ? tdata_off + tdata_size : rodata_off + rodata_size);

    uint64_t debug_line_off = align16(note_off);
    uint64_t debug_line_size = has_debug ? obj->debug_line_section.len : 0;
    uint64_t debug_info_off = align16(debug_line_off + debug_line_size);
    uint64_t debug_info_size = has_debug ? obj->debug_info_section.len : 0;
    uint64_t debug_abbrev_off = align16(debug_info_off + debug_info_size);
    uint64_t debug_abbrev_size = has_debug ? obj->debug_abbrev_section.len : 0;
    uint64_t debug_aranges_off = align16(debug_abbrev_off + debug_abbrev_size);
    uint64_t debug_aranges_size = has_debug ? obj->debug_aranges_section.len : 0;

    uint64_t init_arr_off = align16(debug_aranges_off + debug_aranges_size);
    uint64_t init_arr_size = obj->init_array.len;
    uint64_t fini_arr_off = align16(init_arr_off + init_arr_size);
    uint64_t fini_arr_size = obj->fini_array.len;

    uint64_t running = fini_arr_off + fini_arr_size;
    for (int i = 0; i < nextra; i++) {
        // The section's own load-address alignment requirement (e.g. 8
        // for a section() global holding pointers/size_t) can exceed the
        // generic 16-byte file-layout padding every other section here
        // uses -- align up to whichever is larger so extra_off[i] itself
        // (which becomes this section's virtual address once relocated
        // against the image base) actually satisfies it.
        uint64_t a = obj->extra_secs[i].align > 16 ? obj->extra_secs[i].align : 16;
        extra_off[i] = align_n(running, a);
        extra_size[i] = obj->extra_secs[i].buf.len;
        running = extra_off[i] + extra_size[i];
    }

    uint64_t rela_txt_off = align16(running);
    uint64_t rela_txt_size = (uint64_t)obj->text_reloc_count * 24;
    uint64_t rela_dat_off = align16(rela_txt_off + rela_txt_size);
    uint64_t rela_dat_size = (uint64_t)obj->data_reloc_count * 24;
    uint64_t rela_rod_off = align16(rela_dat_off + rela_dat_size);
    uint64_t rela_rod_size = (uint64_t)obj->rodata_reloc_count * 24;

    running = rela_rod_off + rela_rod_size;
    for (int i = 0; i < nextra; i++) {
        if (!extra_has_relocs[i]) continue;
        extra_rela_off[i] = align16(running);
        extra_rela_size[i] = (uint64_t)obj->extra_secs[i].reloc_count * 24;
        running = extra_rela_off[i] + extra_rela_size[i];
    }

    uint64_t rela_ini_off = align16(running);
    uint64_t rela_ini_size = (uint64_t)obj->init_array_reloc_count * 24;
    uint64_t rela_fin_off = align16(rela_ini_off + rela_ini_size);
    uint64_t rela_fin_size = (uint64_t)obj->fini_array_reloc_count * 24;
    uint64_t rela_debug_line_off = align16(rela_fin_off + rela_fin_size);
    uint64_t rela_debug_line_size = has_rela_debug_line ? 24 : 0;
    uint64_t rela_debug_info_off = align16(rela_debug_line_off + rela_debug_line_size);
    uint64_t rela_debug_info_size = has_rela_debug_info ? 24 : 0;
    uint64_t rela_debug_aranges_off = align16(rela_debug_info_off + rela_debug_info_size);
    uint64_t rela_debug_aranges_size = has_rela_debug_aranges ? 24 : 0;

    uint64_t symtab_off = align16(rela_debug_aranges_off + rela_debug_aranges_size);
    uint64_t symtab_size = (uint64_t)ea.len * 24;
    uint64_t strtab_off = align16(symtab_off + symtab_size);
    uint64_t strtab_size = symstrtab.len;
    uint64_t shstrtab_off = align16(strtab_off + strtab_size);
    uint64_t shstrtab_size = shstrtab.len;
    uint64_t shoff = align16(shstrtab_off + shstrtab_size);

    // -----------------------------------------------------------------------
    // Write file
    // -----------------------------------------------------------------------
    mkdir_p(path);
    FILE *f = fopen(path, "wb");
    if (!f) {
        free(sym_map);
        free(ea.data);
        free(symstrtab.data);
        free(shstrtab.data);
        free(shn_extra);
        free(shn_extra_rela);
        free(sh_extra_idx);
        free(sh_extra_rela_idx);
        free(extra_has_relocs);
        free(extra_off);
        free(extra_size);
        free(extra_rela_off);
        free(extra_rela_size);
        return -1;
    }

    write_ehdr(f, em, shoff, (uint16_t)nshdr, (uint16_t)sh_shstrtab_idx);

    wzeros(f, text_off - 64);
    if (text_size) wbuf(f, obj->text.data, text_size);
    wzeros(f, data_off - (text_off + text_size));
    if (data_size) wbuf(f, obj->data.data, data_size);
    wzeros(f, rodata_off - (data_off + data_size));
    if (rodata_size) wbuf(f, obj->rodata.data, rodata_size);
    if (tdata_size) {
        wzeros(f, tdata_off - (rodata_off + rodata_size));
        wbuf(f, obj->data_tls.data, tdata_size);
    }
    wzeros(f, note_off - (has_tdata ? tdata_off + tdata_size : rodata_off + rodata_size));
    if (has_debug) {
        wzeros(f, debug_line_off - note_off);
        if (debug_line_size) wbuf(f, obj->debug_line_section.data, debug_line_size);
        wzeros(f, debug_info_off - (debug_line_off + debug_line_size));
        if (debug_info_size) wbuf(f, obj->debug_info_section.data, debug_info_size);
        wzeros(f, debug_abbrev_off - (debug_info_off + debug_info_size));
        if (debug_abbrev_size) wbuf(f, obj->debug_abbrev_section.data, debug_abbrev_size);
        wzeros(f, debug_aranges_off - (debug_abbrev_off + debug_abbrev_size));
        if (debug_aranges_size) wbuf(f, obj->debug_aranges_section.data, debug_aranges_size);
        wzeros(f, init_arr_off - (debug_aranges_off + debug_aranges_size));
    }
    if (init_arr_size) wbuf(f, obj->init_array.data, init_arr_size);
    wzeros(f, fini_arr_off - (init_arr_off + init_arr_size));
    if (fini_arr_size) wbuf(f, obj->fini_array.data, fini_arr_size);

    uint64_t written_to = fini_arr_off + fini_arr_size;
    for (int i = 0; i < nextra; i++) {
        wzeros(f, extra_off[i] - written_to);
        if (extra_size[i]) wbuf(f, obj->extra_secs[i].buf.data, extra_size[i]);
        written_to = extra_off[i] + extra_size[i];
    }
    wzeros(f, rela_txt_off - written_to);

    for (int i = 0; i < obj->text_reloc_count; i++) {
        ObjReloc *r = &obj->text_relocs[i];
        int es = r->sym_idx >= 0 ? sym_map[r->sym_idx] : 0;
        w64(f, r->offset);
        w64(f, ELF64_R_INFO((uint32_t)es, r->type));
        wi64(f, r->addend);
    }
    wzeros(f, rela_dat_off - (rela_txt_off + rela_txt_size));

    for (int i = 0; i < obj->data_reloc_count; i++) {
        ObjReloc *r = &obj->data_relocs[i];
        int es = r->sym_idx >= 0 ? sym_map[r->sym_idx] : 0;
        w64(f, r->offset);
        w64(f, ELF64_R_INFO((uint32_t)es, r->type));
        wi64(f, r->addend);
    }
    wzeros(f, rela_rod_off - (rela_dat_off + rela_dat_size));

    for (int i = 0; i < obj->rodata_reloc_count; i++) {
        ObjReloc *r = &obj->rodata_relocs[i];
        int es = r->sym_idx >= 0 ? sym_map[r->sym_idx] : 0;
        w64(f, r->offset);
        w64(f, ELF64_R_INFO((uint32_t)es, r->type));
        wi64(f, r->addend);
    }

    written_to = rela_rod_off + rela_rod_size;
    for (int i = 0; i < nextra; i++) {
        if (!extra_has_relocs[i]) continue;
        wzeros(f, extra_rela_off[i] - written_to);
        for (int j = 0; j < obj->extra_secs[i].reloc_count; j++) {
            ObjReloc *r = &obj->extra_secs[i].relocs[j];
            int es = r->sym_idx >= 0 ? sym_map[r->sym_idx] : 0;
            w64(f, r->offset);
            w64(f, ELF64_R_INFO((uint32_t)es, r->type));
            wi64(f, r->addend);
        }
        written_to = extra_rela_off[i] + extra_rela_size[i];
    }
    wzeros(f, rela_ini_off - written_to);

    for (int i = 0; i < obj->init_array_reloc_count; i++) {
        ObjReloc *r = &obj->init_array_relocs[i];
        int es = r->sym_idx >= 0 ? sym_map[r->sym_idx] : 0;
        w64(f, r->offset);
        w64(f, ELF64_R_INFO((uint32_t)es, r->type));
        wi64(f, r->addend);
    }
    wzeros(f, rela_fin_off - (rela_ini_off + rela_ini_size));

    for (int i = 0; i < obj->fini_array_reloc_count; i++) {
        ObjReloc *r = &obj->fini_array_relocs[i];
        int es = r->sym_idx >= 0 ? sym_map[r->sym_idx] : 0;
        w64(f, r->offset);
        w64(f, ELF64_R_INFO((uint32_t)es, r->type));
        wi64(f, r->addend);
    }
    wzeros(f, rela_debug_line_off - (rela_fin_off + rela_fin_size));

    if (has_rela_debug_line) {
#ifdef __aarch64__
        uint32_t r_type = R_AARCH64_ABS64;
#else
        uint32_t r_type = R_X86_64_64;
#endif
        w64(f, obj->debug_line_addr_off);
        w64(f, ELF64_R_INFO(1, r_type));
        wi64(f, 0);
    }
    wzeros(f, rela_debug_info_off - (rela_debug_line_off + rela_debug_line_size));

    if (has_rela_debug_info) {
#ifdef __aarch64__
        uint32_t r_type = R_AARCH64_ABS64;
#else
        uint32_t r_type = R_X86_64_64;
#endif
        w64(f, obj->debug_low_pc_offset);
        w64(f, ELF64_R_INFO(1, r_type));
        wi64(f, 0);
    }
    wzeros(f, rela_debug_aranges_off - (rela_debug_info_off + rela_debug_info_size));

    if (has_rela_debug_aranges) {
#ifdef __aarch64__
        uint32_t r_type = R_AARCH64_ABS64;
#else
        uint32_t r_type = R_X86_64_64;
#endif
        w64(f, obj->debug_aranges_addr_off);
        w64(f, ELF64_R_INFO(1, r_type));
        wi64(f, 0);
    }
    wzeros(f, symtab_off - (rela_debug_aranges_off + rela_debug_aranges_size));

    for (int i = 0; i < ea.len; i++)
        write_sym(f, ea.data[i].name, ea.data[i].info, ea.data[i].other,
                  ea.data[i].shndx, ea.data[i].value, ea.data[i].size);
    wzeros(f, strtab_off - (symtab_off + symtab_size));

    wbuf(f, symstrtab.data, strtab_size);
    wzeros(f, shstrtab_off - (strtab_off + strtab_size));

    wbuf(f, shstrtab.data, shstrtab_size);
    wzeros(f, shoff - (shstrtab_off + shstrtab_size));

    // Section headers
    write_shdr(f, 0, SHT_NULL, 0, 0, 0, 0, 0, 0, 0);

    write_shdr(f, shn_text, SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR,
               text_off, text_size, 0, 0, 16, 0);
    write_shdr(f, shn_data, SHT_PROGBITS, SHF_ALLOC | SHF_WRITE,
               data_off, data_size, 0, 0, obj->data.align > 8 ? obj->data.align : 8, 0);
    write_shdr(f, shn_bss, SHT_NOBITS, SHF_ALLOC | SHF_WRITE,
               data_off + data_size, obj->bss_size, 0, 0, 8, 0);
    write_shdr(f, shn_rodata, SHT_PROGBITS, SHF_ALLOC,
               rodata_off, rodata_size, 0, 0, obj->rodata.align, 0);
    if (has_tdata)
        write_shdr(f, shn_tdata, SHT_PROGBITS, SHF_TLS | SHF_WRITE | SHF_ALLOC, tdata_off, tdata_size, 0, 0, obj->data_tls.align, 0);
    write_shdr(f, shn_note, SHT_PROGBITS, obj->uses_trampoline ? SHF_EXECINSTR : 0, note_off, 0, 0, 0, 1, 0);

    if (has_debug) {
        write_shdr(f, shn_debug_line, SHT_PROGBITS, 0,
                   debug_line_off, debug_line_size, 0, 0, 1, 0);
        write_shdr(f, shn_debug_info, SHT_PROGBITS, 0,
                   debug_info_off, debug_info_size, 0, 0, 1, 0);
        write_shdr(f, shn_debug_abbrev, SHT_PROGBITS, 0,
                   debug_abbrev_off, debug_abbrev_size, 0, 0, 1, 0);
        write_shdr(f, shn_debug_aranges, SHT_PROGBITS, 0,
                   debug_aranges_off, debug_aranges_size, 0, 0, 1, 0);
    }

    if (has_init_array)
        write_shdr(f, shn_init_array, SHT_INIT_ARRAY, SHF_ALLOC | SHF_WRITE,
                   init_arr_off, init_arr_size, 0, 0, 8, 0);
    if (has_fini_array)
        write_shdr(f, shn_fini_array, SHT_FINI_ARRAY, SHF_ALLOC | SHF_WRITE,
                   fini_arr_off, fini_arr_size, 0, 0, 8, 0);

    for (int i = 0; i < nextra; i++)
        write_shdr(f, shn_extra[i], SHT_PROGBITS, obj->extra_secs[i].sh_flags,
                   extra_off[i], extra_size[i], 0, 0, obj->extra_secs[i].align,
                   obj->extra_secs[i].sh_entsize);

    if (has_rela_text)
        write_shdr(f, shn_rela_txt, SHT_RELA, SHF_INFO_LINK,
                   rela_txt_off, rela_txt_size,
                   (uint32_t)sh_symtab_idx, 1 /* .text */, 8, 24);
    if (has_rela_data)
        write_shdr(f, shn_rela_dat, SHT_RELA, SHF_INFO_LINK,
                   rela_dat_off, rela_dat_size,
                   (uint32_t)sh_symtab_idx, 2 /* .data */, 8, 24);
    if (has_rela_rodata)
        write_shdr(f, shn_rela_rod, SHT_RELA, SHF_INFO_LINK,
                   rela_rod_off, rela_rod_size,
                   (uint32_t)sh_symtab_idx, 4 /* .rodata */, 8, 24);

    for (int i = 0; i < nextra; i++)
        if (extra_has_relocs[i])
            write_shdr(f, shn_extra_rela[i], SHT_RELA, SHF_INFO_LINK,
                       extra_rela_off[i], extra_rela_size[i],
                       (uint32_t)sh_symtab_idx, (uint32_t)sh_extra_idx[i], 8, 24);

    if (has_rela_init)
        write_shdr(f, shn_rela_init, SHT_RELA, SHF_INFO_LINK,
                   rela_ini_off, rela_ini_size,
                   (uint32_t)sh_symtab_idx, (uint32_t)sh_init_array_idx, 8, 24);
    if (has_rela_fini)
        write_shdr(f, shn_rela_fini, SHT_RELA, SHF_INFO_LINK,
                   rela_fin_off, rela_fin_size,
                   (uint32_t)sh_symtab_idx, (uint32_t)sh_fini_array_idx, 8, 24);

    if (has_rela_debug_line)
        write_shdr(f, shn_rela_debug_line, SHT_RELA, SHF_INFO_LINK,
                   rela_debug_line_off, rela_debug_line_size,
                   (uint32_t)sh_symtab_idx, (uint32_t)sh_debug_line_idx, 8, 24);

    if (has_rela_debug_info)
        write_shdr(f, shn_rela_debug_info, SHT_RELA, SHF_INFO_LINK,
                   rela_debug_info_off, rela_debug_info_size,
                   (uint32_t)sh_symtab_idx, (uint32_t)sh_debug_info_idx, 8, 24);

    if (has_rela_debug_aranges)
        write_shdr(f, shn_rela_debug_aranges, SHT_RELA, SHF_INFO_LINK,
                   rela_debug_aranges_off, rela_debug_aranges_size,
                   (uint32_t)sh_symtab_idx, (uint32_t)sh_debug_aranges_idx, 8, 24);

    write_shdr(f, shn_symtab, SHT_SYMTAB, 0,
               symtab_off, symtab_size,
               (uint32_t)sh_strtab_idx, (uint32_t)first_global, 8, 24);
    write_shdr(f, shn_strtab, SHT_STRTAB, 0,
               strtab_off, strtab_size, 0, 0, 1, 0);
    write_shdr(f, shn_shstrtab, SHT_STRTAB, 0,
               shstrtab_off, shstrtab_size, 0, 0, 1, 0);

    fclose(f);
    free(sym_map);
    free(ea.data);
    free(symstrtab.data);
    free(shstrtab.data);
    free(shn_extra);
    free(shn_extra_rela);
    free(sh_extra_idx);
    free(sh_extra_rela_idx);
    free(extra_has_relocs);
    free(extra_off);
    free(extra_size);
    free(extra_rela_off);
    free(extra_rela_size);
    return 0;
}
