Skip to main content

synth_backend/
elf_builder.rs

1//! ELF (Executable and Linkable Format) Builder for ARM
2//!
3//! Generates ELF32 files for ARM Cortex-M targets
4
5use synth_core::Result;
6
7/// ELF file class
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ElfClass {
10    /// 32-bit
11    Elf32 = 1,
12    /// 64-bit
13    Elf64 = 2,
14}
15
16/// ELF data encoding
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ElfData {
19    /// Little-endian
20    LittleEndian = 1,
21    /// Big-endian
22    BigEndian = 2,
23}
24
25/// ELF file type
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ElfType {
28    /// Relocatable file
29    Rel = 1,
30    /// Executable file
31    Exec = 2,
32    /// Shared object file
33    Dyn = 3,
34}
35
36/// ELF machine architecture
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum ElfMachine {
39    /// ARM
40    Arm = 40,
41    /// ARM64/AArch64
42    AArch64 = 183,
43}
44
45/// Section type
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum SectionType {
48    /// Null section
49    Null = 0,
50    /// Program data
51    ProgBits = 1,
52    /// Symbol table
53    SymTab = 2,
54    /// String table
55    StrTab = 3,
56    /// Relocation entries with addends
57    Rela = 4,
58    /// Symbol hash table
59    Hash = 5,
60    /// Dynamic linking information
61    Dynamic = 6,
62    /// Note
63    Note = 7,
64    /// No space (BSS)
65    NoBits = 8,
66    /// Relocation entries
67    Rel = 9,
68    /// ARM build attributes (`SHT_ARM_ATTRIBUTES`, #637) — the `.ARM.attributes`
69    /// section every ARM toolchain consults to auto-select the Thumb/A32 decoder.
70    ArmAttributes = 0x7000_0003,
71}
72
73/// Section flags
74#[derive(Debug, Clone, Copy)]
75pub struct SectionFlags(pub u32);
76
77impl SectionFlags {
78    /// Writable
79    pub const WRITE: u32 = 0x1;
80    /// Occupies memory during execution
81    pub const ALLOC: u32 = 0x2;
82    /// Executable
83    pub const EXEC: u32 = 0x4;
84    /// Mergeable
85    pub const MERGE: u32 = 0x10;
86    /// Contains null-terminated strings
87    pub const STRINGS: u32 = 0x20;
88}
89
90/// ELF section
91#[derive(Debug, Clone)]
92pub struct Section {
93    /// Section name (index into string table)
94    pub name: String,
95    /// Section type
96    pub section_type: SectionType,
97    /// Section flags
98    pub flags: u32,
99    /// Virtual address
100    pub addr: u32,
101    /// Section data
102    pub data: Vec<u8>,
103    /// Alignment
104    pub align: u32,
105    /// Explicit size (for NoBits sections like .bss where data is empty)
106    pub explicit_size: Option<u32>,
107}
108
109impl Section {
110    /// Create a new section
111    pub fn new(name: &str, section_type: SectionType) -> Self {
112        Self {
113            name: name.to_string(),
114            section_type,
115            flags: 0,
116            addr: 0,
117            data: Vec::new(),
118            align: 1,
119            explicit_size: None,
120        }
121    }
122
123    /// Set flags
124    pub fn with_flags(mut self, flags: u32) -> Self {
125        self.flags = flags;
126        self
127    }
128
129    /// Set address
130    pub fn with_addr(mut self, addr: u32) -> Self {
131        self.addr = addr;
132        self
133    }
134
135    /// Set alignment
136    pub fn with_align(mut self, align: u32) -> Self {
137        self.align = align;
138        self
139    }
140
141    /// Add data
142    pub fn with_data(mut self, data: Vec<u8>) -> Self {
143        self.data = data;
144        self
145    }
146
147    /// Set explicit size (for NoBits sections like .bss where data is empty)
148    pub fn with_size(mut self, size: u32) -> Self {
149        self.explicit_size = Some(size);
150        self
151    }
152
153    /// Get the effective size of the section
154    pub fn size(&self) -> u32 {
155        self.explicit_size.unwrap_or(self.data.len() as u32)
156    }
157}
158
159/// Symbol binding
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum SymbolBinding {
162    /// Local symbol
163    Local = 0,
164    /// Global symbol
165    Global = 1,
166    /// Weak symbol
167    Weak = 2,
168}
169
170/// Symbol type
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum SymbolType {
173    /// No type
174    NoType = 0,
175    /// Object (data)
176    Object = 1,
177    /// Function
178    Func = 2,
179    /// Section
180    Section = 3,
181    /// File name
182    File = 4,
183}
184
185/// ELF symbol
186#[derive(Debug, Clone)]
187pub struct Symbol {
188    /// Symbol name
189    pub name: String,
190    /// Value/address
191    pub value: u32,
192    /// Size
193    pub size: u32,
194    /// Binding
195    pub binding: SymbolBinding,
196    /// Type
197    pub symbol_type: SymbolType,
198    /// Section index
199    pub section: u16,
200}
201
202impl Symbol {
203    /// Create a new symbol
204    pub fn new(name: &str) -> Self {
205        Self {
206            name: name.to_string(),
207            value: 0,
208            size: 0,
209            binding: SymbolBinding::Local,
210            symbol_type: SymbolType::NoType,
211            section: 0,
212        }
213    }
214
215    /// Set value
216    pub fn with_value(mut self, value: u32) -> Self {
217        self.value = value;
218        self
219    }
220
221    /// Set size
222    pub fn with_size(mut self, size: u32) -> Self {
223        self.size = size;
224        self
225    }
226
227    /// Set binding
228    pub fn with_binding(mut self, binding: SymbolBinding) -> Self {
229        self.binding = binding;
230        self
231    }
232
233    /// Set type
234    pub fn with_type(mut self, symbol_type: SymbolType) -> Self {
235        self.symbol_type = symbol_type;
236        self
237    }
238
239    /// Set section
240    pub fn with_section(mut self, section: u16) -> Self {
241        self.section = section;
242        self
243    }
244}
245
246/// Program header type
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum ProgramType {
249    /// Null entry
250    Null = 0,
251    /// Loadable segment
252    Load = 1,
253    /// Dynamic linking info
254    Dynamic = 2,
255    /// Interpreter path
256    Interp = 3,
257    /// Note section
258    Note = 4,
259}
260
261/// Program header flags
262pub struct ProgramFlags;
263
264impl ProgramFlags {
265    /// Executable
266    pub const EXEC: u32 = 0x1;
267    /// Writable
268    pub const WRITE: u32 = 0x2;
269    /// Readable
270    pub const READ: u32 = 0x4;
271}
272
273/// ELF program header (segment)
274#[derive(Debug, Clone)]
275pub struct ProgramHeader {
276    /// Segment type
277    pub p_type: ProgramType,
278    /// Offset in file
279    pub offset: u32,
280    /// Virtual address
281    pub vaddr: u32,
282    /// Physical address
283    pub paddr: u32,
284    /// Size in file
285    pub filesz: u32,
286    /// Size in memory
287    pub memsz: u32,
288    /// Flags (R/W/X)
289    pub flags: u32,
290    /// Alignment
291    pub align: u32,
292}
293
294impl ProgramHeader {
295    /// Create a new LOAD segment
296    pub fn load(vaddr: u32, offset: u32, size: u32, flags: u32) -> Self {
297        Self {
298            p_type: ProgramType::Load,
299            offset,
300            vaddr,
301            paddr: vaddr, // Physical = virtual for simple cases
302            filesz: size,
303            memsz: size,
304            flags,
305            align: 4,
306        }
307    }
308
309    /// Create a new LOAD segment for BSS-like regions (no file data, only memory)
310    /// Used for .bss, linear memory, and other zero-initialized regions
311    pub fn load_nobits(vaddr: u32, memsz: u32, flags: u32) -> Self {
312        Self {
313            p_type: ProgramType::Load,
314            offset: 0, // No file offset for NoBits
315            vaddr,
316            paddr: vaddr, // Physical = virtual
317            filesz: 0,    // No file data
318            memsz,        // Memory size to allocate
319            flags,
320            align: 4,
321        }
322    }
323}
324
325/// ARM relocation type
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub enum ArmRelocationType {
328    /// R_ARM_THM_CALL (10) — Thumb BL/BLX instruction (Cortex-M). This is the
329    /// correct relocation for a Thumb-2 `bl` call site; `Call`/R_ARM_CALL below
330    /// is the ARM-mode form and is mis-resolved by `ld` for Thumb calls.
331    ThmCall = 10,
332    /// R_ARM_CALL (28) — BL/BLX instruction
333    Call = 28,
334    /// R_ARM_JUMP24 (29) — B/BL<cond> instruction
335    Jump24 = 29,
336    /// R_ARM_ABS32 (2) — Direct 32-bit reference
337    Abs32 = 2,
338    /// R_ARM_MOVW_ABS_NC (43) — MOVW instruction (low 16 bits)
339    MovwAbsNc = 43,
340    /// R_ARM_MOVT_ABS (44) — MOVT instruction (high 16 bits)
341    MovtAbs = 44,
342}
343
344/// A built `.rel.<name>` section: its name offset in `.shstrtab`, the target
345/// section index it relocates (`sh_info`), its file offset, and the encoded
346/// REL entries. Internal to [`ElfBuilder::build`].
347struct ExtraRelSection {
348    name_offset: usize,
349    target_idx: u32,
350    offset: usize,
351    data: Vec<u8>,
352}
353
354/// ELF relocation entry (REL format, no addend)
355#[derive(Debug, Clone)]
356pub struct Relocation {
357    /// Offset within the section where the relocation applies
358    pub offset: u32,
359    /// Symbol index in the symbol table
360    pub symbol_index: u32,
361    /// Relocation type
362    pub reloc_type: ArmRelocationType,
363}
364
365/// ARM EABI version 5 (soft-float)
366pub const EF_ARM_EABI_VER5: u32 = 0x05000000;
367/// ARM hard-float ABI flag
368pub const EF_ARM_ABI_FLOAT_HARD: u32 = 0x00000400;
369/// ARM soft-float ABI flag
370pub const EF_ARM_ABI_FLOAT_SOFT: u32 = 0x00000200;
371
372/// ARM EABI build-attribute tags and values (#637) — "Addenda to, and Errata
373/// in, the ABI for the Arm Architecture" (build attributes). Only the tags the
374/// synth ELF writer emits; consumers (objdump, gdb, `synth disasm`) use them to
375/// auto-select the Thumb vs A32 decoder without a manual `--triple`.
376pub mod aeabi {
377    /// Tag_CPU_arch (uleb value)
378    pub const TAG_CPU_ARCH: u32 = 6;
379    /// Tag_CPU_arch_profile (uleb value: 'M', 'R', 'A')
380    pub const TAG_CPU_ARCH_PROFILE: u32 = 7;
381    /// Tag_ARM_ISA_use (0 = no A32, 1 = A32 permitted)
382    pub const TAG_ARM_ISA_USE: u32 = 8;
383    /// Tag_THUMB_ISA_use (0 = none, 1 = Thumb-1 (16-bit), 2 = Thumb-2)
384    pub const TAG_THUMB_ISA_USE: u32 = 9;
385
386    /// Tag_CPU_arch value: ARMv7 (Cortex-M3 / Cortex-R profile base)
387    pub const CPU_ARCH_V7: u32 = 10;
388    /// Tag_CPU_arch value: ARMv6-M (Cortex-M0)
389    pub const CPU_ARCH_V6M: u32 = 11;
390    /// Tag_CPU_arch value: ARMv7E-M (Cortex-M4/M7)
391    pub const CPU_ARCH_V7EM: u32 = 13;
392    /// Tag_CPU_arch value: ARMv8.1-M.mainline (Cortex-M55)
393    pub const CPU_ARCH_V8_1M_MAIN: u32 = 21;
394
395    /// Tag_CPU_arch_profile value: microcontroller
396    pub const PROFILE_M: u32 = b'M' as u32;
397    /// Tag_CPU_arch_profile value: real-time
398    pub const PROFILE_R: u32 = b'R' as u32;
399}
400
401/// Encode a u32 as ULEB128 (build-attribute value encoding).
402fn push_uleb128(out: &mut Vec<u8>, mut v: u32) {
403    loop {
404        let byte = (v & 0x7f) as u8;
405        v >>= 7;
406        if v == 0 {
407            out.push(byte);
408            break;
409        }
410        out.push(byte | 0x80);
411    }
412}
413
414/// Build the `.ARM.attributes` section (#637): format-version `'A'`, one
415/// `"aeabi"` vendor subsection carrying a single `Tag_File` (1) subsubsection
416/// with the given file-scope attributes. Tags with value 0 are omitted (0 is
417/// the spec default). Standard toolchains (objdump, gdb, ld) read this to
418/// auto-select the Thumb vs A32 decoder — synth objects become self-describing
419/// instead of requiring a manual `--triple=thumbv7m`.
420pub fn arm_attributes_section(
421    cpu_arch: u32,
422    cpu_arch_profile: u32,
423    arm_isa_use: u32,
424    thumb_isa_use: u32,
425) -> Section {
426    // File-scope attribute pairs (uleb tag, uleb value).
427    let mut attrs = Vec::new();
428    for (tag, value) in [
429        (aeabi::TAG_CPU_ARCH, cpu_arch),
430        (aeabi::TAG_CPU_ARCH_PROFILE, cpu_arch_profile),
431        (aeabi::TAG_ARM_ISA_USE, arm_isa_use),
432        (aeabi::TAG_THUMB_ISA_USE, thumb_isa_use),
433    ] {
434        if value != 0 {
435            push_uleb128(&mut attrs, tag);
436            push_uleb128(&mut attrs, value);
437        }
438    }
439
440    // Tag_File (1) subsubsection: tag byte + u32 length (self-inclusive) + attrs.
441    let file_len = (1 + 4 + attrs.len()) as u32;
442    let mut file_sub = vec![1u8]; // Tag_File
443    file_sub.extend_from_slice(&file_len.to_le_bytes());
444    file_sub.extend_from_slice(&attrs);
445
446    // "aeabi" vendor subsection: u32 length (self-inclusive) + NTBS name + data.
447    let vendor_name = b"aeabi\0";
448    let vendor_len = (4 + vendor_name.len() + file_sub.len()) as u32;
449    let mut blob = vec![b'A']; // format version
450    blob.extend_from_slice(&vendor_len.to_le_bytes());
451    blob.extend_from_slice(vendor_name);
452    blob.extend_from_slice(&file_sub);
453
454    Section::new(".ARM.attributes", SectionType::ArmAttributes)
455        .with_align(1)
456        .with_data(blob)
457}
458
459/// ELF file builder
460pub struct ElfBuilder {
461    /// File class (32 or 64 bit)
462    class: ElfClass,
463    /// Data encoding
464    data: ElfData,
465    /// File type
466    elf_type: ElfType,
467    /// Machine architecture
468    machine: ElfMachine,
469    /// Entry point address
470    entry: u32,
471    /// ELF e_flags (EABI version + float ABI)
472    e_flags: u32,
473    /// Sections
474    sections: Vec<Section>,
475    /// Symbols
476    symbols: Vec<Symbol>,
477    /// Program headers (segments)
478    program_headers: Vec<ProgramHeader>,
479    /// Relocations for .text section
480    relocations: Vec<Relocation>,
481    /// Extra per-section relocation tables, keyed by the target section's name
482    /// (e.g. `.debug_line`). Each produces a `.rel.<name>` section. Kept separate
483    /// from `relocations` (the `.text` set) so the existing `.rel.text` byte
484    /// layout is untouched: when this is empty the build is byte-identical to the
485    /// pre-generalization output (VCR-DBG-001 PR C, #394).
486    extra_relocations: Vec<(String, Vec<Relocation>)>,
487    /// #598: whether the object's functions are Thumb-encoded. Bit 0 of an
488    /// STT_FUNC `st_value` (and of `e_entry`) is the Thumb interworking bit —
489    /// it must be SET for Thumb code (Cortex-M) and CLEAR for A32 code
490    /// (cortex-r5 path). Defaults to `true` (every pre-#598 ARM object was
491    /// treated as Thumb, so Thumb outputs stay bit-identical).
492    thumb_funcs: bool,
493}
494
495impl ElfBuilder {
496    /// Create a new ELF builder for ARM32
497    pub fn new_arm32() -> Self {
498        Self {
499            class: ElfClass::Elf32,
500            data: ElfData::LittleEndian,
501            elf_type: ElfType::Exec,
502            machine: ElfMachine::Arm,
503            entry: 0,
504            e_flags: EF_ARM_EABI_VER5,
505            sections: Vec::new(),
506            symbols: Vec::new(),
507            program_headers: Vec::new(),
508            relocations: Vec::new(),
509            extra_relocations: Vec::new(),
510            thumb_funcs: true,
511        }
512    }
513
514    /// #598: mark the object's functions as A32-encoded (cortex-r5 path) —
515    /// suppresses the Thumb interworking bit on STT_FUNC `st_value`s and on
516    /// `e_entry`. Call BEFORE `with_entry`.
517    pub fn with_thumb_funcs(mut self, thumb: bool) -> Self {
518        self.thumb_funcs = thumb;
519        self
520    }
521
522    /// Set entry point
523    ///
524    /// For ARM Thumb targets, bit 0 is automatically set to indicate Thumb mode.
525    /// Cortex-M is Thumb-only, so function addresses in ELF must have bit 0 set.
526    /// A32 objects (`with_thumb_funcs(false)`, #598) keep bit 0 clear.
527    pub fn with_entry(mut self, entry: u32) -> Self {
528        self.entry = if self.machine == ElfMachine::Arm && self.thumb_funcs {
529            entry | 1 // Set Thumb bit for ARM Thumb targets
530        } else {
531            entry
532        };
533        self
534    }
535
536    /// Set ELF e_flags (e.g. to add hard-float ABI)
537    pub fn set_flags(&mut self, flags: u32) {
538        self.e_flags = flags;
539    }
540
541    /// Set file type
542    pub fn with_type(mut self, elf_type: ElfType) -> Self {
543        self.elf_type = elf_type;
544        self
545    }
546
547    /// Add a section
548    pub fn add_section(&mut self, section: Section) {
549        self.sections.push(section);
550    }
551
552    /// Add a symbol
553    pub fn add_symbol(&mut self, symbol: Symbol) {
554        self.symbols.push(symbol);
555    }
556
557    /// Add a symbol and return its 1-based index in `.symtab` (index 0 is the
558    /// reserved null symbol). Use when a later relocation must reference this
559    /// symbol — e.g. the `.text` base symbol the DWARF `.rel.debug_*` records
560    /// resolve against (VCR-DBG-001).
561    pub fn add_symbol_indexed(&mut self, symbol: Symbol) -> u32 {
562        let index = self.symbols.len() as u32 + 1;
563        self.symbols.push(symbol);
564        index
565    }
566
567    /// Add a program header (segment)
568    pub fn add_program_header(&mut self, ph: ProgramHeader) {
569        self.program_headers.push(ph);
570    }
571
572    /// Add a relocation entry for the .text section
573    pub fn add_relocation(&mut self, reloc: Relocation) {
574        self.relocations.push(reloc);
575    }
576
577    /// Add a relocation table targeting a non-`.text` section by name (e.g.
578    /// `.debug_line`). Produces a separate `.rel.<name>` section whose `sh_info`
579    /// points at the named section. The section must already have been added via
580    /// [`add_section`]; if no matching section exists at build time the table is
581    /// silently dropped. Used by VCR-DBG-001 to relocate the DWARF `.text`
582    /// references so a host linker fixes them up alongside `.text`.
583    pub fn add_section_relocations(&mut self, target_section: &str, relocs: Vec<Relocation>) {
584        if relocs.is_empty() {
585            return;
586        }
587        self.extra_relocations
588            .push((target_section.to_string(), relocs));
589    }
590
591    /// Add an undefined external symbol (e.g., __meld_dispatch_import)
592    /// Returns the symbol index (1-based, accounting for null symbol)
593    pub fn add_undefined_symbol(&mut self, name: &str) -> u32 {
594        let index = self.symbols.len() as u32 + 1; // +1 for null symbol
595        self.symbols.push(Symbol {
596            name: name.to_string(),
597            value: 0,
598            size: 0,
599            binding: SymbolBinding::Global,
600            symbol_type: SymbolType::Func,
601            section: 0, // SHN_UNDEF
602        });
603        index
604    }
605
606    /// Build the ELF file to bytes
607    pub fn build(&self) -> Result<Vec<u8>> {
608        let mut output = Vec::new();
609
610        // ELF header size (52 bytes for ELF32)
611        let header_size = 52;
612        // Program header size (32 bytes for ELF32)
613        let ph_entry_size = 32;
614        let ph_count = self.program_headers.len();
615        let ph_table_size = ph_entry_size * ph_count;
616
617        // Reserve space for ELF header + program headers
618        output.resize(header_size + ph_table_size, 0);
619
620        // Build string table for section names
621        let (shstrtab_data, section_name_offsets, extra_rel_name_offsets) =
622            self.build_section_string_table();
623
624        // Build symbol string table
625        let (strtab_data, symbol_name_offsets) = self.build_symbol_string_table();
626
627        // #656: ELF requires every STB_LOCAL symbol to precede all non-local
628        // symbols in `.symtab`, with the section's `sh_info` set to the index of
629        // the first non-local symbol. Callers add symbols in whatever order is
630        // convenient (and hold 1-based indices from `add_symbol_indexed` /
631        // `add_undefined_symbol` for their relocations), so the LOCAL/GLOBAL
632        // ordering is established here at build time: a stable locals-first
633        // permutation, plus an old→new index map every relocation is rewritten
634        // through. With zero local symbols (every pre-#656 object) the
635        // permutation is the identity and `sh_info` stays 1 — byte-identical.
636        let mut sym_order: Vec<usize> = (0..self.symbols.len()).collect();
637        sym_order.sort_by_key(|&i| self.symbols[i].binding != SymbolBinding::Local);
638        // old_to_new[old_1based] = new_1based; index 0 (the null symbol) maps to 0.
639        let mut old_to_new = vec![0u32; self.symbols.len() + 1];
640        for (new_pos, &old) in sym_order.iter().enumerate() {
641            old_to_new[old + 1] = new_pos as u32 + 1;
642        }
643        let local_count = self
644            .symbols
645            .iter()
646            .filter(|s| s.binding == SymbolBinding::Local)
647            .count();
648        // The null symbol (index 0) counts as local, so first-global = locals + 1.
649        let symtab_sh_info = local_count as u32 + 1;
650        let remap_relocs = |relocs: &[Relocation]| -> Vec<Relocation> {
651            relocs
652                .iter()
653                .map(|r| Relocation {
654                    offset: r.offset,
655                    symbol_index: old_to_new[r.symbol_index as usize],
656                    reloc_type: r.reloc_type,
657                })
658                .collect()
659        };
660
661        // Calculate section offsets (after ELF header + program headers)
662        let mut current_offset = header_size + ph_table_size;
663
664        // Section 1: .shstrtab (section name string table)
665        let shstrtab_offset = current_offset;
666        current_offset += shstrtab_data.len();
667
668        // Section 2: .strtab (symbol name string table)
669        let strtab_offset = current_offset;
670        current_offset += strtab_data.len();
671
672        // User sections
673        let mut section_offsets = Vec::new();
674        for section in &self.sections {
675            section_offsets.push(current_offset);
676            current_offset += section.data.len();
677        }
678
679        // Section 3: .symtab (symbol table), in locals-first order (#656)
680        let symtab_offset = current_offset;
681        let symtab_data = self.build_symbol_table(&symbol_name_offsets, &sym_order);
682        current_offset += symtab_data.len();
683
684        // Section 4+ (optional): .rel.text (relocations), symbol indices
685        // rewritten through the locals-first permutation (#656)
686        let rel_data = Self::encode_rel_entries(&remap_relocs(&self.relocations));
687        let rel_offset = current_offset;
688        current_offset += rel_data.len();
689
690        // Extra per-section relocation tables (.rel.<name>), laid out after
691        // .rel.text. Each entry resolves its target section index by name; a
692        // table whose target section is absent is dropped. Empty when no
693        // --debug-line ⇒ byte-identical to the pre-generalization layout.
694        let mut extra_rel: Vec<ExtraRelSection> = Vec::new();
695        for (i, (target, relocs)) in self.extra_relocations.iter().enumerate() {
696            let Some(target_idx) = self.section_index_by_name(target) else {
697                continue;
698            };
699            let data = Self::encode_rel_entries(&remap_relocs(relocs));
700            let name_offset = extra_rel_name_offsets.get(i).copied().unwrap_or(0);
701            extra_rel.push(ExtraRelSection {
702                name_offset,
703                target_idx,
704                offset: current_offset,
705                data,
706            });
707            current_offset += extra_rel.last().unwrap().data.len();
708        }
709
710        // Section header table comes at the end
711        let sh_offset = current_offset;
712
713        // Now write all the data
714        output.extend_from_slice(&shstrtab_data);
715        output.extend_from_slice(&strtab_data);
716
717        for section in &self.sections {
718            output.extend_from_slice(&section.data);
719        }
720
721        output.extend_from_slice(&symtab_data);
722        output.extend_from_slice(&rel_data);
723        for er in &extra_rel {
724            output.extend_from_slice(&er.data);
725        }
726
727        // Write section headers
728        let section_headers = self.build_section_headers_with_rel(
729            &section_name_offsets,
730            shstrtab_offset,
731            &shstrtab_data,
732            strtab_offset,
733            &strtab_data,
734            symtab_offset,
735            &symtab_data,
736            &section_offsets,
737            rel_offset,
738            &rel_data,
739            &extra_rel,
740            symtab_sh_info,
741        );
742        output.extend_from_slice(&section_headers);
743
744        // Write program headers (right after ELF header)
745        // Auto-correct p_offset for LOAD segments by matching vaddr to section addrs
746        for (i, ph) in self.program_headers.iter().enumerate() {
747            let ph_offset = header_size + i * ph_entry_size;
748            let mut corrected_ph = ph.clone();
749            if corrected_ph.filesz > 0 {
750                // Find the section whose addr matches this segment's vaddr
751                for (si, section) in self.sections.iter().enumerate() {
752                    if section.addr == corrected_ph.vaddr && si < section_offsets.len() {
753                        corrected_ph.offset = section_offsets[si] as u32;
754                        break;
755                    }
756                }
757            }
758            self.write_program_header(
759                &mut output[ph_offset..ph_offset + ph_entry_size],
760                &corrected_ph,
761            );
762        }
763
764        // Now write the actual ELF header at the beginning
765        let has_rel = !self.relocations.is_empty();
766        let num_sections = 4 + self.sections.len() + if has_rel { 1 } else { 0 } + extra_rel.len();
767        let ph_offset = if ph_count > 0 { header_size as u32 } else { 0 };
768        self.write_elf_header_with_phdrs(
769            &mut output[0..header_size],
770            ph_offset,
771            ph_count as u16,
772            sh_offset as u32,
773            num_sections as u16,
774        )?;
775
776        Ok(output)
777    }
778
779    /// Write a single program header
780    fn write_program_header(&self, output: &mut [u8], ph: &ProgramHeader) {
781        let mut cursor = 0;
782
783        // p_type (4 bytes)
784        output[cursor..cursor + 4].copy_from_slice(&(ph.p_type as u32).to_le_bytes());
785        cursor += 4;
786
787        // p_offset (4 bytes)
788        output[cursor..cursor + 4].copy_from_slice(&ph.offset.to_le_bytes());
789        cursor += 4;
790
791        // p_vaddr (4 bytes)
792        output[cursor..cursor + 4].copy_from_slice(&ph.vaddr.to_le_bytes());
793        cursor += 4;
794
795        // p_paddr (4 bytes)
796        output[cursor..cursor + 4].copy_from_slice(&ph.paddr.to_le_bytes());
797        cursor += 4;
798
799        // p_filesz (4 bytes)
800        output[cursor..cursor + 4].copy_from_slice(&ph.filesz.to_le_bytes());
801        cursor += 4;
802
803        // p_memsz (4 bytes)
804        output[cursor..cursor + 4].copy_from_slice(&ph.memsz.to_le_bytes());
805        cursor += 4;
806
807        // p_flags (4 bytes)
808        output[cursor..cursor + 4].copy_from_slice(&ph.flags.to_le_bytes());
809        cursor += 4;
810
811        // p_align (4 bytes)
812        output[cursor..cursor + 4].copy_from_slice(&ph.align.to_le_bytes());
813    }
814
815    /// Write ELF header with program header info
816    fn write_elf_header_with_phdrs(
817        &self,
818        output: &mut [u8],
819        ph_offset: u32,
820        ph_count: u16,
821        sh_offset: u32,
822        sh_count: u16,
823    ) -> Result<()> {
824        let mut cursor = 0;
825
826        // ELF magic number
827        output[cursor..cursor + 4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
828        cursor += 4;
829
830        // Class (32-bit)
831        output[cursor] = self.class as u8;
832        cursor += 1;
833
834        // Data (little-endian)
835        output[cursor] = self.data as u8;
836        cursor += 1;
837
838        // Version
839        output[cursor] = 1;
840        cursor += 1;
841
842        // OS/ABI
843        output[cursor] = 0; // System V
844        cursor += 1;
845
846        // ABI version
847        output[cursor] = 0;
848        cursor += 1;
849
850        // Padding (7 bytes)
851        output[cursor..cursor + 7].copy_from_slice(&[0; 7]);
852        cursor += 7;
853
854        // Type (little-endian u16)
855        let etype = self.elf_type as u16;
856        output[cursor..cursor + 2].copy_from_slice(&etype.to_le_bytes());
857        cursor += 2;
858
859        // Machine (little-endian u16)
860        let machine = self.machine as u16;
861        output[cursor..cursor + 2].copy_from_slice(&machine.to_le_bytes());
862        cursor += 2;
863
864        // Version (little-endian u32)
865        output[cursor..cursor + 4].copy_from_slice(&1u32.to_le_bytes());
866        cursor += 4;
867
868        // Entry point (little-endian u32)
869        output[cursor..cursor + 4].copy_from_slice(&self.entry.to_le_bytes());
870        cursor += 4;
871
872        // Program header offset (little-endian u32)
873        output[cursor..cursor + 4].copy_from_slice(&ph_offset.to_le_bytes());
874        cursor += 4;
875
876        // Section header offset (little-endian u32)
877        output[cursor..cursor + 4].copy_from_slice(&sh_offset.to_le_bytes());
878        cursor += 4;
879
880        // Flags (little-endian u32) - ARM EABI version 5 + float ABI
881        output[cursor..cursor + 4].copy_from_slice(&self.e_flags.to_le_bytes());
882        cursor += 4;
883
884        // ELF header size (little-endian u16)
885        output[cursor..cursor + 2].copy_from_slice(&52u16.to_le_bytes());
886        cursor += 2;
887
888        // Program header entry size (little-endian u16)
889        let ph_entry_size: u16 = if ph_count > 0 { 32 } else { 0 };
890        output[cursor..cursor + 2].copy_from_slice(&ph_entry_size.to_le_bytes());
891        cursor += 2;
892
893        // Program header count (little-endian u16)
894        output[cursor..cursor + 2].copy_from_slice(&ph_count.to_le_bytes());
895        cursor += 2;
896
897        // Section header entry size (little-endian u16)
898        output[cursor..cursor + 2].copy_from_slice(&40u16.to_le_bytes());
899        cursor += 2;
900
901        // Section header count (little-endian u16)
902        output[cursor..cursor + 2].copy_from_slice(&sh_count.to_le_bytes());
903        cursor += 2;
904
905        // Section header string table index (little-endian u16) - .shstrtab is section 1
906        output[cursor..cursor + 2].copy_from_slice(&1u16.to_le_bytes());
907
908        Ok(())
909    }
910
911    /// Build section name string table. Returns the bytes, the per-user-section
912    /// name offsets, and the per-extra-relocation `.rel.<name>` name offsets
913    /// (parallel to `self.extra_relocations`). The extra-rel names are appended
914    /// AFTER `.rel.text`, so when `extra_relocations` is empty the table is
915    /// byte-identical to the pre-generalization layout.
916    fn build_section_string_table(&self) -> (Vec<u8>, Vec<usize>, Vec<usize>) {
917        let mut strtab = vec![0]; // null string at offset 0
918        let mut offsets = Vec::new();
919
920        // Standard sections
921        strtab.extend_from_slice(b".shstrtab\0");
922        strtab.extend_from_slice(b".strtab\0");
923        strtab.extend_from_slice(b".symtab\0");
924
925        // User sections
926        for section in &self.sections {
927            let offset = strtab.len();
928            offsets.push(offset);
929            strtab.extend_from_slice(section.name.as_bytes());
930            strtab.push(0);
931        }
932
933        // .rel.text (if relocations exist)
934        if !self.relocations.is_empty() {
935            strtab.extend_from_slice(b".rel.text\0");
936        }
937
938        // .rel.<name> for each extra per-section relocation table.
939        let mut extra_rel_offsets = Vec::new();
940        for (target, _) in &self.extra_relocations {
941            let offset = strtab.len();
942            extra_rel_offsets.push(offset);
943            strtab.extend_from_slice(format!(".rel{target}\0").as_bytes());
944        }
945
946        (strtab, offsets, extra_rel_offsets)
947    }
948
949    /// Build symbol name string table
950    fn build_symbol_string_table(&self) -> (Vec<u8>, Vec<usize>) {
951        let mut strtab = vec![0]; // null string at offset 0
952        let mut offsets = Vec::new();
953
954        for symbol in &self.symbols {
955            let offset = strtab.len();
956            offsets.push(offset);
957            strtab.extend_from_slice(symbol.name.as_bytes());
958            strtab.push(0);
959        }
960
961        (strtab, offsets)
962    }
963
964    /// Encode a slice of relocations as ELF32 REL entries (8 bytes each). Shared
965    /// by `.rel.text` and the per-section `.rel.<name>` tables.
966    fn encode_rel_entries(relocs: &[Relocation]) -> Vec<u8> {
967        let mut rel_data = Vec::new();
968        for reloc in relocs {
969            // r_offset (4 bytes)
970            rel_data.extend_from_slice(&reloc.offset.to_le_bytes());
971            // r_info (4 bytes) = (sym_index << 8) | type
972            let r_info = (reloc.symbol_index << 8) | (reloc.reloc_type as u32);
973            rel_data.extend_from_slice(&r_info.to_le_bytes());
974        }
975        rel_data
976    }
977
978    /// Resolve a target section name to its ELF section index. User sections
979    /// begin at index 4 (null=0, shstrtab=1, strtab=2, symtab=3). Returns `None`
980    /// if no user section has that name.
981    fn section_index_by_name(&self, name: &str) -> Option<u32> {
982        self.sections
983            .iter()
984            .position(|s| s.name == name)
985            .map(|pos| 4 + pos as u32)
986    }
987
988    /// Build symbol table. `order` is the locals-first permutation of
989    /// `self.symbols` computed in [`build`] (#656): entry `k` of the emitted
990    /// table (after the null symbol) is `self.symbols[order[k]]`.
991    fn build_symbol_table(&self, name_offsets: &[usize], order: &[usize]) -> Vec<u8> {
992        let mut symtab = Vec::new();
993
994        // First entry is always null symbol
995        symtab.extend_from_slice(&[0u8; 16]); // 16 bytes per symbol in ELF32
996
997        // User symbols, locals first (#656)
998        for &i in order {
999            let symbol = &self.symbols[i];
1000            let name_offset = if i < name_offsets.len() {
1001                name_offsets[i] as u32
1002            } else {
1003                0
1004            };
1005
1006            // st_name (4 bytes)
1007            symtab.extend_from_slice(&name_offset.to_le_bytes());
1008
1009            // st_value (4 bytes)
1010            // For ARM THUMB targets, STT_FUNC symbols must have bit 0 set (Thumb
1011            // interworking). #598: A32 objects (cortex-r5 path) must NOT set it —
1012            // bit 0 on an A32 function address is wrong metadata (harnesses had
1013            // to mask it; an interworking-aware consumer would mis-classify the
1014            // function as Thumb).
1015            let value = if self.machine == ElfMachine::Arm
1016                && self.thumb_funcs
1017                && symbol.symbol_type == SymbolType::Func
1018            {
1019                symbol.value | 1
1020            } else {
1021                symbol.value
1022            };
1023            symtab.extend_from_slice(&value.to_le_bytes());
1024
1025            // st_size (4 bytes)
1026            symtab.extend_from_slice(&symbol.size.to_le_bytes());
1027
1028            // st_info (1 byte) = (binding << 4) | (type & 0xf)
1029            let info = ((symbol.binding as u8) << 4) | (symbol.symbol_type as u8 & 0xf);
1030            symtab.push(info);
1031
1032            // st_other (1 byte)
1033            symtab.push(0);
1034
1035            // st_shndx (2 bytes)
1036            symtab.extend_from_slice(&symbol.section.to_le_bytes());
1037        }
1038
1039        symtab
1040    }
1041
1042    /// Build section headers (with optional .rel.text)
1043    #[allow(clippy::too_many_arguments)]
1044    fn build_section_headers_with_rel(
1045        &self,
1046        section_name_offsets: &[usize],
1047        shstrtab_offset: usize,
1048        shstrtab_data: &[u8],
1049        strtab_offset: usize,
1050        strtab_data: &[u8],
1051        symtab_offset: usize,
1052        symtab_data: &[u8],
1053        section_offsets: &[usize],
1054        rel_offset: usize,
1055        rel_data: &[u8],
1056        extra_rel: &[ExtraRelSection],
1057        // #656: `.symtab` sh_info = index of the first non-LOCAL symbol
1058        // (1 + number of local symbols; the null symbol counts as local).
1059        symtab_sh_info: u32,
1060    ) -> Vec<u8> {
1061        let mut headers = Vec::new();
1062
1063        // Section header size is 40 bytes for ELF32
1064
1065        // Section 0: null section
1066        headers.extend_from_slice(&[0u8; 40]);
1067
1068        // Section 1: .shstrtab
1069        self.write_section_header(
1070            &mut headers,
1071            1,
1072            SectionType::StrTab as u32,
1073            0,
1074            0,
1075            shstrtab_offset as u32,
1076            shstrtab_data.len() as u32,
1077            0,
1078            0,
1079            1,
1080            0,
1081        );
1082
1083        // Section 2: .strtab
1084        let strtab_name_offset = ".shstrtab\0".len();
1085        self.write_section_header(
1086            &mut headers,
1087            strtab_name_offset as u32,
1088            SectionType::StrTab as u32,
1089            0,
1090            0,
1091            strtab_offset as u32,
1092            strtab_data.len() as u32,
1093            0,
1094            0,
1095            1,
1096            0,
1097        );
1098
1099        // Section 3: .symtab (links to .strtab which is section 2)
1100        let symtab_name_offset = ".shstrtab\0.strtab\0".len();
1101        self.write_section_header(
1102            &mut headers,
1103            symtab_name_offset as u32,
1104            SectionType::SymTab as u32,
1105            0,
1106            0,
1107            symtab_offset as u32,
1108            symtab_data.len() as u32,
1109            2,
1110            // #656: was hardcoded 1 (the #430 blocker) — with local symbols
1111            // present that under-reports, and `ld` then treats every local as
1112            // global-bindable. Now the real first-non-local index.
1113            symtab_sh_info,
1114            4,
1115            16,
1116        );
1117
1118        // User sections
1119        for (i, section) in self.sections.iter().enumerate() {
1120            let name_offset = if i < section_name_offsets.len() {
1121                section_name_offsets[i] as u32
1122            } else {
1123                0
1124            };
1125            let offset = if i < section_offsets.len() {
1126                section_offsets[i] as u32
1127            } else {
1128                0
1129            };
1130
1131            self.write_section_header(
1132                &mut headers,
1133                name_offset,
1134                section.section_type as u32,
1135                section.flags,
1136                section.addr,
1137                offset,
1138                section.size(),
1139                0,
1140                0,
1141                section.align,
1142                0,
1143            );
1144        }
1145
1146        // .rel.text section (if relocations exist)
1147        if !rel_data.is_empty() {
1148            let rel_name_offset = self.rel_text_shstrtab_offset();
1149            // sh_link = symtab section index (3), sh_info = .text section index (4, first user section)
1150            let text_section_idx = 4u32; // null(0) + shstrtab(1) + strtab(2) + symtab(3) + .text(4)
1151            self.write_section_header(
1152                &mut headers,
1153                rel_name_offset as u32,
1154                SectionType::Rel as u32,
1155                0,
1156                0,
1157                rel_offset as u32,
1158                rel_data.len() as u32,
1159                3,                // sh_link = .symtab section index
1160                text_section_idx, // sh_info = section to which relocations apply
1161                4,
1162                8, // Each REL entry is 8 bytes
1163            );
1164        }
1165
1166        // Extra .rel.<name> sections (e.g. .rel.debug_line). Same shape as
1167        // .rel.text but sh_info points at the named target section.
1168        for er in extra_rel {
1169            self.write_section_header(
1170                &mut headers,
1171                er.name_offset as u32,
1172                SectionType::Rel as u32,
1173                0,
1174                0,
1175                er.offset as u32,
1176                er.data.len() as u32,
1177                3,             // sh_link = .symtab section index
1178                er.target_idx, // sh_info = relocated section
1179                4,
1180                8, // Each REL entry is 8 bytes
1181            );
1182        }
1183
1184        headers
1185    }
1186
1187    /// Compute the shstrtab offset where .rel.text name begins
1188    fn rel_text_shstrtab_offset(&self) -> usize {
1189        // Layout: \0 .shstrtab\0 .strtab\0 .symtab\0 [user sections...] .rel.text\0
1190        let mut offset = 1 + ".shstrtab\0".len() + ".strtab\0".len() + ".symtab\0".len();
1191        for section in &self.sections {
1192            offset += section.name.len() + 1;
1193        }
1194        offset
1195    }
1196
1197    /// Write a single section header
1198    #[allow(clippy::too_many_arguments)]
1199    fn write_section_header(
1200        &self,
1201        output: &mut Vec<u8>,
1202        name: u32,
1203        sh_type: u32,
1204        flags: u32,
1205        addr: u32,
1206        offset: u32,
1207        size: u32,
1208        link: u32,
1209        info: u32,
1210        align: u32,
1211        entsize: u32,
1212    ) {
1213        output.extend_from_slice(&name.to_le_bytes());
1214        output.extend_from_slice(&sh_type.to_le_bytes());
1215        output.extend_from_slice(&flags.to_le_bytes());
1216        output.extend_from_slice(&addr.to_le_bytes());
1217        output.extend_from_slice(&offset.to_le_bytes());
1218        output.extend_from_slice(&size.to_le_bytes());
1219        output.extend_from_slice(&link.to_le_bytes());
1220        output.extend_from_slice(&info.to_le_bytes());
1221        output.extend_from_slice(&align.to_le_bytes());
1222        output.extend_from_slice(&entsize.to_le_bytes());
1223    }
1224
1225    /// Write ELF header (legacy method for tests)
1226    #[allow(dead_code)]
1227    fn write_elf_header(&self, output: &mut Vec<u8>) -> Result<()> {
1228        // ELF magic number
1229        output.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
1230
1231        // Class (32-bit)
1232        output.push(self.class as u8);
1233
1234        // Data (little-endian)
1235        output.push(self.data as u8);
1236
1237        // Version
1238        output.push(1);
1239
1240        // OS/ABI
1241        output.push(0); // System V
1242
1243        // ABI version
1244        output.push(0);
1245
1246        // Padding
1247        output.extend_from_slice(&[0; 7]);
1248
1249        // Type (little-endian u16)
1250        let etype = self.elf_type as u16;
1251        output.extend_from_slice(&etype.to_le_bytes());
1252
1253        // Machine (little-endian u16)
1254        let machine = self.machine as u16;
1255        output.extend_from_slice(&machine.to_le_bytes());
1256
1257        // Version (little-endian u32)
1258        output.extend_from_slice(&1u32.to_le_bytes());
1259
1260        // Entry point (little-endian u32)
1261        output.extend_from_slice(&self.entry.to_le_bytes());
1262
1263        // Program header offset (little-endian u32)
1264        output.extend_from_slice(&0u32.to_le_bytes());
1265
1266        // Section header offset (little-endian u32)
1267        output.extend_from_slice(&0u32.to_le_bytes());
1268
1269        // Flags (little-endian u32)
1270        output.extend_from_slice(&0u32.to_le_bytes());
1271
1272        // ELF header size (little-endian u16)
1273        output.extend_from_slice(&52u16.to_le_bytes());
1274
1275        // Program header entry size (little-endian u16)
1276        output.extend_from_slice(&0u16.to_le_bytes());
1277
1278        // Program header count (little-endian u16)
1279        output.extend_from_slice(&0u16.to_le_bytes());
1280
1281        // Section header entry size (little-endian u16)
1282        output.extend_from_slice(&40u16.to_le_bytes());
1283
1284        // Section header count (little-endian u16)
1285        output.extend_from_slice(&0u16.to_le_bytes());
1286
1287        // Section header string table index (little-endian u16)
1288        output.extend_from_slice(&0u16.to_le_bytes());
1289
1290        Ok(())
1291    }
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296    use super::*;
1297
1298    #[test]
1299    fn test_elf_builder_creation() {
1300        let builder = ElfBuilder::new_arm32();
1301        assert_eq!(builder.class, ElfClass::Elf32);
1302        assert_eq!(builder.data, ElfData::LittleEndian);
1303        assert_eq!(builder.machine, ElfMachine::Arm);
1304    }
1305
1306    #[test]
1307    fn test_section_creation() {
1308        let section = Section::new(".text", SectionType::ProgBits)
1309            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1310            .with_addr(0x8000)
1311            .with_align(4);
1312
1313        assert_eq!(section.name, ".text");
1314        assert_eq!(section.section_type, SectionType::ProgBits);
1315        assert_eq!(section.addr, 0x8000);
1316        assert_eq!(section.align, 4);
1317    }
1318
1319    #[test]
1320    fn test_symbol_creation() {
1321        let symbol = Symbol::new("main")
1322            .with_value(0x8000)
1323            .with_size(128)
1324            .with_binding(SymbolBinding::Global)
1325            .with_type(SymbolType::Func)
1326            .with_section(1);
1327
1328        assert_eq!(symbol.name, "main");
1329        assert_eq!(symbol.value, 0x8000);
1330        assert_eq!(symbol.size, 128);
1331        assert_eq!(symbol.binding, SymbolBinding::Global);
1332        assert_eq!(symbol.symbol_type, SymbolType::Func);
1333    }
1334
1335    #[test]
1336    fn test_elf_header_generation() {
1337        let builder = ElfBuilder::new_arm32().with_entry(0x8000);
1338        let elf = builder.build().unwrap();
1339
1340        // Check magic number
1341        assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
1342
1343        // Check class (32-bit)
1344        assert_eq!(elf[4], 1);
1345
1346        // Check data (little-endian)
1347        assert_eq!(elf[5], 1);
1348
1349        // Check version
1350        assert_eq!(elf[6], 1);
1351    }
1352
1353    #[test]
1354    fn test_add_sections() {
1355        let mut builder = ElfBuilder::new_arm32();
1356
1357        let text = Section::new(".text", SectionType::ProgBits)
1358            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC);
1359
1360        let data = Section::new(".data", SectionType::ProgBits)
1361            .with_flags(SectionFlags::ALLOC | SectionFlags::WRITE);
1362
1363        builder.add_section(text);
1364        builder.add_section(data);
1365
1366        assert_eq!(builder.sections.len(), 2);
1367    }
1368
1369    #[test]
1370    fn test_add_symbols() {
1371        let mut builder = ElfBuilder::new_arm32();
1372
1373        let main_sym = Symbol::new("main")
1374            .with_binding(SymbolBinding::Global)
1375            .with_type(SymbolType::Func);
1376
1377        let data_sym = Symbol::new("data")
1378            .with_binding(SymbolBinding::Local)
1379            .with_type(SymbolType::Object);
1380
1381        builder.add_symbol(main_sym);
1382        builder.add_symbol(data_sym);
1383
1384        assert_eq!(builder.symbols.len(), 2);
1385    }
1386
1387    #[test]
1388    fn test_complete_elf_generation() {
1389        // Create a complete ELF file with sections and symbols
1390        let mut builder = ElfBuilder::new_arm32()
1391            .with_entry(0x8000)
1392            .with_type(ElfType::Exec);
1393
1394        // Add .text section with some ARM code
1395        let text_code = vec![
1396            0x00, 0x48, 0x2d, 0xe9, // push {fp, lr}
1397            0x04, 0xb0, 0x8d, 0xe2, // add fp, sp, #4
1398            0x00, 0x00, 0xa0, 0xe3, // mov r0, #0
1399            0x00, 0x88, 0xbd, 0xe8, // pop {fp, pc}
1400        ];
1401        let text = Section::new(".text", SectionType::ProgBits)
1402            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1403            .with_addr(0x8000)
1404            .with_align(4)
1405            .with_data(text_code);
1406
1407        builder.add_section(text);
1408
1409        // Add .data section
1410        let data_content = vec![0x01, 0x02, 0x03, 0x04];
1411        let data = Section::new(".data", SectionType::ProgBits)
1412            .with_flags(SectionFlags::ALLOC | SectionFlags::WRITE)
1413            .with_addr(0x8100)
1414            .with_align(4)
1415            .with_data(data_content);
1416
1417        builder.add_section(data);
1418
1419        // Add .bss section (no data)
1420        let bss = Section::new(".bss", SectionType::NoBits)
1421            .with_flags(SectionFlags::ALLOC | SectionFlags::WRITE)
1422            .with_addr(0x8200)
1423            .with_align(4);
1424
1425        builder.add_section(bss);
1426
1427        // Add symbols
1428        let main_sym = Symbol::new("main")
1429            .with_value(0x8000)
1430            .with_size(16)
1431            .with_binding(SymbolBinding::Global)
1432            .with_type(SymbolType::Func)
1433            .with_section(4); // .text is section 4 (0=null, 1=shstrtab, 2=strtab, 3=symtab, 4=.text)
1434
1435        builder.add_symbol(main_sym);
1436
1437        let data_var = Symbol::new("global_var")
1438            .with_value(0x8100)
1439            .with_size(4)
1440            .with_binding(SymbolBinding::Global)
1441            .with_type(SymbolType::Object)
1442            .with_section(5); // .data is section 5
1443
1444        builder.add_symbol(data_var);
1445
1446        // Build the ELF file
1447        let elf = builder.build().unwrap();
1448
1449        // Validate ELF header
1450        assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
1451        assert_eq!(elf[4], 1); // 32-bit
1452        assert_eq!(elf[5], 1); // little-endian
1453        assert_eq!(elf[6], 1); // version
1454
1455        // Check that we have a reasonable file size
1456        assert!(elf.len() > 52); // At least header size
1457        assert!(elf.len() < 10000); // Reasonable upper bound
1458
1459        // Validate entry point is set correctly (Thumb bit set for ARM)
1460        let entry_bytes = &elf[24..28];
1461        let entry = u32::from_le_bytes([
1462            entry_bytes[0],
1463            entry_bytes[1],
1464            entry_bytes[2],
1465            entry_bytes[3],
1466        ]);
1467        assert_eq!(entry, 0x8001); // 0x8000 | 1 (Thumb bit)
1468
1469        // Validate section header offset is non-zero
1470        let sh_off_bytes = &elf[32..36];
1471        let sh_off = u32::from_le_bytes([
1472            sh_off_bytes[0],
1473            sh_off_bytes[1],
1474            sh_off_bytes[2],
1475            sh_off_bytes[3],
1476        ]);
1477        assert!(sh_off > 0);
1478
1479        // Validate section count (null + shstrtab + strtab + symtab + .text + .data + .bss = 7)
1480        let sh_num_bytes = &elf[48..50];
1481        let sh_num = u16::from_le_bytes([sh_num_bytes[0], sh_num_bytes[1]]);
1482        assert_eq!(sh_num, 7);
1483
1484        // Validate string table index points to .shstrtab (section 1)
1485        let shstrndx_bytes = &elf[50..52];
1486        let shstrndx = u16::from_le_bytes([shstrndx_bytes[0], shstrndx_bytes[1]]);
1487        assert_eq!(shstrndx, 1);
1488    }
1489
1490    #[test]
1491    fn test_string_table_generation() {
1492        let mut builder = ElfBuilder::new_arm32();
1493
1494        builder.add_section(Section::new(".text", SectionType::ProgBits));
1495        builder.add_section(Section::new(".data", SectionType::ProgBits));
1496
1497        let (strtab, offsets, _extra_rel_offsets) = builder.build_section_string_table();
1498
1499        // Should have null byte at start
1500        assert_eq!(strtab[0], 0);
1501
1502        // Should contain .shstrtab, .strtab, .symtab, .text, .data
1503        let strtab_str = String::from_utf8_lossy(&strtab);
1504        assert!(strtab_str.contains(".shstrtab"));
1505        assert!(strtab_str.contains(".strtab"));
1506        assert!(strtab_str.contains(".symtab"));
1507        assert!(strtab_str.contains(".text"));
1508        assert!(strtab_str.contains(".data"));
1509
1510        // Should have offsets for user sections
1511        assert_eq!(offsets.len(), 2);
1512    }
1513
1514    #[test]
1515    fn test_relocation_support() {
1516        let mut builder = ElfBuilder::new_arm32()
1517            .with_entry(0x8000)
1518            .with_type(ElfType::Rel);
1519
1520        // Add .text section with a BL placeholder
1521        let text_code = vec![0x00u8; 16]; // 4 instructions of placeholder
1522        let text = Section::new(".text", SectionType::ProgBits)
1523            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1524            .with_addr(0x8000)
1525            .with_align(4)
1526            .with_data(text_code);
1527        builder.add_section(text);
1528
1529        // Add undefined external symbol
1530        let sym_idx = builder.add_undefined_symbol("__meld_dispatch_import");
1531        assert!(sym_idx > 0);
1532
1533        // Add relocation for the BL at offset 4
1534        builder.add_relocation(Relocation {
1535            offset: 4,
1536            symbol_index: sym_idx,
1537            reloc_type: ArmRelocationType::Call,
1538        });
1539
1540        let elf = builder.build().unwrap();
1541
1542        // Verify ELF is valid
1543        assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
1544
1545        // Section count should include .rel.text
1546        // null(1) + shstrtab(1) + strtab(1) + symtab(1) + .text(1) + .rel.text(1) = 6
1547        let sh_num = u16::from_le_bytes([elf[48], elf[49]]);
1548        assert_eq!(sh_num, 6);
1549
1550        // Verify the symbol table contains the undefined symbol
1551        // (section = 0 for SHN_UNDEF)
1552        let has_undef = elf
1553            .windows(b"__meld_dispatch_import".len())
1554            .any(|w| w == b"__meld_dispatch_import");
1555        assert!(
1556            has_undef,
1557            "ELF should contain __meld_dispatch_import symbol name"
1558        );
1559    }
1560
1561    #[test]
1562    fn test_symbol_table_encoding() {
1563        let mut builder = ElfBuilder::new_arm32();
1564
1565        let sym = Symbol::new("test_func")
1566            .with_value(0x1000)
1567            .with_size(64)
1568            .with_binding(SymbolBinding::Global)
1569            .with_type(SymbolType::Func)
1570            .with_section(1);
1571
1572        builder.add_symbol(sym);
1573
1574        let (_strtab, offsets) = builder.build_symbol_string_table();
1575        let symtab = builder.build_symbol_table(&offsets, &[0]);
1576
1577        // Should have null symbol (16 bytes) + 1 symbol (16 bytes) = 32 bytes
1578        assert_eq!(symtab.len(), 32);
1579
1580        // First symbol should be all zeros
1581        assert!(symtab[0..16].iter().all(|&b| b == 0));
1582
1583        // Second symbol should have correct encoding
1584        // Check st_value (bytes 4-7 of second entry)
1585        // For ARM STT_FUNC symbols, bit 0 is set for Thumb interworking
1586        let value_bytes = &symtab[20..24];
1587        let value = u32::from_le_bytes([
1588            value_bytes[0],
1589            value_bytes[1],
1590            value_bytes[2],
1591            value_bytes[3],
1592        ]);
1593        assert_eq!(value, 0x1001); // 0x1000 | 1 (Thumb bit)
1594
1595        // Check st_size (bytes 8-11 of second entry)
1596        let size_bytes = &symtab[24..28];
1597        let size = u32::from_le_bytes([size_bytes[0], size_bytes[1], size_bytes[2], size_bytes[3]]);
1598        assert_eq!(size, 64);
1599
1600        // Check st_info (byte 12 of second entry)
1601        let info = symtab[28];
1602        let binding = info >> 4;
1603        let sym_type = info & 0xf;
1604        assert_eq!(binding, SymbolBinding::Global as u8);
1605        assert_eq!(sym_type, SymbolType::Func as u8);
1606    }
1607
1608    /// #598: an A32 object (`with_thumb_funcs(false)`, cortex-r5 path) must
1609    /// NOT set the Thumb interworking bit on STT_FUNC symbols or `e_entry` —
1610    /// bit 0 on an A32 code address is wrong metadata (harnesses had to mask
1611    /// it). Non-func symbols never carried the bit; that stays true.
1612    #[test]
1613    fn test_a32_symbols_have_no_thumb_bit_598() {
1614        let mut builder = ElfBuilder::new_arm32().with_thumb_funcs(false);
1615
1616        let func_sym = Symbol::new("a32_func")
1617            .with_value(0x1000)
1618            .with_size(64)
1619            .with_binding(SymbolBinding::Global)
1620            .with_type(SymbolType::Func)
1621            .with_section(1);
1622        builder.add_symbol(func_sym);
1623
1624        let (_strtab, offsets) = builder.build_symbol_string_table();
1625        let symtab = builder.build_symbol_table(&offsets, &[0]);
1626        let value = u32::from_le_bytes(symtab[20..24].try_into().unwrap());
1627        assert_eq!(value, 0x1000, "A32 STT_FUNC st_value must keep bit 0 clear");
1628
1629        // e_entry stays clear too (was `0 | 1` on the A32 relocatable path).
1630        let builder = ElfBuilder::new_arm32()
1631            .with_thumb_funcs(false)
1632            .with_entry(0x8000);
1633        assert_eq!(builder.entry, 0x8000, "A32 e_entry must keep bit 0 clear");
1634
1635        // The default (Thumb) behavior is unchanged: bit 0 set on both.
1636        let builder = ElfBuilder::new_arm32().with_entry(0x8000);
1637        assert_eq!(builder.entry, 0x8001, "Thumb e_entry keeps the bit");
1638    }
1639
1640    /// Minimal ELF32 section-header reader for the tests below:
1641    /// (sh_type, sh_offset, sh_size, sh_link, sh_info) per section.
1642    fn read_section_headers(elf: &[u8]) -> Vec<(u32, u32, u32, u32, u32)> {
1643        let e_shoff = u32::from_le_bytes(elf[32..36].try_into().unwrap()) as usize;
1644        let e_shnum = u16::from_le_bytes(elf[48..50].try_into().unwrap()) as usize;
1645        (0..e_shnum)
1646            .map(|i| {
1647                let base = e_shoff + i * 40;
1648                let f = |off: usize| {
1649                    u32::from_le_bytes(elf[base + off..base + off + 4].try_into().unwrap())
1650                };
1651                (f(4), f(16), f(20), f(24), f(28))
1652            })
1653            .collect()
1654    }
1655
1656    /// Read symtab entries as (st_value, st_info, st_shndx).
1657    fn read_symtab(elf: &[u8]) -> (Vec<(u32, u8, u16)>, u32) {
1658        let headers = read_section_headers(elf);
1659        let &(_, off, size, _, sh_info) = headers
1660            .iter()
1661            .find(|h| h.0 == SectionType::SymTab as u32)
1662            .expect("symtab present");
1663        let syms = (0..size as usize / 16)
1664            .map(|i| {
1665                let base = off as usize + i * 16;
1666                (
1667                    u32::from_le_bytes(elf[base + 4..base + 8].try_into().unwrap()),
1668                    elf[base + 12],
1669                    u16::from_le_bytes(elf[base + 14..base + 16].try_into().unwrap()),
1670                )
1671            })
1672            .collect();
1673        (syms, sh_info)
1674    }
1675
1676    /// #656: local symbols must be emitted BEFORE globals (stable within each
1677    /// class), `.symtab` `sh_info` must be the first-non-local index, and every
1678    /// relocation's symbol index must be rewritten through the permutation.
1679    #[test]
1680    fn test_locals_sorted_first_sh_info_and_reloc_reindex_656() {
1681        let mut builder = ElfBuilder::new_arm32()
1682            .with_entry(0)
1683            .with_type(ElfType::Rel);
1684        let text = Section::new(".text", SectionType::ProgBits)
1685            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1686            .with_align(4)
1687            .with_data(vec![0u8; 16]);
1688        builder.add_section(text);
1689
1690        // Added out of ELF order: global export first, then a LOCAL internal
1691        // helper, then a global undefined external.
1692        builder.add_symbol(
1693            Symbol::new("exported")
1694                .with_value(0)
1695                .with_binding(SymbolBinding::Global)
1696                .with_type(SymbolType::Func)
1697                .with_section(4),
1698        ); // pre-build index 1
1699        builder.add_symbol(
1700            Symbol::new("func_2")
1701                .with_value(8)
1702                .with_binding(SymbolBinding::Local)
1703                .with_type(SymbolType::Func)
1704                .with_section(4),
1705        ); // pre-build index 2
1706        let undef_idx = builder.add_undefined_symbol("external"); // pre-build index 3
1707        assert_eq!(undef_idx, 3);
1708
1709        // BL at offset 0 → the LOCAL func_2 (pre-build index 2);
1710        // BL at offset 4 → the undefined external (pre-build index 3).
1711        builder.add_relocation(Relocation {
1712            offset: 0,
1713            symbol_index: 2,
1714            reloc_type: ArmRelocationType::ThmCall,
1715        });
1716        builder.add_relocation(Relocation {
1717            offset: 4,
1718            symbol_index: undef_idx,
1719            reloc_type: ArmRelocationType::ThmCall,
1720        });
1721
1722        let elf = builder.build().unwrap();
1723        let (syms, sh_info) = read_symtab(&elf);
1724
1725        // Order: null, func_2 (LOCAL), exported (GLOBAL), external (GLOBAL undef).
1726        assert_eq!(syms.len(), 4);
1727        assert_eq!(syms[0], (0, 0, 0), "null symbol first");
1728        let bind = |info: u8| info >> 4;
1729        assert_eq!(bind(syms[1].1), SymbolBinding::Local as u8, "local first");
1730        assert_eq!(syms[1].0, 8 | 1, "func_2 st_value (thumb bit)");
1731        assert_eq!(bind(syms[2].1), SymbolBinding::Global as u8);
1732        assert_eq!(syms[2].0, 1, "exported st_value 0 | thumb bit");
1733        assert_eq!(bind(syms[3].1), SymbolBinding::Global as u8);
1734        assert_eq!(syms[3].2, 0, "external is SHN_UNDEF");
1735        assert_eq!(sh_info, 2, "sh_info = index of first non-local symbol");
1736
1737        // Relocations rewritten: func_2 is now index 1, external index 3.
1738        let headers = read_section_headers(&elf);
1739        let &(_, rel_off, rel_size, _, rel_info) = headers
1740            .iter()
1741            .find(|h| h.0 == SectionType::Rel as u32)
1742            .expect(".rel.text present");
1743        assert_eq!(rel_info, 4, ".rel.text still targets .text");
1744        assert_eq!(rel_size, 16);
1745        let r_info = |i: usize| {
1746            u32::from_le_bytes(
1747                elf[rel_off as usize + i * 8 + 4..rel_off as usize + i * 8 + 8]
1748                    .try_into()
1749                    .unwrap(),
1750            )
1751        };
1752        assert_eq!(r_info(0) >> 8, 1, "BL func_2 reloc remapped to new index 1");
1753        assert_eq!(r_info(0) & 0xff, ArmRelocationType::ThmCall as u32);
1754        assert_eq!(r_info(1) >> 8, 3, "BL external reloc keeps index 3");
1755    }
1756
1757    /// #656 freeze guard: with zero LOCAL symbols the permutation is the
1758    /// identity and `sh_info` stays 1 — the pre-#656 layout, byte-identical.
1759    #[test]
1760    fn test_all_global_symtab_unchanged_sh_info_1_656() {
1761        let mut builder = ElfBuilder::new_arm32()
1762            .with_entry(0)
1763            .with_type(ElfType::Rel);
1764        builder.add_section(
1765            Section::new(".text", SectionType::ProgBits)
1766                .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1767                .with_data(vec![0u8; 8]),
1768        );
1769        for (name, val) in [("a", 0u32), ("b", 4u32)] {
1770            builder.add_symbol(
1771                Symbol::new(name)
1772                    .with_value(val)
1773                    .with_binding(SymbolBinding::Global)
1774                    .with_type(SymbolType::Func)
1775                    .with_section(4),
1776            );
1777        }
1778        let elf = builder.build().unwrap();
1779        let (syms, sh_info) = read_symtab(&elf);
1780        assert_eq!(sh_info, 1, "no locals ⇒ sh_info stays 1 (pre-#656 layout)");
1781        assert_eq!(syms[1].0, 1, "a first (insertion order preserved)");
1782        assert_eq!(syms[2].0, 5, "b second");
1783    }
1784
1785    /// #637: `.ARM.attributes` blob structure — format version 'A', "aeabi"
1786    /// vendor subsection, Tag_File subsubsection with the uleb tag pairs, and
1787    /// zero-valued tags omitted (spec default).
1788    #[test]
1789    fn test_arm_attributes_section_bytes_637() {
1790        // Cortex-M3: v7, profile M, no A32, Thumb-2.
1791        let sec = arm_attributes_section(aeabi::CPU_ARCH_V7, aeabi::PROFILE_M, 0, 2);
1792        assert_eq!(sec.name, ".ARM.attributes");
1793        assert_eq!(sec.section_type, SectionType::ArmAttributes);
1794        let d = &sec.data;
1795        assert_eq!(d[0], b'A', "format version");
1796        let vendor_len = u32::from_le_bytes(d[1..5].try_into().unwrap()) as usize;
1797        assert_eq!(vendor_len, d.len() - 1, "vendor subsection length");
1798        assert_eq!(&d[5..11], b"aeabi\0");
1799        assert_eq!(d[11], 1, "Tag_File");
1800        let file_len = u32::from_le_bytes(d[12..16].try_into().unwrap()) as usize;
1801        assert_eq!(file_len, d.len() - 11, "Tag_File length");
1802        // Attribute pairs (all values < 128 ⇒ one uleb byte each).
1803        let attrs = &d[16..];
1804        assert_eq!(
1805            attrs,
1806            &[
1807                6, 10, // Tag_CPU_arch = v7
1808                7, b'M', // Tag_CPU_arch_profile = M
1809                9, 2, // Tag_THUMB_ISA_use = Thumb-2 (Tag_ARM_ISA_use=0 omitted)
1810            ],
1811        );
1812
1813        // Cortex-R5: v7, profile R, A32 permitted, Thumb-2 permitted.
1814        let sec = arm_attributes_section(aeabi::CPU_ARCH_V7, aeabi::PROFILE_R, 1, 2);
1815        assert_eq!(&sec.data[16..], &[6, 10, 7, b'R', 8, 1, 9, 2]);
1816    }
1817}