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