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}
69
70/// Section flags
71#[derive(Debug, Clone, Copy)]
72pub struct SectionFlags(pub u32);
73
74impl SectionFlags {
75    /// Writable
76    pub const WRITE: u32 = 0x1;
77    /// Occupies memory during execution
78    pub const ALLOC: u32 = 0x2;
79    /// Executable
80    pub const EXEC: u32 = 0x4;
81    /// Mergeable
82    pub const MERGE: u32 = 0x10;
83    /// Contains null-terminated strings
84    pub const STRINGS: u32 = 0x20;
85}
86
87/// ELF section
88#[derive(Debug, Clone)]
89pub struct Section {
90    /// Section name (index into string table)
91    pub name: String,
92    /// Section type
93    pub section_type: SectionType,
94    /// Section flags
95    pub flags: u32,
96    /// Virtual address
97    pub addr: u32,
98    /// Section data
99    pub data: Vec<u8>,
100    /// Alignment
101    pub align: u32,
102    /// Explicit size (for NoBits sections like .bss where data is empty)
103    pub explicit_size: Option<u32>,
104}
105
106impl Section {
107    /// Create a new section
108    pub fn new(name: &str, section_type: SectionType) -> Self {
109        Self {
110            name: name.to_string(),
111            section_type,
112            flags: 0,
113            addr: 0,
114            data: Vec::new(),
115            align: 1,
116            explicit_size: None,
117        }
118    }
119
120    /// Set flags
121    pub fn with_flags(mut self, flags: u32) -> Self {
122        self.flags = flags;
123        self
124    }
125
126    /// Set address
127    pub fn with_addr(mut self, addr: u32) -> Self {
128        self.addr = addr;
129        self
130    }
131
132    /// Set alignment
133    pub fn with_align(mut self, align: u32) -> Self {
134        self.align = align;
135        self
136    }
137
138    /// Add data
139    pub fn with_data(mut self, data: Vec<u8>) -> Self {
140        self.data = data;
141        self
142    }
143
144    /// Set explicit size (for NoBits sections like .bss where data is empty)
145    pub fn with_size(mut self, size: u32) -> Self {
146        self.explicit_size = Some(size);
147        self
148    }
149
150    /// Get the effective size of the section
151    pub fn size(&self) -> u32 {
152        self.explicit_size.unwrap_or(self.data.len() as u32)
153    }
154}
155
156/// Symbol binding
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum SymbolBinding {
159    /// Local symbol
160    Local = 0,
161    /// Global symbol
162    Global = 1,
163    /// Weak symbol
164    Weak = 2,
165}
166
167/// Symbol type
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum SymbolType {
170    /// No type
171    NoType = 0,
172    /// Object (data)
173    Object = 1,
174    /// Function
175    Func = 2,
176    /// Section
177    Section = 3,
178    /// File name
179    File = 4,
180}
181
182/// ELF symbol
183#[derive(Debug, Clone)]
184pub struct Symbol {
185    /// Symbol name
186    pub name: String,
187    /// Value/address
188    pub value: u32,
189    /// Size
190    pub size: u32,
191    /// Binding
192    pub binding: SymbolBinding,
193    /// Type
194    pub symbol_type: SymbolType,
195    /// Section index
196    pub section: u16,
197}
198
199impl Symbol {
200    /// Create a new symbol
201    pub fn new(name: &str) -> Self {
202        Self {
203            name: name.to_string(),
204            value: 0,
205            size: 0,
206            binding: SymbolBinding::Local,
207            symbol_type: SymbolType::NoType,
208            section: 0,
209        }
210    }
211
212    /// Set value
213    pub fn with_value(mut self, value: u32) -> Self {
214        self.value = value;
215        self
216    }
217
218    /// Set size
219    pub fn with_size(mut self, size: u32) -> Self {
220        self.size = size;
221        self
222    }
223
224    /// Set binding
225    pub fn with_binding(mut self, binding: SymbolBinding) -> Self {
226        self.binding = binding;
227        self
228    }
229
230    /// Set type
231    pub fn with_type(mut self, symbol_type: SymbolType) -> Self {
232        self.symbol_type = symbol_type;
233        self
234    }
235
236    /// Set section
237    pub fn with_section(mut self, section: u16) -> Self {
238        self.section = section;
239        self
240    }
241}
242
243/// Program header type
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum ProgramType {
246    /// Null entry
247    Null = 0,
248    /// Loadable segment
249    Load = 1,
250    /// Dynamic linking info
251    Dynamic = 2,
252    /// Interpreter path
253    Interp = 3,
254    /// Note section
255    Note = 4,
256}
257
258/// Program header flags
259pub struct ProgramFlags;
260
261impl ProgramFlags {
262    /// Executable
263    pub const EXEC: u32 = 0x1;
264    /// Writable
265    pub const WRITE: u32 = 0x2;
266    /// Readable
267    pub const READ: u32 = 0x4;
268}
269
270/// ELF program header (segment)
271#[derive(Debug, Clone)]
272pub struct ProgramHeader {
273    /// Segment type
274    pub p_type: ProgramType,
275    /// Offset in file
276    pub offset: u32,
277    /// Virtual address
278    pub vaddr: u32,
279    /// Physical address
280    pub paddr: u32,
281    /// Size in file
282    pub filesz: u32,
283    /// Size in memory
284    pub memsz: u32,
285    /// Flags (R/W/X)
286    pub flags: u32,
287    /// Alignment
288    pub align: u32,
289}
290
291impl ProgramHeader {
292    /// Create a new LOAD segment
293    pub fn load(vaddr: u32, offset: u32, size: u32, flags: u32) -> Self {
294        Self {
295            p_type: ProgramType::Load,
296            offset,
297            vaddr,
298            paddr: vaddr, // Physical = virtual for simple cases
299            filesz: size,
300            memsz: size,
301            flags,
302            align: 4,
303        }
304    }
305
306    /// Create a new LOAD segment for BSS-like regions (no file data, only memory)
307    /// Used for .bss, linear memory, and other zero-initialized regions
308    pub fn load_nobits(vaddr: u32, memsz: u32, flags: u32) -> Self {
309        Self {
310            p_type: ProgramType::Load,
311            offset: 0, // No file offset for NoBits
312            vaddr,
313            paddr: vaddr, // Physical = virtual
314            filesz: 0,    // No file data
315            memsz,        // Memory size to allocate
316            flags,
317            align: 4,
318        }
319    }
320}
321
322/// ARM relocation type
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub enum ArmRelocationType {
325    /// R_ARM_THM_CALL (10) — Thumb BL/BLX instruction (Cortex-M). This is the
326    /// correct relocation for a Thumb-2 `bl` call site; `Call`/R_ARM_CALL below
327    /// is the ARM-mode form and is mis-resolved by `ld` for Thumb calls.
328    ThmCall = 10,
329    /// R_ARM_CALL (28) — BL/BLX instruction
330    Call = 28,
331    /// R_ARM_JUMP24 (29) — B/BL<cond> instruction
332    Jump24 = 29,
333    /// R_ARM_ABS32 (2) — Direct 32-bit reference
334    Abs32 = 2,
335    /// R_ARM_MOVW_ABS_NC (43) — MOVW instruction (low 16 bits)
336    MovwAbsNc = 43,
337    /// R_ARM_MOVT_ABS (44) — MOVT instruction (high 16 bits)
338    MovtAbs = 44,
339}
340
341/// A built `.rel.<name>` section: its name offset in `.shstrtab`, the target
342/// section index it relocates (`sh_info`), its file offset, and the encoded
343/// REL entries. Internal to [`ElfBuilder::build`].
344struct ExtraRelSection {
345    name_offset: usize,
346    target_idx: u32,
347    offset: usize,
348    data: Vec<u8>,
349}
350
351/// ELF relocation entry (REL format, no addend)
352#[derive(Debug, Clone)]
353pub struct Relocation {
354    /// Offset within the section where the relocation applies
355    pub offset: u32,
356    /// Symbol index in the symbol table
357    pub symbol_index: u32,
358    /// Relocation type
359    pub reloc_type: ArmRelocationType,
360}
361
362/// ARM EABI version 5 (soft-float)
363pub const EF_ARM_EABI_VER5: u32 = 0x05000000;
364/// ARM hard-float ABI flag
365pub const EF_ARM_ABI_FLOAT_HARD: u32 = 0x00000400;
366/// ARM soft-float ABI flag
367pub const EF_ARM_ABI_FLOAT_SOFT: u32 = 0x00000200;
368
369/// ELF file builder
370pub struct ElfBuilder {
371    /// File class (32 or 64 bit)
372    class: ElfClass,
373    /// Data encoding
374    data: ElfData,
375    /// File type
376    elf_type: ElfType,
377    /// Machine architecture
378    machine: ElfMachine,
379    /// Entry point address
380    entry: u32,
381    /// ELF e_flags (EABI version + float ABI)
382    e_flags: u32,
383    /// Sections
384    sections: Vec<Section>,
385    /// Symbols
386    symbols: Vec<Symbol>,
387    /// Program headers (segments)
388    program_headers: Vec<ProgramHeader>,
389    /// Relocations for .text section
390    relocations: Vec<Relocation>,
391    /// Extra per-section relocation tables, keyed by the target section's name
392    /// (e.g. `.debug_line`). Each produces a `.rel.<name>` section. Kept separate
393    /// from `relocations` (the `.text` set) so the existing `.rel.text` byte
394    /// layout is untouched: when this is empty the build is byte-identical to the
395    /// pre-generalization output (VCR-DBG-001 PR C, #394).
396    extra_relocations: Vec<(String, Vec<Relocation>)>,
397    /// #598: whether the object's functions are Thumb-encoded. Bit 0 of an
398    /// STT_FUNC `st_value` (and of `e_entry`) is the Thumb interworking bit —
399    /// it must be SET for Thumb code (Cortex-M) and CLEAR for A32 code
400    /// (cortex-r5 path). Defaults to `true` (every pre-#598 ARM object was
401    /// treated as Thumb, so Thumb outputs stay bit-identical).
402    thumb_funcs: bool,
403}
404
405impl ElfBuilder {
406    /// Create a new ELF builder for ARM32
407    pub fn new_arm32() -> Self {
408        Self {
409            class: ElfClass::Elf32,
410            data: ElfData::LittleEndian,
411            elf_type: ElfType::Exec,
412            machine: ElfMachine::Arm,
413            entry: 0,
414            e_flags: EF_ARM_EABI_VER5,
415            sections: Vec::new(),
416            symbols: Vec::new(),
417            program_headers: Vec::new(),
418            relocations: Vec::new(),
419            extra_relocations: Vec::new(),
420            thumb_funcs: true,
421        }
422    }
423
424    /// #598: mark the object's functions as A32-encoded (cortex-r5 path) —
425    /// suppresses the Thumb interworking bit on STT_FUNC `st_value`s and on
426    /// `e_entry`. Call BEFORE `with_entry`.
427    pub fn with_thumb_funcs(mut self, thumb: bool) -> Self {
428        self.thumb_funcs = thumb;
429        self
430    }
431
432    /// Set entry point
433    ///
434    /// For ARM Thumb targets, bit 0 is automatically set to indicate Thumb mode.
435    /// Cortex-M is Thumb-only, so function addresses in ELF must have bit 0 set.
436    /// A32 objects (`with_thumb_funcs(false)`, #598) keep bit 0 clear.
437    pub fn with_entry(mut self, entry: u32) -> Self {
438        self.entry = if self.machine == ElfMachine::Arm && self.thumb_funcs {
439            entry | 1 // Set Thumb bit for ARM Thumb targets
440        } else {
441            entry
442        };
443        self
444    }
445
446    /// Set ELF e_flags (e.g. to add hard-float ABI)
447    pub fn set_flags(&mut self, flags: u32) {
448        self.e_flags = flags;
449    }
450
451    /// Set file type
452    pub fn with_type(mut self, elf_type: ElfType) -> Self {
453        self.elf_type = elf_type;
454        self
455    }
456
457    /// Add a section
458    pub fn add_section(&mut self, section: Section) {
459        self.sections.push(section);
460    }
461
462    /// Add a symbol
463    pub fn add_symbol(&mut self, symbol: Symbol) {
464        self.symbols.push(symbol);
465    }
466
467    /// Add a symbol and return its 1-based index in `.symtab` (index 0 is the
468    /// reserved null symbol). Use when a later relocation must reference this
469    /// symbol — e.g. the `.text` base symbol the DWARF `.rel.debug_*` records
470    /// resolve against (VCR-DBG-001).
471    pub fn add_symbol_indexed(&mut self, symbol: Symbol) -> u32 {
472        let index = self.symbols.len() as u32 + 1;
473        self.symbols.push(symbol);
474        index
475    }
476
477    /// Add a program header (segment)
478    pub fn add_program_header(&mut self, ph: ProgramHeader) {
479        self.program_headers.push(ph);
480    }
481
482    /// Add a relocation entry for the .text section
483    pub fn add_relocation(&mut self, reloc: Relocation) {
484        self.relocations.push(reloc);
485    }
486
487    /// Add a relocation table targeting a non-`.text` section by name (e.g.
488    /// `.debug_line`). Produces a separate `.rel.<name>` section whose `sh_info`
489    /// points at the named section. The section must already have been added via
490    /// [`add_section`]; if no matching section exists at build time the table is
491    /// silently dropped. Used by VCR-DBG-001 to relocate the DWARF `.text`
492    /// references so a host linker fixes them up alongside `.text`.
493    pub fn add_section_relocations(&mut self, target_section: &str, relocs: Vec<Relocation>) {
494        if relocs.is_empty() {
495            return;
496        }
497        self.extra_relocations
498            .push((target_section.to_string(), relocs));
499    }
500
501    /// Add an undefined external symbol (e.g., __meld_dispatch_import)
502    /// Returns the symbol index (1-based, accounting for null symbol)
503    pub fn add_undefined_symbol(&mut self, name: &str) -> u32 {
504        let index = self.symbols.len() as u32 + 1; // +1 for null symbol
505        self.symbols.push(Symbol {
506            name: name.to_string(),
507            value: 0,
508            size: 0,
509            binding: SymbolBinding::Global,
510            symbol_type: SymbolType::Func,
511            section: 0, // SHN_UNDEF
512        });
513        index
514    }
515
516    /// Build the ELF file to bytes
517    pub fn build(&self) -> Result<Vec<u8>> {
518        let mut output = Vec::new();
519
520        // ELF header size (52 bytes for ELF32)
521        let header_size = 52;
522        // Program header size (32 bytes for ELF32)
523        let ph_entry_size = 32;
524        let ph_count = self.program_headers.len();
525        let ph_table_size = ph_entry_size * ph_count;
526
527        // Reserve space for ELF header + program headers
528        output.resize(header_size + ph_table_size, 0);
529
530        // Build string table for section names
531        let (shstrtab_data, section_name_offsets, extra_rel_name_offsets) =
532            self.build_section_string_table();
533
534        // Build symbol string table
535        let (strtab_data, symbol_name_offsets) = self.build_symbol_string_table();
536
537        // Calculate section offsets (after ELF header + program headers)
538        let mut current_offset = header_size + ph_table_size;
539
540        // Section 1: .shstrtab (section name string table)
541        let shstrtab_offset = current_offset;
542        current_offset += shstrtab_data.len();
543
544        // Section 2: .strtab (symbol name string table)
545        let strtab_offset = current_offset;
546        current_offset += strtab_data.len();
547
548        // User sections
549        let mut section_offsets = Vec::new();
550        for section in &self.sections {
551            section_offsets.push(current_offset);
552            current_offset += section.data.len();
553        }
554
555        // Section 3: .symtab (symbol table)
556        let symtab_offset = current_offset;
557        let symtab_data = self.build_symbol_table(&symbol_name_offsets);
558        current_offset += symtab_data.len();
559
560        // Section 4+ (optional): .rel.text (relocations)
561        let rel_data = self.build_relocation_table();
562        let rel_offset = current_offset;
563        current_offset += rel_data.len();
564
565        // Extra per-section relocation tables (.rel.<name>), laid out after
566        // .rel.text. Each entry resolves its target section index by name; a
567        // table whose target section is absent is dropped. Empty when no
568        // --debug-line ⇒ byte-identical to the pre-generalization layout.
569        let mut extra_rel: Vec<ExtraRelSection> = Vec::new();
570        for (i, (target, relocs)) in self.extra_relocations.iter().enumerate() {
571            let Some(target_idx) = self.section_index_by_name(target) else {
572                continue;
573            };
574            let data = Self::encode_rel_entries(relocs);
575            let name_offset = extra_rel_name_offsets.get(i).copied().unwrap_or(0);
576            extra_rel.push(ExtraRelSection {
577                name_offset,
578                target_idx,
579                offset: current_offset,
580                data,
581            });
582            current_offset += extra_rel.last().unwrap().data.len();
583        }
584
585        // Section header table comes at the end
586        let sh_offset = current_offset;
587
588        // Now write all the data
589        output.extend_from_slice(&shstrtab_data);
590        output.extend_from_slice(&strtab_data);
591
592        for section in &self.sections {
593            output.extend_from_slice(&section.data);
594        }
595
596        output.extend_from_slice(&symtab_data);
597        output.extend_from_slice(&rel_data);
598        for er in &extra_rel {
599            output.extend_from_slice(&er.data);
600        }
601
602        // Write section headers
603        let section_headers = self.build_section_headers_with_rel(
604            &section_name_offsets,
605            shstrtab_offset,
606            &shstrtab_data,
607            strtab_offset,
608            &strtab_data,
609            symtab_offset,
610            &symtab_data,
611            &section_offsets,
612            rel_offset,
613            &rel_data,
614            &extra_rel,
615        );
616        output.extend_from_slice(&section_headers);
617
618        // Write program headers (right after ELF header)
619        // Auto-correct p_offset for LOAD segments by matching vaddr to section addrs
620        for (i, ph) in self.program_headers.iter().enumerate() {
621            let ph_offset = header_size + i * ph_entry_size;
622            let mut corrected_ph = ph.clone();
623            if corrected_ph.filesz > 0 {
624                // Find the section whose addr matches this segment's vaddr
625                for (si, section) in self.sections.iter().enumerate() {
626                    if section.addr == corrected_ph.vaddr && si < section_offsets.len() {
627                        corrected_ph.offset = section_offsets[si] as u32;
628                        break;
629                    }
630                }
631            }
632            self.write_program_header(
633                &mut output[ph_offset..ph_offset + ph_entry_size],
634                &corrected_ph,
635            );
636        }
637
638        // Now write the actual ELF header at the beginning
639        let has_rel = !self.relocations.is_empty();
640        let num_sections = 4 + self.sections.len() + if has_rel { 1 } else { 0 } + extra_rel.len();
641        let ph_offset = if ph_count > 0 { header_size as u32 } else { 0 };
642        self.write_elf_header_with_phdrs(
643            &mut output[0..header_size],
644            ph_offset,
645            ph_count as u16,
646            sh_offset as u32,
647            num_sections as u16,
648        )?;
649
650        Ok(output)
651    }
652
653    /// Write a single program header
654    fn write_program_header(&self, output: &mut [u8], ph: &ProgramHeader) {
655        let mut cursor = 0;
656
657        // p_type (4 bytes)
658        output[cursor..cursor + 4].copy_from_slice(&(ph.p_type as u32).to_le_bytes());
659        cursor += 4;
660
661        // p_offset (4 bytes)
662        output[cursor..cursor + 4].copy_from_slice(&ph.offset.to_le_bytes());
663        cursor += 4;
664
665        // p_vaddr (4 bytes)
666        output[cursor..cursor + 4].copy_from_slice(&ph.vaddr.to_le_bytes());
667        cursor += 4;
668
669        // p_paddr (4 bytes)
670        output[cursor..cursor + 4].copy_from_slice(&ph.paddr.to_le_bytes());
671        cursor += 4;
672
673        // p_filesz (4 bytes)
674        output[cursor..cursor + 4].copy_from_slice(&ph.filesz.to_le_bytes());
675        cursor += 4;
676
677        // p_memsz (4 bytes)
678        output[cursor..cursor + 4].copy_from_slice(&ph.memsz.to_le_bytes());
679        cursor += 4;
680
681        // p_flags (4 bytes)
682        output[cursor..cursor + 4].copy_from_slice(&ph.flags.to_le_bytes());
683        cursor += 4;
684
685        // p_align (4 bytes)
686        output[cursor..cursor + 4].copy_from_slice(&ph.align.to_le_bytes());
687    }
688
689    /// Write ELF header with program header info
690    fn write_elf_header_with_phdrs(
691        &self,
692        output: &mut [u8],
693        ph_offset: u32,
694        ph_count: u16,
695        sh_offset: u32,
696        sh_count: u16,
697    ) -> Result<()> {
698        let mut cursor = 0;
699
700        // ELF magic number
701        output[cursor..cursor + 4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
702        cursor += 4;
703
704        // Class (32-bit)
705        output[cursor] = self.class as u8;
706        cursor += 1;
707
708        // Data (little-endian)
709        output[cursor] = self.data as u8;
710        cursor += 1;
711
712        // Version
713        output[cursor] = 1;
714        cursor += 1;
715
716        // OS/ABI
717        output[cursor] = 0; // System V
718        cursor += 1;
719
720        // ABI version
721        output[cursor] = 0;
722        cursor += 1;
723
724        // Padding (7 bytes)
725        output[cursor..cursor + 7].copy_from_slice(&[0; 7]);
726        cursor += 7;
727
728        // Type (little-endian u16)
729        let etype = self.elf_type as u16;
730        output[cursor..cursor + 2].copy_from_slice(&etype.to_le_bytes());
731        cursor += 2;
732
733        // Machine (little-endian u16)
734        let machine = self.machine as u16;
735        output[cursor..cursor + 2].copy_from_slice(&machine.to_le_bytes());
736        cursor += 2;
737
738        // Version (little-endian u32)
739        output[cursor..cursor + 4].copy_from_slice(&1u32.to_le_bytes());
740        cursor += 4;
741
742        // Entry point (little-endian u32)
743        output[cursor..cursor + 4].copy_from_slice(&self.entry.to_le_bytes());
744        cursor += 4;
745
746        // Program header offset (little-endian u32)
747        output[cursor..cursor + 4].copy_from_slice(&ph_offset.to_le_bytes());
748        cursor += 4;
749
750        // Section header offset (little-endian u32)
751        output[cursor..cursor + 4].copy_from_slice(&sh_offset.to_le_bytes());
752        cursor += 4;
753
754        // Flags (little-endian u32) - ARM EABI version 5 + float ABI
755        output[cursor..cursor + 4].copy_from_slice(&self.e_flags.to_le_bytes());
756        cursor += 4;
757
758        // ELF header size (little-endian u16)
759        output[cursor..cursor + 2].copy_from_slice(&52u16.to_le_bytes());
760        cursor += 2;
761
762        // Program header entry size (little-endian u16)
763        let ph_entry_size: u16 = if ph_count > 0 { 32 } else { 0 };
764        output[cursor..cursor + 2].copy_from_slice(&ph_entry_size.to_le_bytes());
765        cursor += 2;
766
767        // Program header count (little-endian u16)
768        output[cursor..cursor + 2].copy_from_slice(&ph_count.to_le_bytes());
769        cursor += 2;
770
771        // Section header entry size (little-endian u16)
772        output[cursor..cursor + 2].copy_from_slice(&40u16.to_le_bytes());
773        cursor += 2;
774
775        // Section header count (little-endian u16)
776        output[cursor..cursor + 2].copy_from_slice(&sh_count.to_le_bytes());
777        cursor += 2;
778
779        // Section header string table index (little-endian u16) - .shstrtab is section 1
780        output[cursor..cursor + 2].copy_from_slice(&1u16.to_le_bytes());
781
782        Ok(())
783    }
784
785    /// Build section name string table. Returns the bytes, the per-user-section
786    /// name offsets, and the per-extra-relocation `.rel.<name>` name offsets
787    /// (parallel to `self.extra_relocations`). The extra-rel names are appended
788    /// AFTER `.rel.text`, so when `extra_relocations` is empty the table is
789    /// byte-identical to the pre-generalization layout.
790    fn build_section_string_table(&self) -> (Vec<u8>, Vec<usize>, Vec<usize>) {
791        let mut strtab = vec![0]; // null string at offset 0
792        let mut offsets = Vec::new();
793
794        // Standard sections
795        strtab.extend_from_slice(b".shstrtab\0");
796        strtab.extend_from_slice(b".strtab\0");
797        strtab.extend_from_slice(b".symtab\0");
798
799        // User sections
800        for section in &self.sections {
801            let offset = strtab.len();
802            offsets.push(offset);
803            strtab.extend_from_slice(section.name.as_bytes());
804            strtab.push(0);
805        }
806
807        // .rel.text (if relocations exist)
808        if !self.relocations.is_empty() {
809            strtab.extend_from_slice(b".rel.text\0");
810        }
811
812        // .rel.<name> for each extra per-section relocation table.
813        let mut extra_rel_offsets = Vec::new();
814        for (target, _) in &self.extra_relocations {
815            let offset = strtab.len();
816            extra_rel_offsets.push(offset);
817            strtab.extend_from_slice(format!(".rel{target}\0").as_bytes());
818        }
819
820        (strtab, offsets, extra_rel_offsets)
821    }
822
823    /// Build symbol name string table
824    fn build_symbol_string_table(&self) -> (Vec<u8>, Vec<usize>) {
825        let mut strtab = vec![0]; // null string at offset 0
826        let mut offsets = Vec::new();
827
828        for symbol in &self.symbols {
829            let offset = strtab.len();
830            offsets.push(offset);
831            strtab.extend_from_slice(symbol.name.as_bytes());
832            strtab.push(0);
833        }
834
835        (strtab, offsets)
836    }
837
838    /// Build relocation table (ELF32 REL entries: 8 bytes each)
839    fn build_relocation_table(&self) -> Vec<u8> {
840        Self::encode_rel_entries(&self.relocations)
841    }
842
843    /// Encode a slice of relocations as ELF32 REL entries (8 bytes each). Shared
844    /// by `.rel.text` and the per-section `.rel.<name>` tables.
845    fn encode_rel_entries(relocs: &[Relocation]) -> Vec<u8> {
846        let mut rel_data = Vec::new();
847        for reloc in relocs {
848            // r_offset (4 bytes)
849            rel_data.extend_from_slice(&reloc.offset.to_le_bytes());
850            // r_info (4 bytes) = (sym_index << 8) | type
851            let r_info = (reloc.symbol_index << 8) | (reloc.reloc_type as u32);
852            rel_data.extend_from_slice(&r_info.to_le_bytes());
853        }
854        rel_data
855    }
856
857    /// Resolve a target section name to its ELF section index. User sections
858    /// begin at index 4 (null=0, shstrtab=1, strtab=2, symtab=3). Returns `None`
859    /// if no user section has that name.
860    fn section_index_by_name(&self, name: &str) -> Option<u32> {
861        self.sections
862            .iter()
863            .position(|s| s.name == name)
864            .map(|pos| 4 + pos as u32)
865    }
866
867    /// Build symbol table
868    fn build_symbol_table(&self, name_offsets: &[usize]) -> Vec<u8> {
869        let mut symtab = Vec::new();
870
871        // First entry is always null symbol
872        symtab.extend_from_slice(&[0u8; 16]); // 16 bytes per symbol in ELF32
873
874        // User symbols
875        for (i, symbol) in self.symbols.iter().enumerate() {
876            let name_offset = if i < name_offsets.len() {
877                name_offsets[i] as u32
878            } else {
879                0
880            };
881
882            // st_name (4 bytes)
883            symtab.extend_from_slice(&name_offset.to_le_bytes());
884
885            // st_value (4 bytes)
886            // For ARM THUMB targets, STT_FUNC symbols must have bit 0 set (Thumb
887            // interworking). #598: A32 objects (cortex-r5 path) must NOT set it —
888            // bit 0 on an A32 function address is wrong metadata (harnesses had
889            // to mask it; an interworking-aware consumer would mis-classify the
890            // function as Thumb).
891            let value = if self.machine == ElfMachine::Arm
892                && self.thumb_funcs
893                && symbol.symbol_type == SymbolType::Func
894            {
895                symbol.value | 1
896            } else {
897                symbol.value
898            };
899            symtab.extend_from_slice(&value.to_le_bytes());
900
901            // st_size (4 bytes)
902            symtab.extend_from_slice(&symbol.size.to_le_bytes());
903
904            // st_info (1 byte) = (binding << 4) | (type & 0xf)
905            let info = ((symbol.binding as u8) << 4) | (symbol.symbol_type as u8 & 0xf);
906            symtab.push(info);
907
908            // st_other (1 byte)
909            symtab.push(0);
910
911            // st_shndx (2 bytes)
912            symtab.extend_from_slice(&symbol.section.to_le_bytes());
913        }
914
915        symtab
916    }
917
918    /// Build section headers (with optional .rel.text)
919    #[allow(clippy::too_many_arguments)]
920    fn build_section_headers_with_rel(
921        &self,
922        section_name_offsets: &[usize],
923        shstrtab_offset: usize,
924        shstrtab_data: &[u8],
925        strtab_offset: usize,
926        strtab_data: &[u8],
927        symtab_offset: usize,
928        symtab_data: &[u8],
929        section_offsets: &[usize],
930        rel_offset: usize,
931        rel_data: &[u8],
932        extra_rel: &[ExtraRelSection],
933    ) -> Vec<u8> {
934        let mut headers = Vec::new();
935
936        // Section header size is 40 bytes for ELF32
937
938        // Section 0: null section
939        headers.extend_from_slice(&[0u8; 40]);
940
941        // Section 1: .shstrtab
942        self.write_section_header(
943            &mut headers,
944            1,
945            SectionType::StrTab as u32,
946            0,
947            0,
948            shstrtab_offset as u32,
949            shstrtab_data.len() as u32,
950            0,
951            0,
952            1,
953            0,
954        );
955
956        // Section 2: .strtab
957        let strtab_name_offset = ".shstrtab\0".len();
958        self.write_section_header(
959            &mut headers,
960            strtab_name_offset as u32,
961            SectionType::StrTab as u32,
962            0,
963            0,
964            strtab_offset as u32,
965            strtab_data.len() as u32,
966            0,
967            0,
968            1,
969            0,
970        );
971
972        // Section 3: .symtab (links to .strtab which is section 2)
973        let symtab_name_offset = ".shstrtab\0.strtab\0".len();
974        self.write_section_header(
975            &mut headers,
976            symtab_name_offset as u32,
977            SectionType::SymTab as u32,
978            0,
979            0,
980            symtab_offset as u32,
981            symtab_data.len() as u32,
982            2,
983            1,
984            4,
985            16,
986        );
987
988        // User sections
989        for (i, section) in self.sections.iter().enumerate() {
990            let name_offset = if i < section_name_offsets.len() {
991                section_name_offsets[i] as u32
992            } else {
993                0
994            };
995            let offset = if i < section_offsets.len() {
996                section_offsets[i] as u32
997            } else {
998                0
999            };
1000
1001            self.write_section_header(
1002                &mut headers,
1003                name_offset,
1004                section.section_type as u32,
1005                section.flags,
1006                section.addr,
1007                offset,
1008                section.size(),
1009                0,
1010                0,
1011                section.align,
1012                0,
1013            );
1014        }
1015
1016        // .rel.text section (if relocations exist)
1017        if !rel_data.is_empty() {
1018            let rel_name_offset = self.rel_text_shstrtab_offset();
1019            // sh_link = symtab section index (3), sh_info = .text section index (4, first user section)
1020            let text_section_idx = 4u32; // null(0) + shstrtab(1) + strtab(2) + symtab(3) + .text(4)
1021            self.write_section_header(
1022                &mut headers,
1023                rel_name_offset as u32,
1024                SectionType::Rel as u32,
1025                0,
1026                0,
1027                rel_offset as u32,
1028                rel_data.len() as u32,
1029                3,                // sh_link = .symtab section index
1030                text_section_idx, // sh_info = section to which relocations apply
1031                4,
1032                8, // Each REL entry is 8 bytes
1033            );
1034        }
1035
1036        // Extra .rel.<name> sections (e.g. .rel.debug_line). Same shape as
1037        // .rel.text but sh_info points at the named target section.
1038        for er in extra_rel {
1039            self.write_section_header(
1040                &mut headers,
1041                er.name_offset as u32,
1042                SectionType::Rel as u32,
1043                0,
1044                0,
1045                er.offset as u32,
1046                er.data.len() as u32,
1047                3,             // sh_link = .symtab section index
1048                er.target_idx, // sh_info = relocated section
1049                4,
1050                8, // Each REL entry is 8 bytes
1051            );
1052        }
1053
1054        headers
1055    }
1056
1057    /// Compute the shstrtab offset where .rel.text name begins
1058    fn rel_text_shstrtab_offset(&self) -> usize {
1059        // Layout: \0 .shstrtab\0 .strtab\0 .symtab\0 [user sections...] .rel.text\0
1060        let mut offset = 1 + ".shstrtab\0".len() + ".strtab\0".len() + ".symtab\0".len();
1061        for section in &self.sections {
1062            offset += section.name.len() + 1;
1063        }
1064        offset
1065    }
1066
1067    /// Write a single section header
1068    #[allow(clippy::too_many_arguments)]
1069    fn write_section_header(
1070        &self,
1071        output: &mut Vec<u8>,
1072        name: u32,
1073        sh_type: u32,
1074        flags: u32,
1075        addr: u32,
1076        offset: u32,
1077        size: u32,
1078        link: u32,
1079        info: u32,
1080        align: u32,
1081        entsize: u32,
1082    ) {
1083        output.extend_from_slice(&name.to_le_bytes());
1084        output.extend_from_slice(&sh_type.to_le_bytes());
1085        output.extend_from_slice(&flags.to_le_bytes());
1086        output.extend_from_slice(&addr.to_le_bytes());
1087        output.extend_from_slice(&offset.to_le_bytes());
1088        output.extend_from_slice(&size.to_le_bytes());
1089        output.extend_from_slice(&link.to_le_bytes());
1090        output.extend_from_slice(&info.to_le_bytes());
1091        output.extend_from_slice(&align.to_le_bytes());
1092        output.extend_from_slice(&entsize.to_le_bytes());
1093    }
1094
1095    /// Write ELF header (legacy method for tests)
1096    #[allow(dead_code)]
1097    fn write_elf_header(&self, output: &mut Vec<u8>) -> Result<()> {
1098        // ELF magic number
1099        output.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
1100
1101        // Class (32-bit)
1102        output.push(self.class as u8);
1103
1104        // Data (little-endian)
1105        output.push(self.data as u8);
1106
1107        // Version
1108        output.push(1);
1109
1110        // OS/ABI
1111        output.push(0); // System V
1112
1113        // ABI version
1114        output.push(0);
1115
1116        // Padding
1117        output.extend_from_slice(&[0; 7]);
1118
1119        // Type (little-endian u16)
1120        let etype = self.elf_type as u16;
1121        output.extend_from_slice(&etype.to_le_bytes());
1122
1123        // Machine (little-endian u16)
1124        let machine = self.machine as u16;
1125        output.extend_from_slice(&machine.to_le_bytes());
1126
1127        // Version (little-endian u32)
1128        output.extend_from_slice(&1u32.to_le_bytes());
1129
1130        // Entry point (little-endian u32)
1131        output.extend_from_slice(&self.entry.to_le_bytes());
1132
1133        // Program header offset (little-endian u32)
1134        output.extend_from_slice(&0u32.to_le_bytes());
1135
1136        // Section header offset (little-endian u32)
1137        output.extend_from_slice(&0u32.to_le_bytes());
1138
1139        // Flags (little-endian u32)
1140        output.extend_from_slice(&0u32.to_le_bytes());
1141
1142        // ELF header size (little-endian u16)
1143        output.extend_from_slice(&52u16.to_le_bytes());
1144
1145        // Program header entry size (little-endian u16)
1146        output.extend_from_slice(&0u16.to_le_bytes());
1147
1148        // Program header count (little-endian u16)
1149        output.extend_from_slice(&0u16.to_le_bytes());
1150
1151        // Section header entry size (little-endian u16)
1152        output.extend_from_slice(&40u16.to_le_bytes());
1153
1154        // Section header count (little-endian u16)
1155        output.extend_from_slice(&0u16.to_le_bytes());
1156
1157        // Section header string table index (little-endian u16)
1158        output.extend_from_slice(&0u16.to_le_bytes());
1159
1160        Ok(())
1161    }
1162}
1163
1164#[cfg(test)]
1165mod tests {
1166    use super::*;
1167
1168    #[test]
1169    fn test_elf_builder_creation() {
1170        let builder = ElfBuilder::new_arm32();
1171        assert_eq!(builder.class, ElfClass::Elf32);
1172        assert_eq!(builder.data, ElfData::LittleEndian);
1173        assert_eq!(builder.machine, ElfMachine::Arm);
1174    }
1175
1176    #[test]
1177    fn test_section_creation() {
1178        let section = Section::new(".text", SectionType::ProgBits)
1179            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1180            .with_addr(0x8000)
1181            .with_align(4);
1182
1183        assert_eq!(section.name, ".text");
1184        assert_eq!(section.section_type, SectionType::ProgBits);
1185        assert_eq!(section.addr, 0x8000);
1186        assert_eq!(section.align, 4);
1187    }
1188
1189    #[test]
1190    fn test_symbol_creation() {
1191        let symbol = Symbol::new("main")
1192            .with_value(0x8000)
1193            .with_size(128)
1194            .with_binding(SymbolBinding::Global)
1195            .with_type(SymbolType::Func)
1196            .with_section(1);
1197
1198        assert_eq!(symbol.name, "main");
1199        assert_eq!(symbol.value, 0x8000);
1200        assert_eq!(symbol.size, 128);
1201        assert_eq!(symbol.binding, SymbolBinding::Global);
1202        assert_eq!(symbol.symbol_type, SymbolType::Func);
1203    }
1204
1205    #[test]
1206    fn test_elf_header_generation() {
1207        let builder = ElfBuilder::new_arm32().with_entry(0x8000);
1208        let elf = builder.build().unwrap();
1209
1210        // Check magic number
1211        assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
1212
1213        // Check class (32-bit)
1214        assert_eq!(elf[4], 1);
1215
1216        // Check data (little-endian)
1217        assert_eq!(elf[5], 1);
1218
1219        // Check version
1220        assert_eq!(elf[6], 1);
1221    }
1222
1223    #[test]
1224    fn test_add_sections() {
1225        let mut builder = ElfBuilder::new_arm32();
1226
1227        let text = Section::new(".text", SectionType::ProgBits)
1228            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC);
1229
1230        let data = Section::new(".data", SectionType::ProgBits)
1231            .with_flags(SectionFlags::ALLOC | SectionFlags::WRITE);
1232
1233        builder.add_section(text);
1234        builder.add_section(data);
1235
1236        assert_eq!(builder.sections.len(), 2);
1237    }
1238
1239    #[test]
1240    fn test_add_symbols() {
1241        let mut builder = ElfBuilder::new_arm32();
1242
1243        let main_sym = Symbol::new("main")
1244            .with_binding(SymbolBinding::Global)
1245            .with_type(SymbolType::Func);
1246
1247        let data_sym = Symbol::new("data")
1248            .with_binding(SymbolBinding::Local)
1249            .with_type(SymbolType::Object);
1250
1251        builder.add_symbol(main_sym);
1252        builder.add_symbol(data_sym);
1253
1254        assert_eq!(builder.symbols.len(), 2);
1255    }
1256
1257    #[test]
1258    fn test_complete_elf_generation() {
1259        // Create a complete ELF file with sections and symbols
1260        let mut builder = ElfBuilder::new_arm32()
1261            .with_entry(0x8000)
1262            .with_type(ElfType::Exec);
1263
1264        // Add .text section with some ARM code
1265        let text_code = vec![
1266            0x00, 0x48, 0x2d, 0xe9, // push {fp, lr}
1267            0x04, 0xb0, 0x8d, 0xe2, // add fp, sp, #4
1268            0x00, 0x00, 0xa0, 0xe3, // mov r0, #0
1269            0x00, 0x88, 0xbd, 0xe8, // pop {fp, pc}
1270        ];
1271        let text = Section::new(".text", SectionType::ProgBits)
1272            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1273            .with_addr(0x8000)
1274            .with_align(4)
1275            .with_data(text_code);
1276
1277        builder.add_section(text);
1278
1279        // Add .data section
1280        let data_content = vec![0x01, 0x02, 0x03, 0x04];
1281        let data = Section::new(".data", SectionType::ProgBits)
1282            .with_flags(SectionFlags::ALLOC | SectionFlags::WRITE)
1283            .with_addr(0x8100)
1284            .with_align(4)
1285            .with_data(data_content);
1286
1287        builder.add_section(data);
1288
1289        // Add .bss section (no data)
1290        let bss = Section::new(".bss", SectionType::NoBits)
1291            .with_flags(SectionFlags::ALLOC | SectionFlags::WRITE)
1292            .with_addr(0x8200)
1293            .with_align(4);
1294
1295        builder.add_section(bss);
1296
1297        // Add symbols
1298        let main_sym = Symbol::new("main")
1299            .with_value(0x8000)
1300            .with_size(16)
1301            .with_binding(SymbolBinding::Global)
1302            .with_type(SymbolType::Func)
1303            .with_section(4); // .text is section 4 (0=null, 1=shstrtab, 2=strtab, 3=symtab, 4=.text)
1304
1305        builder.add_symbol(main_sym);
1306
1307        let data_var = Symbol::new("global_var")
1308            .with_value(0x8100)
1309            .with_size(4)
1310            .with_binding(SymbolBinding::Global)
1311            .with_type(SymbolType::Object)
1312            .with_section(5); // .data is section 5
1313
1314        builder.add_symbol(data_var);
1315
1316        // Build the ELF file
1317        let elf = builder.build().unwrap();
1318
1319        // Validate ELF header
1320        assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
1321        assert_eq!(elf[4], 1); // 32-bit
1322        assert_eq!(elf[5], 1); // little-endian
1323        assert_eq!(elf[6], 1); // version
1324
1325        // Check that we have a reasonable file size
1326        assert!(elf.len() > 52); // At least header size
1327        assert!(elf.len() < 10000); // Reasonable upper bound
1328
1329        // Validate entry point is set correctly (Thumb bit set for ARM)
1330        let entry_bytes = &elf[24..28];
1331        let entry = u32::from_le_bytes([
1332            entry_bytes[0],
1333            entry_bytes[1],
1334            entry_bytes[2],
1335            entry_bytes[3],
1336        ]);
1337        assert_eq!(entry, 0x8001); // 0x8000 | 1 (Thumb bit)
1338
1339        // Validate section header offset is non-zero
1340        let sh_off_bytes = &elf[32..36];
1341        let sh_off = u32::from_le_bytes([
1342            sh_off_bytes[0],
1343            sh_off_bytes[1],
1344            sh_off_bytes[2],
1345            sh_off_bytes[3],
1346        ]);
1347        assert!(sh_off > 0);
1348
1349        // Validate section count (null + shstrtab + strtab + symtab + .text + .data + .bss = 7)
1350        let sh_num_bytes = &elf[48..50];
1351        let sh_num = u16::from_le_bytes([sh_num_bytes[0], sh_num_bytes[1]]);
1352        assert_eq!(sh_num, 7);
1353
1354        // Validate string table index points to .shstrtab (section 1)
1355        let shstrndx_bytes = &elf[50..52];
1356        let shstrndx = u16::from_le_bytes([shstrndx_bytes[0], shstrndx_bytes[1]]);
1357        assert_eq!(shstrndx, 1);
1358    }
1359
1360    #[test]
1361    fn test_string_table_generation() {
1362        let mut builder = ElfBuilder::new_arm32();
1363
1364        builder.add_section(Section::new(".text", SectionType::ProgBits));
1365        builder.add_section(Section::new(".data", SectionType::ProgBits));
1366
1367        let (strtab, offsets, _extra_rel_offsets) = builder.build_section_string_table();
1368
1369        // Should have null byte at start
1370        assert_eq!(strtab[0], 0);
1371
1372        // Should contain .shstrtab, .strtab, .symtab, .text, .data
1373        let strtab_str = String::from_utf8_lossy(&strtab);
1374        assert!(strtab_str.contains(".shstrtab"));
1375        assert!(strtab_str.contains(".strtab"));
1376        assert!(strtab_str.contains(".symtab"));
1377        assert!(strtab_str.contains(".text"));
1378        assert!(strtab_str.contains(".data"));
1379
1380        // Should have offsets for user sections
1381        assert_eq!(offsets.len(), 2);
1382    }
1383
1384    #[test]
1385    fn test_relocation_support() {
1386        let mut builder = ElfBuilder::new_arm32()
1387            .with_entry(0x8000)
1388            .with_type(ElfType::Rel);
1389
1390        // Add .text section with a BL placeholder
1391        let text_code = vec![0x00u8; 16]; // 4 instructions of placeholder
1392        let text = Section::new(".text", SectionType::ProgBits)
1393            .with_flags(SectionFlags::ALLOC | SectionFlags::EXEC)
1394            .with_addr(0x8000)
1395            .with_align(4)
1396            .with_data(text_code);
1397        builder.add_section(text);
1398
1399        // Add undefined external symbol
1400        let sym_idx = builder.add_undefined_symbol("__meld_dispatch_import");
1401        assert!(sym_idx > 0);
1402
1403        // Add relocation for the BL at offset 4
1404        builder.add_relocation(Relocation {
1405            offset: 4,
1406            symbol_index: sym_idx,
1407            reloc_type: ArmRelocationType::Call,
1408        });
1409
1410        let elf = builder.build().unwrap();
1411
1412        // Verify ELF is valid
1413        assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
1414
1415        // Section count should include .rel.text
1416        // null(1) + shstrtab(1) + strtab(1) + symtab(1) + .text(1) + .rel.text(1) = 6
1417        let sh_num = u16::from_le_bytes([elf[48], elf[49]]);
1418        assert_eq!(sh_num, 6);
1419
1420        // Verify the symbol table contains the undefined symbol
1421        // (section = 0 for SHN_UNDEF)
1422        let has_undef = elf
1423            .windows(b"__meld_dispatch_import".len())
1424            .any(|w| w == b"__meld_dispatch_import");
1425        assert!(
1426            has_undef,
1427            "ELF should contain __meld_dispatch_import symbol name"
1428        );
1429    }
1430
1431    #[test]
1432    fn test_symbol_table_encoding() {
1433        let mut builder = ElfBuilder::new_arm32();
1434
1435        let sym = Symbol::new("test_func")
1436            .with_value(0x1000)
1437            .with_size(64)
1438            .with_binding(SymbolBinding::Global)
1439            .with_type(SymbolType::Func)
1440            .with_section(1);
1441
1442        builder.add_symbol(sym);
1443
1444        let (_strtab, offsets) = builder.build_symbol_string_table();
1445        let symtab = builder.build_symbol_table(&offsets);
1446
1447        // Should have null symbol (16 bytes) + 1 symbol (16 bytes) = 32 bytes
1448        assert_eq!(symtab.len(), 32);
1449
1450        // First symbol should be all zeros
1451        assert!(symtab[0..16].iter().all(|&b| b == 0));
1452
1453        // Second symbol should have correct encoding
1454        // Check st_value (bytes 4-7 of second entry)
1455        // For ARM STT_FUNC symbols, bit 0 is set for Thumb interworking
1456        let value_bytes = &symtab[20..24];
1457        let value = u32::from_le_bytes([
1458            value_bytes[0],
1459            value_bytes[1],
1460            value_bytes[2],
1461            value_bytes[3],
1462        ]);
1463        assert_eq!(value, 0x1001); // 0x1000 | 1 (Thumb bit)
1464
1465        // Check st_size (bytes 8-11 of second entry)
1466        let size_bytes = &symtab[24..28];
1467        let size = u32::from_le_bytes([size_bytes[0], size_bytes[1], size_bytes[2], size_bytes[3]]);
1468        assert_eq!(size, 64);
1469
1470        // Check st_info (byte 12 of second entry)
1471        let info = symtab[28];
1472        let binding = info >> 4;
1473        let sym_type = info & 0xf;
1474        assert_eq!(binding, SymbolBinding::Global as u8);
1475        assert_eq!(sym_type, SymbolType::Func as u8);
1476    }
1477
1478    /// #598: an A32 object (`with_thumb_funcs(false)`, cortex-r5 path) must
1479    /// NOT set the Thumb interworking bit on STT_FUNC symbols or `e_entry` —
1480    /// bit 0 on an A32 code address is wrong metadata (harnesses had to mask
1481    /// it). Non-func symbols never carried the bit; that stays true.
1482    #[test]
1483    fn test_a32_symbols_have_no_thumb_bit_598() {
1484        let mut builder = ElfBuilder::new_arm32().with_thumb_funcs(false);
1485
1486        let func_sym = Symbol::new("a32_func")
1487            .with_value(0x1000)
1488            .with_size(64)
1489            .with_binding(SymbolBinding::Global)
1490            .with_type(SymbolType::Func)
1491            .with_section(1);
1492        builder.add_symbol(func_sym);
1493
1494        let (_strtab, offsets) = builder.build_symbol_string_table();
1495        let symtab = builder.build_symbol_table(&offsets);
1496        let value = u32::from_le_bytes(symtab[20..24].try_into().unwrap());
1497        assert_eq!(value, 0x1000, "A32 STT_FUNC st_value must keep bit 0 clear");
1498
1499        // e_entry stays clear too (was `0 | 1` on the A32 relocatable path).
1500        let builder = ElfBuilder::new_arm32()
1501            .with_thumb_funcs(false)
1502            .with_entry(0x8000);
1503        assert_eq!(builder.entry, 0x8000, "A32 e_entry must keep bit 0 clear");
1504
1505        // The default (Thumb) behavior is unchanged: bit 0 set on both.
1506        let builder = ElfBuilder::new_arm32().with_entry(0x8000);
1507        assert_eq!(builder.entry, 0x8001, "Thumb e_entry keeps the bit");
1508    }
1509}