Skip to main content

ras/
object.rs

1//! Object file generation: ELF (Linux/BSD), Mach-O (macOS), COFF/PE (Windows).
2
3use crate::error::RasError;
4#[cfg(test)]
5use crate::parser::SectionFlags;
6use crate::parser::Section;
7use lamina_platform::{TargetArchitecture, TargetOperatingSystem};
8use std::io::Write;
9
10/// Extra sections and metadata for [`ObjectWriter`] backends.
11#[derive(Debug, Clone)]
12pub struct ObjectWriteOptions {
13    /// When true, [`ElfWriter`] also emits minimal `.debug_line`, `.debug_abbrev`, and `.debug_info`.
14    pub emit_minimal_dwarf_sections: bool,
15    /// File name stored in DWARF line/info (used when `emit_minimal_dwarf_sections` is set).
16    pub dwarf_decl_file_name: String,
17}
18
19impl Default for ObjectWriteOptions {
20    fn default() -> Self {
21        Self {
22            emit_minimal_dwarf_sections: false,
23            dwarf_decl_file_name: "lamina.s".to_string(),
24        }
25    }
26}
27
28/// A symbol with its resolved offset within its defining section, ready for the
29/// object file symbol table. Built by the assembler after two-pass resolution.
30#[derive(Debug, Clone)]
31pub struct ObjectSymbol {
32    pub name: String,
33    pub global: bool,
34    pub section: String,
35    pub value: u64,
36}
37
38/// A relocation entry for an unresolved (external) symbol reference.
39/// `offset` is the byte offset within the code section of the 4-byte displacement field.
40#[derive(Debug, Clone)]
41pub struct ExternalReloc {
42    pub offset: usize,
43    pub symbol: String,
44}
45
46/// All data needed to write a single relocatable object file.
47///
48/// Passed to [`ObjectWriter::write_object_file`] so the trait method signature
49/// stays below clippy's argument-count threshold without losing any information.
50pub struct ObjectWriteRequest<'a> {
51    pub code: &'a [u8],
52    pub sections: &'a [Section],
53    pub symbols: &'a [ObjectSymbol],
54    pub relocations: &'a [ExternalReloc],
55    pub target_arch: TargetArchitecture,
56    pub target_os: TargetOperatingSystem,
57    pub opts: &'a ObjectWriteOptions,
58}
59
60/// Backend for writing relocatable object files (ELF, Mach-O, or COFF/PE).
61pub trait ObjectWriter {
62    /// Write a relocatable object file to `path`.
63    fn write_object_file(
64        &mut self,
65        path: &std::path::Path,
66        req: &ObjectWriteRequest<'_>,
67    ) -> Result<(), RasError>;
68}
69
70const ELF64_HEADER_SIZE: usize = 64;
71const ELF64_SECTION_HEADER_SIZE: usize = 64;
72const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F'];
73const ET_REL: u16 = 1;
74const EM_X86_64: u16 = 62;
75const EM_AARCH64: u16 = 183;
76const EM_RISCV: u16 = 243;
77const EM_ARX64: u16 = 0xa064;
78const SHT_NULL: u32 = 0;
79const SHT_PROGBITS: u32 = 1;
80const SHT_SYMTAB: u32 = 2;
81const SHT_STRTAB: u32 = 3;
82const SHT_RELA: u32 = 4;
83const SHF_ALLOC: u64 = 2;
84const SHF_EXECINSTR: u64 = 4;
85const SHF_INFO_LINK: u64 = 0x40;
86const ELF64_SYM_SIZE: u64 = 24;
87const ELF64_RELA_SIZE: u64 = 24;
88const STB_LOCAL: u8 = 0;
89const STB_GLOBAL: u8 = 1;
90const STT_FUNC: u8 = 2;
91const TEXT_SECTION_INDEX: u16 = 1;
92const R_X86_64_PLT32: u64 = 4;
93const R_AARCH64_CALL26: u64 = 283;
94
95fn elf64_e_machine(arch: TargetArchitecture) -> Option<u16> {
96    match arch {
97        TargetArchitecture::X86_64 => Some(EM_X86_64),
98        TargetArchitecture::Aarch64 => Some(EM_AARCH64),
99        TargetArchitecture::Arx64 => Some(EM_ARX64),
100        TargetArchitecture::Riscv32 | TargetArchitecture::Riscv64 => Some(EM_RISCV),
101        _ => None,
102    }
103}
104
105fn push_uleb128(buf: &mut Vec<u8>, mut n: u32) {
106    loop {
107        let mut b = (n & 0x7f) as u8;
108        n >>= 7;
109        if n != 0 {
110            b |= 0x80;
111        }
112        buf.push(b);
113        if n == 0 {
114            break;
115        }
116    }
117}
118
119fn dwarf_debug_line_bytes(decl_file: &str) -> Vec<u8> {
120    let mut prog = Vec::new();
121    prog.push(0);
122    push_uleb128(&mut prog, 9);
123    prog.push(2);
124    prog.extend_from_slice(&0u64.to_le_bytes());
125    prog.push(1);
126    prog.push(0);
127    push_uleb128(&mut prog, 1);
128    prog.push(1);
129
130    let std_lens = [0u8, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1];
131    let mut body_tail = vec![1u8, 1, 1, -5i8 as u8, 14, 13];
132    body_tail.extend_from_slice(&std_lens);
133    body_tail.push(0);
134    push_uleb128(&mut body_tail, 0);
135    push_uleb128(&mut body_tail, 0);
136    push_uleb128(&mut body_tail, 0);
137    for b in decl_file.as_bytes() {
138        if *b == 0 {
139            break;
140        }
141        body_tail.push(*b);
142    }
143    body_tail.push(0);
144    body_tail.push(0);
145    body_tail.extend_from_slice(&prog);
146
147    let header_length = body_tail.len() as u32;
148    let mut unit_inner = Vec::new();
149    unit_inner.extend_from_slice(&4u16.to_le_bytes());
150    unit_inner.extend_from_slice(&header_length.to_le_bytes());
151    unit_inner.extend_from_slice(&body_tail);
152    let unit_length = unit_inner.len() as u32;
153    let mut out = Vec::new();
154    out.extend_from_slice(&unit_length.to_le_bytes());
155    out.extend_from_slice(&unit_inner);
156    out
157}
158
159fn dwarf_debug_abbrev_bytes() -> Vec<u8> {
160    vec![
161        1, 0x11, 0x00, 0x16, 0x17, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00,
162    ]
163}
164
165fn dwarf_debug_info_bytes(decl_file: &str) -> Vec<u8> {
166    let mut die = Vec::new();
167    die.push(1);
168    die.extend_from_slice(&0u32.to_le_bytes());
169    for b in decl_file.as_bytes() {
170        if *b == 0 {
171            break;
172        }
173        die.push(*b);
174    }
175    die.push(0);
176
177    let mut inner = Vec::new();
178    inner.extend_from_slice(&4u16.to_le_bytes());
179    inner.extend_from_slice(&0u32.to_le_bytes());
180    inner.push(8);
181    inner.extend_from_slice(&die);
182    let unit_length = inner.len() as u32;
183    let mut out = Vec::new();
184    out.extend_from_slice(&unit_length.to_le_bytes());
185    out.extend_from_slice(&inner);
186    out
187}
188
189fn write_elf64_header_with_shnum<W: Write>(
190    w: &mut W,
191    e_machine: u16,
192    e_shnum: u16,
193    e_shstrndx: u16,
194) -> Result<(), RasError> {
195    let mut e_ident = [0u8; 16];
196    e_ident[0..4].copy_from_slice(&ELF_MAGIC);
197    e_ident[4] = 2;
198    e_ident[5] = 1;
199    e_ident[6] = 1;
200    w.write_all(&e_ident)
201        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
202    w.write_all(&ET_REL.to_le_bytes())
203        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
204    w.write_all(&e_machine.to_le_bytes())
205        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
206    w.write_all(&1u32.to_le_bytes())
207        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
208    w.write_all(&0u64.to_le_bytes())
209        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
210    w.write_all(&0u64.to_le_bytes())
211        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
212    w.write_all(&(ELF64_HEADER_SIZE as u64).to_le_bytes())
213        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
214    w.write_all(&0u32.to_le_bytes())
215        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
216    w.write_all(&64u16.to_le_bytes())
217        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
218    w.write_all(&0u16.to_le_bytes())
219        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
220    w.write_all(&0u16.to_le_bytes())
221        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
222    w.write_all(&64u16.to_le_bytes())
223        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
224    w.write_all(&e_shnum.to_le_bytes())
225        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
226    w.write_all(&e_shstrndx.to_le_bytes())
227        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
228    Ok(())
229}
230
231fn write_elf64_section_header<W: Write>(
232    w: &mut W,
233    sh_name: u32,
234    sh_type: u32,
235    sh_flags: u64,
236    sh_offset: u64,
237    sh_size: u64,
238    sh_addralign: u64,
239) -> Result<(), RasError> {
240    w.write_all(&sh_name.to_le_bytes())
241        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
242    w.write_all(&sh_type.to_le_bytes())
243        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
244    w.write_all(&sh_flags.to_le_bytes())
245        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
246    w.write_all(&0u64.to_le_bytes())
247        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
248    w.write_all(&sh_offset.to_le_bytes())
249        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
250    w.write_all(&sh_size.to_le_bytes())
251        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
252    w.write_all(&0u32.to_le_bytes())
253        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
254    w.write_all(&0u32.to_le_bytes())
255        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
256    w.write_all(&sh_addralign.to_le_bytes())
257        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
258    w.write_all(&0u64.to_le_bytes())
259        .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
260    Ok(())
261}
262
263/// Full ELF64 section header fields. `sh_addr` is always zero for relocatable
264/// objects, so it is not exposed. Unset fields default to zero.
265#[derive(Default)]
266struct Elf64SectionHeader {
267    name: u32,
268    section_type: u32,
269    flags: u64,
270    offset: u64,
271    size: u64,
272    link: u32,
273    info: u32,
274    addralign: u64,
275    entsize: u64,
276}
277
278fn write_elf64_section_header_full<W: Write>(
279    w: &mut W,
280    header: &Elf64SectionHeader,
281) -> Result<(), RasError> {
282    let map = |e: std::io::Error| RasError::ObjectError(format!("ELF write: {}", e));
283    w.write_all(&header.name.to_le_bytes()).map_err(map)?;
284    w.write_all(&header.section_type.to_le_bytes()).map_err(map)?;
285    w.write_all(&header.flags.to_le_bytes()).map_err(map)?;
286    w.write_all(&0u64.to_le_bytes()).map_err(map)?; // sh_addr
287    w.write_all(&header.offset.to_le_bytes()).map_err(map)?;
288    w.write_all(&header.size.to_le_bytes()).map_err(map)?;
289    w.write_all(&header.link.to_le_bytes()).map_err(map)?;
290    w.write_all(&header.info.to_le_bytes()).map_err(map)?;
291    w.write_all(&header.addralign.to_le_bytes()).map_err(map)?;
292    w.write_all(&header.entsize.to_le_bytes()).map_err(map)?;
293    Ok(())
294}
295
296fn write_elf64_symbol<W: Write>(
297    w: &mut W,
298    name_offset: u32,
299    bind: u8,
300    sym_type: u8,
301    section_index: u16,
302    value: u64,
303) -> Result<(), RasError> {
304    let map = |e: std::io::Error| RasError::ObjectError(format!("ELF write: {}", e));
305    w.write_all(&name_offset.to_le_bytes()).map_err(map)?;
306    w.write_all(&[(bind << 4) | (sym_type & 0xf)]).map_err(map)?;
307    w.write_all(&[0u8]).map_err(map)?; // st_other
308    w.write_all(&section_index.to_le_bytes()).map_err(map)?;
309    w.write_all(&value.to_le_bytes()).map_err(map)?;
310    w.write_all(&0u64.to_le_bytes()).map_err(map)?; // st_size
311    Ok(())
312}
313
314/// Symbols defined in `.text` that should appear in the object symbol table.
315/// Assembler-internal labels (`.L*`) are dropped, matching gas behavior.
316/// Locals are returned before globals so the symbol table satisfies the ELF
317/// requirement that `sh_info` marks the first global symbol.
318fn linkable_text_symbols(symbols: &[ObjectSymbol]) -> (Vec<&ObjectSymbol>, Vec<&ObjectSymbol>) {
319    let mut locals = Vec::new();
320    let mut globals = Vec::new();
321    for symbol in symbols {
322        if symbol.section != ".text" || symbol.name.starts_with(".L") {
323            continue;
324        }
325        if symbol.global {
326            globals.push(symbol);
327        } else {
328            locals.push(symbol);
329        }
330    }
331    (locals, globals)
332}
333
334/// Builds `.symtab` bytes (null entry + locals + globals) and `.strtab`,
335/// also appending undefined external symbols for cross-object relocations.
336///
337/// Returns `(symtab_bytes, strtab_bytes, first_global_index, extern_base_index)`.
338///
339/// - `first_global_index`: symtab index of the first `STB_GLOBAL` entry;
340///   used as `sh_info` on the symtab section header.
341/// - `extern_base_index`: symtab index of the first `SHN_UNDEF` external entry;
342///   used when building `.rela.text` `r_sym` fields.
343fn build_symtab_with_externals(
344    symbols: &[ObjectSymbol],
345    extern_names: &[&str],
346) -> (Vec<u8>, Vec<u8>, u32, u32) {
347    let (locals, globals) = linkable_text_symbols(symbols);
348
349    let mut strtab = vec![0u8];
350    let mut symtab = vec![0u8; ELF64_SYM_SIZE as usize]; // null symbol
351
352    // Dedup extern names while preserving order
353    let mut seen = std::collections::HashSet::new();
354    let unique_externs: Vec<&str> = extern_names
355        .iter()
356        .copied()
357        .filter(|&n| seen.insert(n))
358        .collect();
359
360    // Local defined symbols
361    for sym in &locals {
362        let name_off = strtab.len() as u32;
363        strtab.extend_from_slice(sym.name.as_bytes());
364        strtab.push(0);
365        let mut e = Vec::new();
366        let _ = write_elf64_symbol(&mut e, name_off, STB_LOCAL, STT_FUNC, TEXT_SECTION_INDEX, sym.value);
367        symtab.extend_from_slice(&e);
368    }
369
370    let first_global_index = 1 + locals.len() as u32;
371
372    // Global defined symbols
373    for sym in &globals {
374        let name_off = strtab.len() as u32;
375        strtab.extend_from_slice(sym.name.as_bytes());
376        strtab.push(0);
377        let mut e = Vec::new();
378        let _ = write_elf64_symbol(&mut e, name_off, STB_GLOBAL, STT_FUNC, TEXT_SECTION_INDEX, sym.value);
379        symtab.extend_from_slice(&e);
380    }
381
382    let extern_base_index = first_global_index + globals.len() as u32;
383
384    // Undefined external symbols (STB_GLOBAL, SHN_UNDEF=0, value=0)
385    for name in &unique_externs {
386        let name_off = strtab.len() as u32;
387        strtab.extend_from_slice(name.as_bytes());
388        strtab.push(0);
389        let mut e = Vec::new();
390        let _ = write_elf64_symbol(&mut e, name_off, STB_GLOBAL, 0, 0, 0); // SHN_UNDEF = 0
391        symtab.extend_from_slice(&e);
392    }
393
394    (symtab, strtab, first_global_index, extern_base_index)
395}
396
397/// Build the raw bytes for a `.rela.text` section.
398fn build_rela_text(
399    relocations: &[ExternalReloc],
400    extern_names: &[&str],
401    extern_base_index: u32,
402    e_machine: u16,
403) -> Vec<u8> {
404    // Deduplicated extern name → symbol table index
405    let mut seen: std::collections::HashMap<&str, u32> = std::collections::HashMap::new();
406    let mut next_idx = extern_base_index;
407    for name in extern_names {
408        seen.entry(name).or_insert_with(|| {
409            let idx = next_idx;
410            next_idx += 1;
411            idx
412        });
413    }
414
415    let rel_type: u64 = match e_machine {
416        183 => R_AARCH64_CALL26, // EM_AARCH64
417        _ => R_X86_64_PLT32,
418    };
419
420    let mut out = Vec::with_capacity(relocations.len() * 24);
421    for reloc in relocations {
422        let sym_idx = *seen.get(reloc.symbol.as_str()).unwrap_or(&0) as u64;
423        let r_info = (sym_idx << 32) | rel_type;
424        out.extend_from_slice(&(reloc.offset as u64).to_le_bytes()); // r_offset
425        out.extend_from_slice(&r_info.to_le_bytes());                // r_info
426        out.extend_from_slice(&(-4i64).to_le_bytes());               // r_addend = -4
427    }
428    out
429}
430
431pub struct ElfWriter;
432
433impl Default for ElfWriter {
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439impl ElfWriter {
440    pub fn new() -> Self {
441        Self
442    }
443}
444
445impl ObjectWriter for ElfWriter {
446    fn write_object_file(
447        &mut self,
448        path: &std::path::Path,
449        req: &ObjectWriteRequest<'_>,
450    ) -> Result<(), RasError> {
451        let code = req.code;
452        let symbols = req.symbols;
453        let relocations = req.relocations;
454        let opts = req.opts;
455        let e_machine = elf64_e_machine(req.target_arch).ok_or_else(|| {
456            RasError::ObjectError(format!("ELF does not support arch: {:?}", req.target_arch))
457        })?;
458
459        let mut f = std::fs::File::create(path)
460            .map_err(|e| RasError::ObjectError(format!("Failed to create ELF file: {}", e)))?;
461
462        if opts.emit_minimal_dwarf_sections {
463            const SHSTRTAB_DWARF: &[u8] =
464                b"\0.text\0.debug_line\0.debug_abbrev\0.debug_info\0.shstrtab\0";
465            let line_b = dwarf_debug_line_bytes(opts.dwarf_decl_file_name.as_str());
466            let abbrev_b = dwarf_debug_abbrev_bytes();
467            let info_b = dwarf_debug_info_bytes(opts.dwarf_decl_file_name.as_str());
468
469            let n_sec = 6u16;
470            let shoff = ELF64_HEADER_SIZE as u64;
471            let first_data = shoff + u64::from(n_sec) * ELF64_SECTION_HEADER_SIZE as u64;
472            let text_align = 16u64;
473            let text_size_aligned = (code.len() as u64 + text_align - 1) & !(text_align - 1);
474            let mut cur = first_data + text_size_aligned;
475            let line_off = cur;
476            cur += line_b.len() as u64;
477            let abbrev_off = cur;
478            cur += abbrev_b.len() as u64;
479            let info_off = cur;
480            cur += info_b.len() as u64;
481            let shstrtab_off = cur;
482            let shstrtab_size = SHSTRTAB_DWARF.len() as u64;
483
484            write_elf64_header_with_shnum(&mut f, e_machine, n_sec, 5)?;
485            write_elf64_section_header(&mut f, 0, SHT_NULL, 0, 0, 0, 0)?;
486            write_elf64_section_header(
487                &mut f,
488                1,
489                SHT_PROGBITS,
490                SHF_ALLOC | SHF_EXECINSTR,
491                first_data,
492                code.len() as u64,
493                text_align,
494            )?;
495            write_elf64_section_header(
496                &mut f,
497                7,
498                SHT_PROGBITS,
499                0,
500                line_off,
501                line_b.len() as u64,
502                1,
503            )?;
504            write_elf64_section_header(
505                &mut f,
506                19,
507                SHT_PROGBITS,
508                0,
509                abbrev_off,
510                abbrev_b.len() as u64,
511                1,
512            )?;
513            write_elf64_section_header(
514                &mut f,
515                33,
516                SHT_PROGBITS,
517                0,
518                info_off,
519                info_b.len() as u64,
520                1,
521            )?;
522            write_elf64_section_header(&mut f, 45, SHT_STRTAB, 0, shstrtab_off, shstrtab_size, 1)?;
523
524            let pad = text_size_aligned as usize - code.len();
525            f.write_all(code)
526                .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
527            if pad > 0 {
528                f.write_all(&vec![0u8; pad])
529                    .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
530            }
531            f.write_all(&line_b)
532                .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
533            f.write_all(&abbrev_b)
534                .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
535            f.write_all(&info_b)
536                .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
537            f.write_all(SHSTRTAB_DWARF)
538                .map_err(|e| RasError::ObjectError(format!("ELF write: {}", e)))?;
539            return Ok(());
540        }
541
542        // Build symtab including undefined external symbols (so weld/ld can resolve them).
543        let extern_names: Vec<&str> = relocations.iter().map(|r| r.symbol.as_str()).collect();
544        let (symtab_bytes, strtab_bytes, first_global_index, extern_sym_base) =
545            build_symtab_with_externals(symbols, &extern_names);
546
547        // Build .rela.text entries for each external relocation.
548        // R_X86_64_PLT32 / R_AARCH64_CALL26 with addend = -4.
549        let rela_bytes = if !relocations.is_empty() {
550            build_rela_text(relocations, &extern_names, extern_sym_base, e_machine)
551        } else {
552            Vec::new()
553        };
554        let has_rela = !rela_bytes.is_empty();
555
556        // Section layout:
557        //   Without rela: [0] NULL  [1] .text  [2] .symtab  [3] .strtab  [4] .shstrtab
558        //   With rela:    [0] NULL  [1] .text  [2] .rela.text  [3] .symtab  [4] .strtab  [5] .shstrtab
559        const SHSTRTAB_NO_RELA: &[u8] = b"\0.text\0.symtab\0.strtab\0.shstrtab\0";
560        const SHSTRTAB_RELA: &[u8] =
561            b"\0.text\0.rela.text\0.symtab\0.strtab\0.shstrtab\0";
562
563        let (shstrtab, name_text, name_rela_text, name_symtab, name_strtab, name_shstrtab,
564             section_count, shstrtab_index, symtab_section_index, strtab_section_index) =
565        if has_rela {
566            (SHSTRTAB_RELA,
567             1u32, 7u32, 18u32, 26u32, 34u32,
568             6u16, 5u16, 3u32, 4u32)
569        } else {
570            (SHSTRTAB_NO_RELA,
571             1u32, 0u32, 7u32, 15u32, 23u32,
572             5u16, 4u16, 2u32, 3u32)
573        };
574
575        let shoff = ELF64_HEADER_SIZE as u64;
576        let text_offset = shoff + u64::from(section_count) * ELF64_SECTION_HEADER_SIZE as u64;
577        let text_align = 16u64;
578        let text_size_aligned = (code.len() as u64 + text_align - 1) & !(text_align - 1);
579        let rela_offset = text_offset + text_size_aligned;
580        let rela_size = rela_bytes.len() as u64;
581        let symtab_offset = rela_offset + rela_size;
582        let strtab_offset = symtab_offset + symtab_bytes.len() as u64;
583        let shstrtab_offset = strtab_offset + strtab_bytes.len() as u64;
584
585        write_elf64_header_with_shnum(&mut f, e_machine, section_count, shstrtab_index)?;
586        // [0] NULL
587        write_elf64_section_header(&mut f, 0, SHT_NULL, 0, 0, 0, 0)?;
588        // [1] .text
589        write_elf64_section_header(
590            &mut f,
591            name_text,
592            SHT_PROGBITS,
593            SHF_ALLOC | SHF_EXECINSTR,
594            text_offset,
595            code.len() as u64,
596            text_align,
597        )?;
598        // [2] .rela.text (only when needed)
599        if has_rela {
600            write_elf64_section_header_full(
601                &mut f,
602                &Elf64SectionHeader {
603                    name: name_rela_text,
604                    section_type: SHT_RELA,
605                    flags: SHF_INFO_LINK,
606                    offset: rela_offset,
607                    size: rela_size,
608                    link: symtab_section_index,    // points to .symtab
609                    info: TEXT_SECTION_INDEX as u32, // relocates .text
610                    addralign: 8,
611                    entsize: ELF64_RELA_SIZE,
612                },
613            )?;
614        }
615        // .symtab
616        write_elf64_section_header_full(
617            &mut f,
618            &Elf64SectionHeader {
619                name: name_symtab,
620                section_type: SHT_SYMTAB,
621                offset: symtab_offset,
622                size: symtab_bytes.len() as u64,
623                link: strtab_section_index,
624                info: first_global_index,
625                addralign: 8,
626                entsize: ELF64_SYM_SIZE,
627                ..Default::default()
628            },
629        )?;
630        // .strtab
631        write_elf64_section_header_full(
632            &mut f,
633            &Elf64SectionHeader {
634                name: name_strtab,
635                section_type: SHT_STRTAB,
636                offset: strtab_offset,
637                size: strtab_bytes.len() as u64,
638                addralign: 1,
639                ..Default::default()
640            },
641        )?;
642        // .shstrtab
643        write_elf64_section_header_full(
644            &mut f,
645            &Elf64SectionHeader {
646                name: name_shstrtab,
647                section_type: SHT_STRTAB,
648                offset: shstrtab_offset,
649                size: shstrtab.len() as u64,
650                addralign: 1,
651                ..Default::default()
652            },
653        )?;
654
655        let map = |e: std::io::Error| RasError::ObjectError(format!("ELF write: {}", e));
656        let padding = text_size_aligned as usize - code.len();
657        f.write_all(code).map_err(map)?;
658        if padding > 0 {
659            f.write_all(&vec![0u8; padding]).map_err(map)?;
660        }
661        if has_rela {
662            f.write_all(&rela_bytes).map_err(map)?;
663        }
664        f.write_all(&symtab_bytes).map_err(map)?;
665        f.write_all(&strtab_bytes).map_err(map)?;
666        f.write_all(shstrtab).map_err(map)?;
667
668        Ok(())
669    }
670}
671
672pub struct MachOWriter;
673
674impl Default for MachOWriter {
675    fn default() -> Self {
676        Self::new()
677    }
678}
679
680impl MachOWriter {
681    pub fn new() -> Self {
682        Self
683    }
684}
685
686impl ObjectWriter for MachOWriter {
687    fn write_object_file(
688        &mut self,
689        _path: &std::path::Path,
690        _req: &ObjectWriteRequest<'_>,
691    ) -> Result<(), RasError> {
692        Err(RasError::ObjectError(
693            "Mach-O object file generation not yet implemented".to_string(),
694        ))
695    }
696}
697
698pub struct CoffWriter;
699
700impl Default for CoffWriter {
701    fn default() -> Self {
702        Self::new()
703    }
704}
705
706impl CoffWriter {
707    pub fn new() -> Self {
708        Self
709    }
710}
711
712const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
713const IMAGE_SCN_CNT_CODE: u32 = 0x0000_0020;
714const IMAGE_SCN_ALIGN_16BYTES: u32 = 0x0050_0000;
715const IMAGE_SCN_MEM_EXECUTE: u32 = 0x2000_0000;
716const IMAGE_SCN_MEM_READ: u32 = 0x4000_0000;
717const IMAGE_SYM_CLASS_STATIC: u8 = 3;
718
719const IMAGE_REL_AMD64_REL32: u16 = 4;
720
721impl ObjectWriter for CoffWriter {
722    fn write_object_file(
723        &mut self,
724        path: &std::path::Path,
725        req: &ObjectWriteRequest<'_>,
726    ) -> Result<(), RasError> {
727        let code = req.code;
728        let symbols = req.symbols;
729        let relocations = req.relocations;
730        if req.target_arch != TargetArchitecture::X86_64 {
731            return Err(RasError::ObjectError(format!(
732                "COFF writer: unsupported architecture {:?}",
733                req.target_arch
734            )));
735        }
736
737        // Collect unique external symbols referenced by relocations.
738        let mut extern_names: Vec<String> = Vec::new();
739        for r in relocations {
740            if !extern_names.contains(&r.symbol) {
741                extern_names.push(r.symbol.clone());
742            }
743        }
744
745        // Collect global exported functions (defined in this object).
746        let exported: Vec<&ObjectSymbol> = symbols
747            .iter()
748            .filter(|s| s.global && !s.name.starts_with(".L"))
749            .collect();
750
751        // Build symbol table and string table.
752        // Symbol index layout: 0 = .text section, 1..=exported, then extern symbols.
753        let mut strtab: Vec<u8> = vec![0u8; 4]; // size placeholder
754        let mut sym_bytes: Vec<u8> = Vec::new();
755
756        let push_sym = |sym_bytes: &mut Vec<u8>, strtab: &mut Vec<u8>,
757                             name: &str, value: u32, section: i16,
758                             ty: u16, storage: u8| {
759            let mut entry = [0u8; 18];
760            let nb = name.as_bytes();
761            if nb.len() <= 8 {
762                entry[..nb.len()].copy_from_slice(nb);
763            } else {
764                let off = strtab.len() as u32;
765                entry[4..8].copy_from_slice(&off.to_le_bytes());
766                strtab.extend_from_slice(nb);
767                strtab.push(0);
768            }
769            entry[8..12].copy_from_slice(&value.to_le_bytes());
770            entry[12..14].copy_from_slice(&section.to_le_bytes());
771            entry[14..16].copy_from_slice(&ty.to_le_bytes());
772            entry[16] = storage;
773            sym_bytes.extend_from_slice(&entry);
774        };
775
776        // Index 0: .text section symbol
777        push_sym(&mut sym_bytes, &mut strtab, ".text", 0, 1, 0, IMAGE_SYM_CLASS_STATIC);
778        // Indices 1..=exported: defined global functions
779        for sym in &exported {
780            push_sym(&mut sym_bytes, &mut strtab, &sym.name,
781                sym.value as u32, 1, 0x20, 2 /* EXTERNAL */);
782        }
783        // Indices after exported: undefined external symbols (printf, etc.)
784        let extern_base_idx = 1 + exported.len(); // 0-based index of first extern sym
785        for name in &extern_names {
786            push_sym(&mut sym_bytes, &mut strtab, name, 0, 0, 0x20, 2);
787        }
788
789        let strtab_size = strtab.len() as u32;
790        strtab[0..4].copy_from_slice(&strtab_size.to_le_bytes());
791        let total_syms = (1 + exported.len() + extern_names.len()) as u32;
792
793        // Build COFF relocation entries (10 bytes each).
794        let mut reloc_bytes: Vec<u8> = Vec::new();
795        for r in relocations {
796            let sym_idx = extern_base_idx
797                + extern_names.iter().position(|n| *n == r.symbol).unwrap_or(0);
798            reloc_bytes.extend_from_slice(&(r.offset as u32).to_le_bytes()); // VirtualAddress
799            reloc_bytes.extend_from_slice(&(sym_idx as u32).to_le_bytes());  // SymbolTableIndex
800            reloc_bytes.extend_from_slice(&IMAGE_REL_AMD64_REL32.to_le_bytes()); // Type
801        }
802        let n_relocs = relocations.len() as u16;
803
804        let hdr_sz = 20usize;
805        let sec_hdr_sz = 40usize;
806        let align = 16usize;
807        let padded_len = (code.len().div_ceil(align) * align) as u32;
808        let raw_data_off = hdr_sz + sec_hdr_sz;
809        let reloc_off = raw_data_off + padded_len as usize;
810        let sym_off = reloc_off + reloc_bytes.len();
811
812        let text_name = b".text\0\0\0";
813        let sec_flags = IMAGE_SCN_CNT_CODE
814            | IMAGE_SCN_ALIGN_16BYTES
815            | IMAGE_SCN_MEM_EXECUTE
816            | IMAGE_SCN_MEM_READ;
817
818        let map = |e: std::io::Error| RasError::ObjectError(format!("COFF write: {}", e));
819        let mut f = std::fs::File::create(path)
820            .map_err(|e| RasError::ObjectError(format!("Failed to create COFF file: {}", e)))?;
821
822        // COFF file header (20 bytes)
823        f.write_all(&IMAGE_FILE_MACHINE_AMD64.to_le_bytes()).map_err(map)?;
824        f.write_all(&1u16.to_le_bytes()).map_err(map)?;
825        f.write_all(&0u32.to_le_bytes()).map_err(map)?;
826        f.write_all(&(sym_off as u32).to_le_bytes()).map_err(map)?;
827        f.write_all(&total_syms.to_le_bytes()).map_err(map)?;
828        f.write_all(&0u16.to_le_bytes()).map_err(map)?;
829        f.write_all(&0u16.to_le_bytes()).map_err(map)?;
830
831        // .text section header (40 bytes)
832        f.write_all(text_name).map_err(map)?;
833        f.write_all(&0u32.to_le_bytes()).map_err(map)?;          // VirtualSize
834        f.write_all(&0u32.to_le_bytes()).map_err(map)?;          // VirtualAddress
835        f.write_all(&padded_len.to_le_bytes()).map_err(map)?;    // SizeOfRawData
836        f.write_all(&(raw_data_off as u32).to_le_bytes()).map_err(map)?; // PointerToRawData
837        if n_relocs > 0 {
838            f.write_all(&(reloc_off as u32).to_le_bytes()).map_err(map)?; // PointerToRelocations
839        } else {
840            f.write_all(&0u32.to_le_bytes()).map_err(map)?;
841        }
842        f.write_all(&0u32.to_le_bytes()).map_err(map)?;          // PointerToLinenumbers
843        f.write_all(&n_relocs.to_le_bytes()).map_err(map)?;      // NumberOfRelocations
844        f.write_all(&0u16.to_le_bytes()).map_err(map)?;          // NumberOfLinenumbers
845        f.write_all(&sec_flags.to_le_bytes()).map_err(map)?;     // Characteristics
846
847        // Raw data
848        f.write_all(code).map_err(map)?;
849        let pad = padded_len as usize - code.len();
850        if pad > 0 {
851            f.write_all(&vec![0u8; pad]).map_err(map)?;
852        }
853
854        // Relocation table
855        if !reloc_bytes.is_empty() {
856            f.write_all(&reloc_bytes).map_err(map)?;
857        }
858
859        // Symbol table + string table
860        f.write_all(&sym_bytes).map_err(map)?;
861        f.write_all(&strtab).map_err(map)?;
862
863        Ok(())
864    }
865}
866
867/// Returns the object writer for `target_os` (ELF, Mach-O, or COFF).
868///
869/// This is the single dispatch point used by [`crate::assembler::RasAssembler`];
870/// add new OS families here instead of duplicating `match` arms.
871pub fn object_writer_for_os(
872    target_os: TargetOperatingSystem,
873) -> Result<Box<dyn ObjectWriter>, RasError> {
874    match target_os {
875        TargetOperatingSystem::Linux
876        | TargetOperatingSystem::FreeBSD
877        | TargetOperatingSystem::OpenBSD
878        | TargetOperatingSystem::NetBSD
879        | TargetOperatingSystem::DragonFly
880        | TargetOperatingSystem::Redox => Ok(Box::new(ElfWriter::new())),
881        TargetOperatingSystem::MacOS => Ok(Box::new(MachOWriter::new())),
882        TargetOperatingSystem::Windows => Ok(Box::new(CoffWriter::new())),
883        _ => Err(RasError::UnsupportedTarget(format!(
884            "No object file format is mapped for operating system {:?}",
885            target_os
886        ))),
887    }
888}
889
890#[cfg(test)]
891fn build_symtab_and_strtab(symbols: &[ObjectSymbol]) -> (Vec<u8>, Vec<u8>, u32) {
892    let (symtab, strtab, first_global, _) = build_symtab_with_externals(symbols, &[]);
893    (symtab, strtab, first_global)
894}
895
896#[cfg(test)]
897mod tests {
898    use super::*;
899
900    #[test]
901    fn symtab_includes_globals_and_skips_local_temporaries() {
902        let symbols = vec![
903            ObjectSymbol {
904                name: "main".to_string(),
905                global: true,
906                section: ".text".to_string(),
907                value: 0,
908            },
909            ObjectSymbol {
910                name: ".L_main_entry".to_string(),
911                global: false,
912                section: ".text".to_string(),
913                value: 16,
914            },
915            ObjectSymbol {
916                name: "helper".to_string(),
917                global: false,
918                section: ".text".to_string(),
919                value: 32,
920            },
921        ];
922
923        let (symtab, strtab, first_global) = build_symtab_and_strtab(&symbols);
924
925        // Null entry + one local (helper) + one global (main); `.L` is dropped.
926        assert_eq!(symtab.len(), 3 * ELF64_SYM_SIZE as usize);
927        assert_eq!(first_global, 2);
928        assert!(strtab.windows(4).any(|w| w == b"main"));
929        assert!(strtab.windows(6).any(|w| w == b"helper"));
930        assert!(!strtab.windows(2).any(|w| w == b".L"));
931    }
932
933    #[test]
934    fn symtab_drops_symbols_outside_text() {
935        let symbols = vec![ObjectSymbol {
936            name: "data_label".to_string(),
937            global: true,
938            section: ".data".to_string(),
939            value: 0,
940        }];
941
942        let (symtab, _strtab, first_global) = build_symtab_and_strtab(&symbols);
943
944        assert_eq!(symtab.len(), ELF64_SYM_SIZE as usize);
945        assert_eq!(first_global, 1);
946    }
947
948    #[test]
949    fn test_elf64_x86_64_write() {
950        let mut w = ElfWriter::new();
951        let code = [0xc3u8];
952        let sections = vec![Section {
953            name: ".text".to_string(),
954            flags: SectionFlags {
955                alloc: true,
956                exec: true,
957                write: false,
958            },
959        }];
960        let symbols: Vec<ObjectSymbol> = vec![];
961        let path = std::env::temp_dir().join("ras_elf_test_x86_64.o");
962        let opts = ObjectWriteOptions::default();
963        let result = w.write_object_file(&path, &ObjectWriteRequest {
964            code: &code, sections: &sections, symbols: &symbols, relocations: &[],
965            target_arch: TargetArchitecture::X86_64,
966            target_os: TargetOperatingSystem::Linux,
967            opts: &opts,
968        });
969        let _ = std::fs::remove_file(&path);
970        result.expect("ELF write should succeed");
971    }
972
973    #[test]
974    fn test_elf64_aarch64_write() {
975        let mut w = ElfWriter::new();
976        let code = [0xc0, 0x03, 0x5f, 0xd6];
977        let sections = vec![Section {
978            name: ".text".to_string(),
979            flags: SectionFlags { alloc: true, exec: true, write: false },
980        }];
981        let symbols: Vec<ObjectSymbol> = vec![];
982        let path = std::env::temp_dir().join("ras_elf_test_aarch64.o");
983        let opts = ObjectWriteOptions::default();
984        let result = w.write_object_file(&path, &ObjectWriteRequest {
985            code: &code, sections: &sections, symbols: &symbols, relocations: &[],
986            target_arch: TargetArchitecture::Aarch64,
987            target_os: TargetOperatingSystem::Linux,
988            opts: &opts,
989        });
990        let _ = std::fs::remove_file(&path);
991        result.expect("ELF write should succeed");
992    }
993
994    #[test]
995    fn object_writer_for_os_elf_and_coff_paths() {
996        assert!(object_writer_for_os(TargetOperatingSystem::Linux).is_ok());
997        assert!(object_writer_for_os(TargetOperatingSystem::MacOS).is_ok());
998        assert!(object_writer_for_os(TargetOperatingSystem::Windows).is_ok());
999    }
1000
1001    #[test]
1002    fn test_elf64_has_valid_magic() {
1003        let mut w = ElfWriter::new();
1004        let code = [0xc3u8];
1005        let path = std::env::temp_dir().join("ras_elf_magic_test.o");
1006        let opts = ObjectWriteOptions::default();
1007        w.write_object_file(&path, &ObjectWriteRequest {
1008            code: &code, sections: &[], symbols: &[], relocations: &[],
1009            target_arch: TargetArchitecture::X86_64,
1010            target_os: TargetOperatingSystem::Linux,
1011            opts: &opts,
1012        }).expect("write");
1013        let buf = std::fs::read(&path).expect("read");
1014        let _ = std::fs::remove_file(&path);
1015        assert!(buf.len() >= 64);
1016        assert_eq!(&buf[0..4], &[0x7f, b'E', b'L', b'F']);
1017    }
1018
1019    #[test]
1020    fn test_elf64_emits_debug_sections_when_requested() {
1021        let mut w = ElfWriter::new();
1022        let code = [0xc3u8];
1023        let path = std::env::temp_dir().join("ras_elf_dwarf_test.o");
1024        let opts = ObjectWriteOptions {
1025            emit_minimal_dwarf_sections: true,
1026            dwarf_decl_file_name: "t.s".to_string(),
1027        };
1028        w.write_object_file(&path, &ObjectWriteRequest {
1029            code: &code, sections: &[], symbols: &[], relocations: &[],
1030            target_arch: TargetArchitecture::X86_64,
1031            target_os: TargetOperatingSystem::Linux,
1032            opts: &opts,
1033        }).expect("write");
1034        let buf = std::fs::read(&path).expect("read");
1035        let _ = std::fs::remove_file(&path);
1036        assert!(
1037            buf.windows(11).any(|w| w == b".debug_line"),
1038            "expected .debug_line in shstrtab"
1039        );
1040    }
1041
1042    #[test]
1043    fn test_coff_amd64_write() {
1044        let mut w = CoffWriter::new();
1045        let code = [0xc3u8];
1046        let path = std::env::temp_dir().join("ras_coff_test.obj");
1047        let opts = ObjectWriteOptions::default();
1048        w.write_object_file(&path, &ObjectWriteRequest {
1049            code: &code, sections: &[], symbols: &[], relocations: &[],
1050            target_arch: TargetArchitecture::X86_64,
1051            target_os: TargetOperatingSystem::Windows,
1052            opts: &opts,
1053        }).expect("coff write");
1054        let buf = std::fs::read(&path).expect("read");
1055        let _ = std::fs::remove_file(&path);
1056        assert_eq!(&buf[0..2], &IMAGE_FILE_MACHINE_AMD64.to_le_bytes());
1057    }
1058}