Skip to main content

llvm_codegen/
emit.rs

1//! Object-file emission.
2//!
3//! Produces a minimal ELF-64, Mach-O 64-bit, or COFF relocatable object file
4//! containing a single `.text` section.
5//! The actual byte encoding is supplied by the target via the [`Emitter`] trait.
6
7// ── object-file model ──────────────────────────────────────────────────────
8
9/// Supported object-file formats.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum ObjectFormat {
12    /// `Elf` variant.
13    Elf,
14    /// `MachO` variant.
15    MachO,
16    /// `Coff` variant.
17    Coff,
18}
19
20/// Kind of relocation record.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum RelocKind {
23    /// 32-bit PC-relative addend (e.g. near call / branch).
24    Pc32,
25    /// 64-bit absolute address.
26    Abs64,
27}
28
29/// A single relocation record.
30#[derive(Clone, Debug)]
31pub struct Reloc {
32    /// Byte offset within the section data.
33    pub offset: u64,
34    /// Index into `ObjectFile::symbols` for the referenced symbol.
35    pub symbol: usize,
36    /// Public API for `kind`.
37    pub kind: RelocKind,
38    /// Addend (ELF RELA / Mach-O addend).
39    pub addend: i64,
40}
41
42/// A single source mapping row for debug line table emission.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct DebugLineRow {
45    /// Public API for `address`.
46    pub address: u64,
47    /// Public API for `line`.
48    pub line: u32,
49    /// Public API for `column`.
50    pub column: u32,
51}
52
53/// A named output section (`.text`, `__TEXT,__text`, etc.).
54#[derive(Clone, Debug)]
55pub struct Section {
56    /// Public API for `name`.
57    pub name: String,
58    /// Public API for `data`.
59    pub data: Vec<u8>,
60    /// Public API for `relocs`.
61    pub relocs: Vec<Reloc>,
62    /// Address->source rows collected while encoding this section.
63    pub debug_rows: Vec<DebugLineRow>,
64}
65
66/// A symbol definition.
67#[derive(Clone, Debug)]
68pub struct Symbol {
69    /// Public API for `name`.
70    pub name: String,
71    /// Index of the section this symbol lives in.
72    pub section: usize,
73    /// Byte offset within that section.
74    pub offset: u64,
75    /// Public API for `size`.
76    pub size: u64,
77    /// Public API for `global`.
78    pub global: bool,
79}
80
81/// Assembled object file ready to be written to disk or passed to a linker.
82#[derive(Clone, Debug)]
83pub struct ObjectFile {
84    /// Public API for `format`.
85    pub format: ObjectFormat,
86    /// ELF e_machine value when `format == ObjectFormat::Elf`.
87    /// Ignored for Mach-O.
88    pub elf_machine: u16,
89    /// COFF `Machine` field when `format == ObjectFormat::Coff`.
90    pub coff_machine: u16,
91    /// Public API for `sections`.
92    pub sections: Vec<Section>,
93    /// Public API for `symbols`.
94    pub symbols: Vec<Symbol>,
95}
96
97impl ObjectFile {
98    /// Serialize the object file to raw bytes.
99    pub fn to_bytes(&self) -> Vec<u8> {
100        match self.format {
101            ObjectFormat::Elf => serialize_elf(self),
102            ObjectFormat::MachO => serialize_macho(self),
103            ObjectFormat::Coff => serialize_coff(self),
104        }
105    }
106}
107
108// ── Emitter trait ──────────────────────────────────────────────────────────
109
110use crate::isel::{MachineFunction, PReg};
111
112/// Implemented by each target to encode machine instructions into bytes.
113pub trait Emitter {
114    /// Encode `mf` and return a [`Section`] containing the machine code.
115    fn emit_function(&mut self, mf: &MachineFunction) -> Section;
116
117    /// The object format this emitter targets.
118    fn object_format(&self) -> ObjectFormat;
119
120    /// ELF `e_machine` field for this target.
121    fn elf_machine(&self) -> u16 {
122        62 // EM_X86_64
123    }
124
125    /// COFF `Machine` field for this target.
126    fn coff_machine(&self) -> u16 {
127        0x8664 // IMAGE_FILE_MACHINE_AMD64
128    }
129}
130
131/// Build a complete [`ObjectFile`] from a [`MachineFunction`] using `emitter`.
132pub fn emit_object(mf: &MachineFunction, emitter: &mut dyn Emitter) -> ObjectFile {
133    let section = emitter.emit_function(mf);
134    let size = section.data.len() as u64;
135    let sym = Symbol {
136        name: mf.name.clone(),
137        section: 0,
138        offset: 0,
139        size,
140        global: true,
141    };
142    let mut sections = vec![section];
143
144    // Always emit baseline unwind metadata for supported object families.
145    // This enables stack unwinding infrastructure to discover frame ranges
146    // even when full per-instruction CFI richness is still evolving.
147    match emitter.object_format() {
148        ObjectFormat::Elf => {
149            sections.push(Section {
150                name: ".eh_frame".into(),
151                data: build_eh_frame(size, mf.frame_size, &mf.used_callee_saved),
152                relocs: Vec::new(),
153                debug_rows: Vec::new(),
154            });
155        }
156        ObjectFormat::Coff => {
157            let (xdata, pdata) = build_coff_unwind_tables(size, mf.frame_size, &mf.used_callee_saved);
158            sections.push(Section {
159                name: ".xdata".into(),
160                data: xdata,
161                relocs: Vec::new(),
162                debug_rows: Vec::new(),
163            });
164            sections.push(Section {
165                name: ".pdata".into(),
166                data: pdata,
167                relocs: Vec::new(),
168                debug_rows: Vec::new(),
169            });
170        }
171        ObjectFormat::MachO => {}
172    }
173
174    let has_debug = !sections[0].debug_rows.is_empty() || mf.debug_line_start.is_some();
175    if has_debug {
176        let source = mf.debug_source.as_deref().unwrap_or("unknown.c");
177        let rows = if !sections[0].debug_rows.is_empty() {
178            sections[0].debug_rows.clone()
179        } else {
180            vec![DebugLineRow {
181                address: 0,
182                line: mf.debug_line_start.unwrap_or(1),
183                column: 0,
184            }]
185        };
186        match emitter.object_format() {
187            ObjectFormat::Elf => {
188                let line = build_debug_line(source, &rows);
189                let abbrev = build_debug_abbrev();
190                let loclists = build_debug_loclists(size);
191                let info = build_debug_info(source, &mf.name, size, 0, 0, 12);
192                sections.push(Section {
193                    name: ".debug_abbrev".into(),
194                    data: abbrev,
195                    relocs: Vec::new(),
196                    debug_rows: Vec::new(),
197                });
198                sections.push(Section {
199                    name: ".debug_info".into(),
200                    data: info,
201                    relocs: Vec::new(),
202                    debug_rows: Vec::new(),
203                });
204                sections.push(Section {
205                    name: ".debug_line".into(),
206                    data: line,
207                    relocs: Vec::new(),
208                    debug_rows: Vec::new(),
209                });
210                sections.push(Section {
211                    name: ".debug_loclists".into(),
212                    data: loclists,
213                    relocs: Vec::new(),
214                    debug_rows: Vec::new(),
215                });
216            }
217            ObjectFormat::Coff => {
218                let cv = build_codeview_debug_s(source, &rows);
219                sections.push(Section {
220                    name: ".debug$S".into(),
221                    data: cv,
222                    relocs: Vec::new(),
223                    debug_rows: Vec::new(),
224                });
225            }
226            ObjectFormat::MachO => {}
227        };
228    }
229    ObjectFile {
230        format: emitter.object_format(),
231        elf_machine: emitter.elf_machine(),
232        coff_machine: emitter.coff_machine(),
233        sections,
234        symbols: vec![sym],
235    }
236}
237
238// ── ELF-64 serialization ───────────────────────────────────────────────────
239//
240// Minimal ELF-64 relocatable (.o) layout:
241//   ELF header (64 B)
242//   Section header table
243//     [0] null
244//     [1] .text       SHT_PROGBITS
245//     [2] .symtab     SHT_SYMTAB
246//     [3] .strtab     SHT_STRTAB   (symbol names)
247//     [4] .shstrtab   SHT_STRTAB   (section names)
248//     [5] .rela.text  SHT_RELA     (if relocs present)
249//   Section data: .text, .symtab, .strtab, .shstrtab, .rela.text
250
251fn serialize_elf(obj: &ObjectFile) -> Vec<u8> {
252    let text_sec = obj.sections.first();
253    let text_data = text_sec.map_or(&[][..], |s| s.data.as_slice());
254    let text_relocs = text_sec.map_or(&[][..], |s| s.relocs.as_slice());
255    let extra_secs = if obj.sections.len() > 1 {
256        &obj.sections[1..]
257    } else {
258        &[][..]
259    };
260    let has_relocs = !text_relocs.is_empty();
261
262    let mut shstrtab: Vec<u8> = vec![0u8];
263    let text_name_off = push_str(&mut shstrtab, b".text");
264    let extra_name_offs: Vec<u32> = extra_secs
265        .iter()
266        .map(|s| push_str(&mut shstrtab, s.name.as_bytes()))
267        .collect();
268    let symtab_name_off = push_str(&mut shstrtab, b".symtab");
269    let strtab_name_off = push_str(&mut shstrtab, b".strtab");
270    let shstrtab_name_off = push_str(&mut shstrtab, b".shstrtab");
271    let relatext_name_off = if has_relocs {
272        push_str(&mut shstrtab, b".rela.text")
273    } else {
274        0
275    };
276
277    let mut strtab: Vec<u8> = vec![0u8];
278    let sym_name_offs: Vec<u32> = obj
279        .symbols
280        .iter()
281        .map(|s| push_str(&mut strtab, s.name.as_bytes()))
282        .collect();
283
284    const ELF_HDR: u64 = 64;
285    const SH_ENT: u64 = 64;
286    const SYM_ENT: u64 = 24;
287    const RELA_ENT: u64 = 24;
288    const SHT_PROGBITS: u32 = 1;
289    const SHT_SYMTAB: u32 = 2;
290    const SHT_STRTAB: u32 = 3;
291    const SHT_RELA: u32 = 4;
292
293    let idx_text = 1u16;
294    let idx_extra_start = idx_text + 1;
295    let idx_symtab = idx_extra_start + extra_secs.len() as u16;
296    let idx_strtab = idx_symtab + 1;
297    let idx_shstrtab = idx_strtab + 1;
298    let idx_rela = idx_shstrtab + 1;
299
300    let num_sections: u16 = if has_relocs {
301        idx_rela + 1
302    } else {
303        idx_shstrtab + 1
304    };
305    let sh_table_size = num_sections as u64 * SH_ENT;
306
307    let mut cursor = ELF_HDR + sh_table_size;
308    let text_off = cursor;
309    let text_size = text_data.len() as u64;
310    cursor += text_size;
311
312    let mut extra_offs = Vec::with_capacity(extra_secs.len());
313    for sec in extra_secs {
314        extra_offs.push(cursor);
315        cursor += sec.data.len() as u64;
316    }
317
318    let sym_count = 1 + obj.symbols.len() as u64;
319    let symtab_off = cursor;
320    let symtab_size = sym_count * SYM_ENT;
321    cursor += symtab_size;
322
323    let strtab_off = cursor;
324    cursor += strtab.len() as u64;
325    let shstrtab_off = cursor;
326    cursor += shstrtab.len() as u64;
327
328    let relatext_off = cursor;
329    let relatext_size = text_relocs.len() as u64 * RELA_ENT;
330
331    let mut buf = Vec::<u8>::new();
332    buf.extend_from_slice(b"\x7fELF");
333    buf.push(2);
334    buf.push(1);
335    buf.push(1);
336    buf.push(0);
337    buf.extend_from_slice(&[0u8; 8]);
338    w16(&mut buf, 1);
339    w16(&mut buf, obj.elf_machine);
340    w32(&mut buf, 1);
341    w64(&mut buf, 0);
342    w64(&mut buf, 0);
343    w64(&mut buf, ELF_HDR);
344    w32(&mut buf, 0);
345    w16(&mut buf, ELF_HDR as u16);
346    w16(&mut buf, 0);
347    w16(&mut buf, 0);
348    w16(&mut buf, SH_ENT as u16);
349    w16(&mut buf, num_sections);
350    w16(&mut buf, idx_shstrtab);
351
352    let write_shdr = |buf: &mut Vec<u8>,
353                      name: u32,
354                      sh_type: u32,
355                      flags: u64,
356                      addr: u64,
357                      off: u64,
358                      size: u64,
359                      link: u32,
360                      info: u32,
361                      align: u64,
362                      entsize: u64| {
363        w32(buf, name);
364        w32(buf, sh_type);
365        w64(buf, flags);
366        w64(buf, addr);
367        w64(buf, off);
368        w64(buf, size);
369        w32(buf, link);
370        w32(buf, info);
371        w64(buf, align);
372        w64(buf, entsize);
373    };
374
375    write_shdr(&mut buf, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
376    write_shdr(
377        &mut buf,
378        text_name_off,
379        SHT_PROGBITS,
380        6,
381        0,
382        text_off,
383        text_size,
384        0,
385        0,
386        16,
387        0,
388    );
389
390    for (i, sec) in extra_secs.iter().enumerate() {
391        write_shdr(
392            &mut buf,
393            extra_name_offs[i],
394            SHT_PROGBITS,
395            0,
396            0,
397            extra_offs[i],
398            sec.data.len() as u64,
399            0,
400            0,
401            1,
402            0,
403        );
404    }
405
406    write_shdr(
407        &mut buf,
408        symtab_name_off,
409        SHT_SYMTAB,
410        0,
411        0,
412        symtab_off,
413        symtab_size,
414        idx_strtab as u32,
415        1,
416        8,
417        SYM_ENT,
418    );
419    write_shdr(
420        &mut buf,
421        strtab_name_off,
422        SHT_STRTAB,
423        0,
424        0,
425        strtab_off,
426        strtab.len() as u64,
427        0,
428        0,
429        1,
430        0,
431    );
432    write_shdr(
433        &mut buf,
434        shstrtab_name_off,
435        SHT_STRTAB,
436        0,
437        0,
438        shstrtab_off,
439        shstrtab.len() as u64,
440        0,
441        0,
442        1,
443        0,
444    );
445    if has_relocs {
446        write_shdr(
447            &mut buf,
448            relatext_name_off,
449            SHT_RELA,
450            0,
451            0,
452            relatext_off,
453            relatext_size,
454            idx_symtab as u32,
455            idx_text as u32,
456            8,
457            RELA_ENT,
458        );
459    }
460
461    buf.extend_from_slice(text_data);
462    for sec in extra_secs {
463        buf.extend_from_slice(&sec.data);
464    }
465
466    buf.extend_from_slice(&[0u8; 24]);
467    for (i, sym) in obj.symbols.iter().enumerate() {
468        let st_info: u8 = (1u8 << 4) | 2u8;
469        let st_shndx: u16 = (sym.section + 1) as u16;
470        w32(&mut buf, sym_name_offs[i]);
471        buf.push(st_info);
472        buf.push(0);
473        w16(&mut buf, st_shndx);
474        w64(&mut buf, sym.offset);
475        w64(&mut buf, sym.size);
476    }
477
478    buf.extend_from_slice(&strtab);
479    buf.extend_from_slice(&shstrtab);
480
481    if has_relocs {
482        for reloc in text_relocs {
483            let sym_idx = (reloc.symbol + 1) as u64;
484            let r_type: u64 = match reloc.kind {
485                RelocKind::Pc32 => 2,
486                RelocKind::Abs64 => 1,
487            };
488            let r_info = (sym_idx << 32) | r_type;
489            w64(&mut buf, reloc.offset);
490            w64(&mut buf, r_info);
491            buf.extend_from_slice(&reloc.addend.to_le_bytes());
492        }
493    }
494    buf
495}
496
497fn build_debug_line(source_file: &str, rows: &[DebugLineRow]) -> Vec<u8> {
498    let file = source_file.rsplit('/').next().unwrap_or(source_file);
499
500    let mut header_body = Vec::<u8>::new();
501    header_body.push(1); // minimum_instruction_length
502    header_body.push(1); // default_is_stmt
503    header_body.push((-5i8) as u8); // line_base
504    header_body.push(14); // line_range
505    header_body.push(13); // opcode_base
506    header_body.extend_from_slice(&[0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1]); // std opcode lengths
507    header_body.push(0); // include_directories terminator
508    header_body.extend_from_slice(file.as_bytes());
509    header_body.push(0); // file name terminator
510    write_uleb128(&mut header_body, 0); // dir index
511    write_uleb128(&mut header_body, 0); // mtime
512    write_uleb128(&mut header_body, 0); // size
513    header_body.push(0); // file_names terminator
514
515    let mut program = Vec::<u8>::new();
516    let mut sorted = rows.to_vec();
517    sorted.sort_by_key(|r| r.address);
518    let mut cur_addr = 0u64;
519    let mut cur_line = 1u32;
520    let mut cur_col = 0u32;
521    for row in sorted {
522        if row.address > cur_addr {
523            program.push(2); // DW_LNS_advance_pc
524            write_uleb128(&mut program, row.address - cur_addr);
525            cur_addr = row.address;
526        }
527        if row.line != cur_line {
528            program.push(3); // DW_LNS_advance_line
529            write_sleb128(&mut program, row.line as i64 - cur_line as i64);
530            cur_line = row.line;
531        }
532        if row.column != cur_col {
533            program.push(5); // DW_LNS_set_column
534            write_uleb128(&mut program, row.column as u64);
535            cur_col = row.column;
536        }
537        program.push(1); // DW_LNS_copy
538    }
539    program.push(0);
540    program.push(1);
541    program.push(1); // DW_LNE_end_sequence
542
543    let unit_length = (2 + 4 + header_body.len() + program.len()) as u32;
544    let mut out = Vec::<u8>::new();
545    w32(&mut out, unit_length);
546    w16(&mut out, 2); // DWARF v2 line table
547    w32(&mut out, header_body.len() as u32);
548    out.extend_from_slice(&header_body);
549    out.extend_from_slice(&program);
550    out
551}
552
553fn build_debug_abbrev() -> Vec<u8> {
554    // DWARF5 abbrev set:
555    // 1: CU (children=yes)
556    // 2: subprogram (children=yes)
557    // 3: variable (children=no)
558    // 4: base_type (children=no)
559    const DW_TAG_COMPILE_UNIT: u64 = 0x11;
560    const DW_TAG_SUBPROGRAM: u64 = 0x2e;
561    const DW_TAG_VARIABLE: u64 = 0x34;
562    const DW_TAG_BASE_TYPE: u64 = 0x24;
563
564    const DW_CHILDREN_NO: u8 = 0x00;
565    const DW_CHILDREN_YES: u8 = 0x01;
566
567    const DW_AT_NAME: u64 = 0x03;
568    const DW_AT_STMT_LIST: u64 = 0x10;
569    const DW_AT_LOW_PC: u64 = 0x11;
570    const DW_AT_HIGH_PC: u64 = 0x12;
571    const DW_AT_COMP_DIR: u64 = 0x1b;
572    const DW_AT_ENCODING: u64 = 0x3e;
573    const DW_AT_BYTE_SIZE: u64 = 0x0b;
574    const DW_AT_LOCLISTS_BASE: u64 = 0x8c;
575
576    const DW_FORM_ADDR: u64 = 0x01;
577    const DW_FORM_DATA1: u64 = 0x0b;
578    const DW_FORM_DATA8: u64 = 0x07;
579    const DW_FORM_STRING: u64 = 0x08;
580    const DW_FORM_SEC_OFFSET: u64 = 0x17;
581
582    let mut out = Vec::new();
583
584    // Abbrev code 1: compile_unit
585    write_uleb128(&mut out, 1);
586    write_uleb128(&mut out, DW_TAG_COMPILE_UNIT);
587    out.push(DW_CHILDREN_YES);
588    write_uleb128(&mut out, DW_AT_NAME);
589    write_uleb128(&mut out, DW_FORM_STRING);
590    write_uleb128(&mut out, DW_AT_STMT_LIST);
591    write_uleb128(&mut out, DW_FORM_SEC_OFFSET);
592    write_uleb128(&mut out, DW_AT_COMP_DIR);
593    write_uleb128(&mut out, DW_FORM_STRING);
594    write_uleb128(&mut out, DW_AT_LOW_PC);
595    write_uleb128(&mut out, DW_FORM_ADDR);
596    write_uleb128(&mut out, DW_AT_HIGH_PC);
597    write_uleb128(&mut out, DW_FORM_DATA8);
598    write_uleb128(&mut out, DW_AT_LOCLISTS_BASE);
599    write_uleb128(&mut out, DW_FORM_SEC_OFFSET);
600    out.push(0);
601    out.push(0);
602
603    // Abbrev code 2: subprogram
604    write_uleb128(&mut out, 2);
605    write_uleb128(&mut out, DW_TAG_SUBPROGRAM);
606    out.push(DW_CHILDREN_YES);
607    write_uleb128(&mut out, DW_AT_NAME);
608    write_uleb128(&mut out, DW_FORM_STRING);
609    write_uleb128(&mut out, DW_AT_LOW_PC);
610    write_uleb128(&mut out, DW_FORM_ADDR);
611    write_uleb128(&mut out, DW_AT_HIGH_PC);
612    write_uleb128(&mut out, DW_FORM_DATA8);
613    out.push(0);
614    out.push(0);
615
616    // Abbrev code 3: variable (keep minimal to satisfy verifier)
617    write_uleb128(&mut out, 3);
618    write_uleb128(&mut out, DW_TAG_VARIABLE);
619    out.push(DW_CHILDREN_NO);
620    write_uleb128(&mut out, DW_AT_NAME);
621    write_uleb128(&mut out, DW_FORM_STRING);
622    out.push(0);
623    out.push(0);
624
625    // Abbrev code 4: base_type (e.g. i64)
626    write_uleb128(&mut out, 4);
627    write_uleb128(&mut out, DW_TAG_BASE_TYPE);
628    out.push(DW_CHILDREN_NO);
629    write_uleb128(&mut out, DW_AT_NAME);
630    write_uleb128(&mut out, DW_FORM_STRING);
631    write_uleb128(&mut out, DW_AT_ENCODING);
632    write_uleb128(&mut out, DW_FORM_DATA1);
633    write_uleb128(&mut out, DW_AT_BYTE_SIZE);
634    write_uleb128(&mut out, DW_FORM_DATA1);
635    out.push(0);
636    out.push(0);
637
638    // End of abbrev table
639    out.push(0);
640    out
641}
642
643fn build_debug_info(
644    source_file: &str,
645    fn_name: &str,
646    text_size: u64,
647    stmt_list_off: u32,
648    _abbrev_off: u32,
649    _loclists_var_off: u32,
650) -> Vec<u8> {
651    const DWARF_VERSION: u16 = 5;
652    const DW_UT_COMPILE: u8 = 0x01;
653    const DW_ATE_SIGNED: u8 = 0x05;
654
655    let file = source_file.rsplit('/').next().unwrap_or(source_file);
656    let comp_dir = source_file
657        .rfind('/')
658        .map(|i| &source_file[..i])
659        .filter(|s| !s.is_empty())
660        .unwrap_or(".");
661
662    let mut body = Vec::new();
663
664    // DIE: compile unit (abbrev 1)
665    write_uleb128(&mut body, 1);
666    body.extend_from_slice(file.as_bytes());
667    body.push(0);
668    w32(&mut body, stmt_list_off);
669    body.extend_from_slice(comp_dir.as_bytes());
670    body.push(0);
671    w64(&mut body, 0); // low_pc
672    w64(&mut body, text_size); // high_pc as address range size
673    w32(&mut body, 0); // DW_AT_loclists_base (base for indexed loclists)
674
675    // DIE: subprogram (abbrev 2)
676    write_uleb128(&mut body, 2);
677    body.extend_from_slice(fn_name.as_bytes());
678    body.push(0);
679    w64(&mut body, 0);
680    w64(&mut body, text_size);
681
682    // DIE: variable (abbrev 3)
683    write_uleb128(&mut body, 3);
684    body.extend_from_slice(b"result");
685    body.push(0);
686
687    // End children of subprogram.
688    body.push(0);
689
690    // DIE: base_type (abbrev 4)
691    write_uleb128(&mut body, 4);
692    body.extend_from_slice(b"i64");
693    body.push(0);
694    body.push(DW_ATE_SIGNED);
695    body.push(8);
696
697    // End children of CU.
698    body.push(0);
699
700    let mut out = Vec::new();
701    let unit_length = (2 + 1 + 1 + 4 + body.len()) as u32;
702    w32(&mut out, unit_length);
703    w16(&mut out, DWARF_VERSION);
704    out.push(DW_UT_COMPILE);
705    out.push(8); // address size
706    w32(&mut out, 0); // abbrev offset
707    out.extend_from_slice(&body);
708    out
709}
710
711fn build_debug_loclists(text_size: u64) -> Vec<u8> {
712    // DWARF5 .debug_loclists with one list at offset 12 (after header).
713    // List entries:
714    //   DW_LLE_offset_pair [0, text_size] exprloc(DW_OP_reg0)
715    //   DW_LLE_end_of_list
716    // Use offset-pair (relative to CU low_pc) to avoid absolute relocations in .o files.
717    const DW_LLE_END_OF_LIST: u8 = 0x00;
718    const DW_LLE_OFFSET_PAIR: u8 = 0x04;
719    const DW_OP_REG0: u8 = 0x50;
720
721    let mut body = Vec::new();
722    body.push(DW_LLE_OFFSET_PAIR);
723    write_uleb128(&mut body, 0);
724    write_uleb128(&mut body, text_size.max(1));
725    body.push(1); // exprloc length
726    body.push(DW_OP_REG0);
727    body.push(DW_LLE_END_OF_LIST);
728
729    let mut out = Vec::new();
730    let unit_length = (2 + 1 + 1 + 4 + body.len()) as u32;
731    w32(&mut out, unit_length);
732    w16(&mut out, 5); // DWARF v5
733    out.push(8); // address size
734    out.push(0); // segment selector size
735    w32(&mut out, 0); // offset entry count
736    out.extend_from_slice(&body);
737    out
738}
739
740fn build_eh_frame(text_size: u64, frame_size: u32, used_callee_saved: &[PReg]) -> Vec<u8> {
741    // Baseline .eh_frame with one CIE/FDE, now shaped by frame facts.
742    // CIE augmentation uses zR and encodes FDE pointers as pcrel/sdata4.
743    let mut out = Vec::new();
744
745    let mut cie = Vec::new();
746    cie.push(1); // version
747    cie.extend_from_slice(b"zR\0"); // augmentation
748    write_uleb128(&mut cie, 1); // code alignment factor
749    write_sleb128(&mut cie, -8); // data alignment factor
750    write_uleb128(&mut cie, 16); // return address register (RIP)
751    write_uleb128(&mut cie, 1); // augmentation data length
752    cie.push(0x1b); // DW_EH_PE_pcrel | DW_EH_PE_sdata4
753
754    // Initial canonical frame: CFA = rsp + 8, RA saved at cfa-8.
755    cie.push(0x0c); // DW_CFA_def_cfa
756    write_uleb128(&mut cie, 7); // rsp
757    write_uleb128(&mut cie, 8);
758    cie.push(0x90); // DW_CFA_offset + r16 (rip)
759    write_uleb128(&mut cie, 1);
760
761    w32(&mut out, cie.len() as u32 + 4);
762    w32(&mut out, 0); // CIE id
763    out.extend_from_slice(&cie);
764    while out.len() % 8 != 0 {
765        out.push(0);
766    }
767
768    let fde_start = out.len();
769    let mut fde = Vec::new();
770    w32(&mut fde, 0); // initial_location (placeholder in object file)
771    w32(&mut fde, text_size.max(1) as u32); // address range
772
773    // Build FDE instruction stream from frame shape.
774    let mut fde_prog = Vec::new();
775    let mut cfa_off = 8u64;
776
777    // Account for frame pointer setup and pushed callee-saved registers.
778    let pushes = if frame_size > 0 || !used_callee_saved.is_empty() {
779        1 + used_callee_saved.len() as u64 // push rbp + pushes
780    } else {
781        0
782    };
783
784    if pushes > 0 {
785        cfa_off += pushes * 8;
786        fde_prog.push(0x0e); // DW_CFA_def_cfa_offset
787        write_uleb128(&mut fde_prog, cfa_off);
788
789        // rbp saved at CFA-16 after push rbp + call return address.
790        fde_prog.push(0x86); // DW_CFA_offset + r6 (rbp)
791        write_uleb128(&mut fde_prog, 2);
792
793        for (idx, pr) in used_callee_saved.iter().enumerate() {
794            let reg = pr.0 as u8;
795            if reg <= 0x3f {
796                fde_prog.push(0x80 | reg); // DW_CFA_offset + reg
797                write_uleb128(&mut fde_prog, 3 + idx as u64); // after RA+RBP
798            }
799        }
800
801        // set CFA register to rbp once prologue establishes frame pointer.
802        fde_prog.push(0x0d); // DW_CFA_def_cfa_register
803        write_uleb128(&mut fde_prog, 6); // rbp
804    }
805
806    if frame_size > 0 {
807        cfa_off += frame_size as u64;
808        fde_prog.push(0x0e); // DW_CFA_def_cfa_offset
809        write_uleb128(&mut fde_prog, cfa_off);
810    }
811
812    write_uleb128(&mut fde, fde_prog.len() as u64); // augmentation data length
813    fde.extend_from_slice(&fde_prog);
814
815    w32(&mut out, fde.len() as u32 + 4);
816    let cie_ptr = fde_start as u32;
817    w32(&mut out, cie_ptr); // CIE pointer (offset back to CIE at 0)
818    out.extend_from_slice(&fde);
819    while out.len() % 8 != 0 {
820        out.push(0);
821    }
822
823    w32(&mut out, 0); // terminator
824    out
825}
826
827fn build_coff_unwind_tables(text_size: u64, frame_size: u32, used_callee_saved: &[PReg]) -> (Vec<u8>, Vec<u8>) {
828    // x64 UNWIND_INFO shaped from prologue facts (push rbp + optional stack alloc).
829    // Keep a conservative subset of unwind codes for compatibility.
830    let has_frame = frame_size > 0 || !used_callee_saved.is_empty();
831    let mut codes: Vec<(u8, u8, u16)> = Vec::new();
832
833    if has_frame {
834        // UWOP_PUSH_NONVOL for RBP at prologue offset 1.
835        codes.push((1, 0, 5)); // info=RBP
836    }
837
838    let alloc_size = if frame_size == 0 { 0 } else { ((frame_size as u32 + 15) / 16) * 16 };
839    if alloc_size > 0 && alloc_size <= 128 {
840        // UWOP_ALLOC_SMALL: info = (size/8)-1
841        let info = ((alloc_size / 8) - 1) as u16;
842        codes.push((4, 2, info));
843    }
844
845    let count_of_codes = codes.len() as u8;
846    let mut xdata = Vec::new();
847    xdata.push(0x01); // version=1, flags=0
848    xdata.push(if has_frame { 4 } else { 0 }); // conservative prolog size
849    xdata.push(count_of_codes); // unwind code slots (we encode one slot each)
850    xdata.push(if has_frame { 5 } else { 0 }); // frame register=RBP, offset=0
851
852    for (code_off, unwind_op, op_info) in &codes {
853        xdata.push(*code_off);
854        xdata.push(((*op_info as u8) << 4) | (*unwind_op & 0x0f));
855    }
856
857    // Align unwind info to 4-byte boundary.
858    while xdata.len() % 4 != 0 {
859        xdata.push(0);
860    }
861
862    // One RUNTIME_FUNCTION entry in .pdata:
863    // BeginAddress, EndAddress, UnwindInfoAddress (all image-relative u32).
864    let mut pdata = Vec::new();
865    w32(&mut pdata, 0);
866    w32(&mut pdata, text_size.max(1) as u32);
867    w32(&mut pdata, 0); // points at start of .xdata in this object's section space
868
869    (xdata, pdata)
870}
871
872// ── Mach-O 64-bit serialization ────────────────────────────────────────────
873//
874// Minimal Mach-O 64-bit MH_OBJECT layout:
875//   mach_header_64     (32 B)
876//   LC_SEGMENT_64      (72 B)
877//     section_64 __TEXT,__text  (80 B)
878//   LC_SYMTAB          (24 B)
879//   LC_DYSYMTAB        (80 B)
880//   [padding to 16-byte boundary]
881//   section data: __text
882//   relocation entries
883//   symbol table (nlist_64, 16 B each)
884//   string table
885
886fn serialize_macho(obj: &ObjectFile) -> Vec<u8> {
887    let text_data = obj.sections.first().map_or(&[][..], |s| s.data.as_slice());
888    let text_size = text_data.len() as u32;
889    let text_relocs = obj
890        .sections
891        .first()
892        .map_or(&[][..], |s| s.relocs.as_slice());
893
894    const MH_HDR: u32 = 32;
895    const SEG_CMD: u32 = 72;
896    const SECT_HDR: u32 = 80;
897    const SYMTAB_CMD: u32 = 24;
898    const DYSYMTAB_CMD: u32 = 80;
899    const SYM_ENT: u32 = 16; // nlist_64
900
901    let header_size = MH_HDR + SEG_CMD + SECT_HDR + SYMTAB_CMD + DYSYMTAB_CMD;
902    let cmds_size = SEG_CMD + SECT_HDR + SYMTAB_CMD + DYSYMTAB_CMD;
903
904    // Align __text to 16-byte boundary after headers.
905    let text_align = 16u32;
906    let text_pad = (text_align - (header_size % text_align)) % text_align;
907    let text_off = header_size + text_pad;
908    let reloc_off = text_off + text_size;
909    let reloc_size = text_relocs.len() as u32 * 8;
910
911    // String table: index 0 = \0 (null, required by Mach-O spec — empty string).
912    let mut strtab: Vec<u8> = vec![0u8];
913    let sym_name_offs: Vec<u32> = obj
914        .symbols
915        .iter()
916        .map(|s| {
917            let off = strtab.len() as u32;
918            strtab.push(b'_'); // C symbol underscore prefix
919            strtab.extend_from_slice(s.name.as_bytes());
920            strtab.push(0);
921            off
922        })
923        .collect();
924    while strtab.len() % 4 != 0 {
925        strtab.push(0);
926    } // align to 4 bytes
927
928    let symtab_off = reloc_off + reloc_size;
929    let symtab_size = obj.symbols.len() as u32 * SYM_ENT;
930    let strtab_off = symtab_off + symtab_size;
931
932    let mut buf = Vec::<u8>::new();
933
934    // mach_header_64
935    w32(&mut buf, 0xfeedfacf); // MH_MAGIC_64
936    w32(&mut buf, 0x01000007); // CPU_TYPE_X86_64
937    w32(&mut buf, 0x00000003); // CPU_SUBTYPE_X86_64_ALL
938    w32(&mut buf, 1); // MH_OBJECT
939    w32(&mut buf, 3); // ncmds
940    w32(&mut buf, cmds_size); // sizeofcmds
941    w32(&mut buf, 0); // flags
942    w32(&mut buf, 0); // reserved
943
944    // LC_SEGMENT_64
945    w32(&mut buf, 0x19); // LC_SEGMENT_64
946    w32(&mut buf, SEG_CMD + SECT_HDR); // cmdsize
947    buf.extend_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0"); // segname[16]
948    w64(&mut buf, 0); // vmaddr
949    w64(&mut buf, text_size as u64); // vmsize
950    w64(&mut buf, text_off as u64); // fileoff
951    w64(&mut buf, text_size as u64); // filesize
952    w32(&mut buf, 7); // maxprot
953    w32(&mut buf, 5); // initprot (R|X)
954    w32(&mut buf, 1); // nsects
955    w32(&mut buf, 0); // flags
956
957    // section_64 __TEXT,__text
958    buf.extend_from_slice(b"__text\0\0\0\0\0\0\0\0\0\0"); // sectname[16]
959    buf.extend_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0"); // segname[16]
960    w64(&mut buf, 0); // addr
961    w64(&mut buf, text_size as u64); // size
962    w32(&mut buf, text_off); // offset
963    w32(&mut buf, 4); // align (2^4 = 16)
964    w32(&mut buf, reloc_off); // reloff
965    w32(&mut buf, text_relocs.len() as u32); // nreloc
966    w32(&mut buf, 0x80000400); // S_ATTR_PURE_INSTRUCTIONS|S_ATTR_SOME_INSTRUCTIONS
967    w32(&mut buf, 0);
968    w32(&mut buf, 0);
969    w32(&mut buf, 0); // reserved1-3
970
971    // LC_SYMTAB
972    w32(&mut buf, 2); // LC_SYMTAB
973    w32(&mut buf, SYMTAB_CMD);
974    w32(&mut buf, symtab_off);
975    w32(&mut buf, obj.symbols.len() as u32);
976    w32(&mut buf, strtab_off);
977    w32(&mut buf, strtab.len() as u32);
978
979    // LC_DYSYMTAB
980    w32(&mut buf, 0xB); // LC_DYSYMTAB
981    w32(&mut buf, DYSYMTAB_CMD);
982    let n_globals = obj.symbols.iter().filter(|s| s.global).count() as u32;
983    w32(&mut buf, 0);
984    w32(&mut buf, 0); // ilocalsym, nlocalsym
985    w32(&mut buf, 0);
986    w32(&mut buf, n_globals); // iextdefsym, nextdefsym
987    w32(&mut buf, n_globals);
988    w32(&mut buf, 0); // iundefsym, nundefsym
989    buf.extend_from_slice(&[0u8; 48]); // remaining fields
990
991    // padding
992    buf.resize(buf.len() + text_pad as usize, 0);
993
994    // __text section data
995    buf.extend_from_slice(text_data);
996
997    // relocation entries (relocation_info, 8 bytes each)
998    for reloc in text_relocs {
999        let sym_idx = reloc.symbol as u32;
1000        let (r_type, r_length, r_pcrel): (u32, u32, u32) = match reloc.kind {
1001            RelocKind::Pc32 => (2, 2, 1),  // X86_64_RELOC_BRANCH, 4 bytes, PC-rel
1002            RelocKind::Abs64 => (0, 3, 0), // X86_64_RELOC_UNSIGNED, 8 bytes, abs
1003        };
1004        let r_extern: u32 = 1;
1005        let r_info =
1006            sym_idx | (r_pcrel << 24) | (r_length << 25) | (r_extern << 27) | (r_type << 28);
1007        w32(&mut buf, reloc.offset as u32); // r_address
1008        w32(&mut buf, r_info);
1009    }
1010
1011    // symbol table (nlist_64)
1012    for (i, sym) in obj.symbols.iter().enumerate() {
1013        let n_type: u8 = if sym.global { 0x0F } else { 0x0E }; // N_EXT|N_SECT
1014        w32(&mut buf, sym_name_offs[i]); // n_strx
1015        buf.push(n_type); // n_type
1016        buf.push(1); // n_sect (1-based, __text = 1)
1017        w16(&mut buf, 0); // n_desc
1018        w64(&mut buf, sym.offset); // n_value
1019    }
1020
1021    // string table
1022    buf.extend_from_slice(&strtab);
1023
1024    buf
1025}
1026
1027// ── COFF (PE/COFF object) serialization ───────────────────────────────────
1028
1029fn serialize_coff(obj: &ObjectFile) -> Vec<u8> {
1030    const FILE_HEADER_SIZE: usize = 20;
1031    const SECTION_HEADER_SIZE: usize = 40;
1032    const RELOC_SIZE: usize = 10;
1033    const SYMBOL_SIZE: usize = 18;
1034
1035    let nsec = obj.sections.len();
1036    let sec_headers_size = nsec * SECTION_HEADER_SIZE;
1037    let sec_data_start = FILE_HEADER_SIZE + sec_headers_size;
1038
1039    let mut data_ptr = sec_data_start as u32;
1040    let mut sec_raw_ptrs = Vec::with_capacity(nsec);
1041    let mut sec_reloc_ptrs = Vec::with_capacity(nsec);
1042    for sec in &obj.sections {
1043        let raw_size = sec.data.len() as u32;
1044        let reloc_size = (sec.relocs.len() * RELOC_SIZE) as u32;
1045        sec_raw_ptrs.push(data_ptr);
1046        data_ptr = data_ptr.wrapping_add(raw_size);
1047        sec_reloc_ptrs.push(if reloc_size > 0 { data_ptr } else { 0 });
1048        data_ptr = data_ptr.wrapping_add(reloc_size);
1049    }
1050
1051    let symtab_ptr = data_ptr;
1052    let nsym = obj.symbols.len() as u32;
1053    // COFF string table: u32 size + NUL-terminated strings.
1054    let mut strtab: Vec<u8> = vec![0, 0, 0, 0];
1055    let mut section_name_offs = Vec::with_capacity(nsec);
1056    let mut symbol_name_offs = Vec::with_capacity(obj.symbols.len());
1057    for sec in &obj.sections {
1058        section_name_offs.push(append_coff_string(&mut strtab, &sec.name));
1059    }
1060    for sym in &obj.symbols {
1061        symbol_name_offs.push(append_coff_string(&mut strtab, &sym.name));
1062    }
1063    let strtab_size = strtab.len() as u32;
1064    strtab[0..4].copy_from_slice(&strtab_size.to_le_bytes());
1065
1066    let total_est = symtab_ptr as usize + nsym as usize * SYMBOL_SIZE + strtab.len();
1067    let mut buf = Vec::with_capacity(total_est);
1068
1069    // IMAGE_FILE_HEADER
1070    w16(&mut buf, obj.coff_machine); // Machine
1071    w16(&mut buf, nsec as u16); // NumberOfSections
1072    w32(&mut buf, 0); // TimeDateStamp
1073    w32(&mut buf, symtab_ptr); // PointerToSymbolTable
1074    w32(&mut buf, nsym); // NumberOfSymbols
1075    w16(&mut buf, 0); // SizeOfOptionalHeader
1076    w16(&mut buf, 0); // Characteristics
1077
1078    // IMAGE_SECTION_HEADER
1079    for (i, sec) in obj.sections.iter().enumerate() {
1080        write_coff_name_field(&mut buf, &sec.name, section_name_offs[i]);
1081        w32(&mut buf, 0); // VirtualSize
1082        w32(&mut buf, 0); // VirtualAddress
1083        w32(&mut buf, sec.data.len() as u32); // SizeOfRawData
1084        w32(&mut buf, sec_raw_ptrs[i]); // PointerToRawData
1085        w32(&mut buf, sec_reloc_ptrs[i]); // PointerToRelocations
1086        w32(&mut buf, 0); // PointerToLinenumbers
1087        w16(&mut buf, sec.relocs.len() as u16); // NumberOfRelocations
1088        w16(&mut buf, 0); // NumberOfLinenumbers
1089        w32(&mut buf, coff_section_characteristics(&sec.name));
1090    }
1091
1092    // section data + relocations
1093    for sec in &obj.sections {
1094        buf.extend_from_slice(&sec.data);
1095        for reloc in &sec.relocs {
1096            w32(&mut buf, reloc.offset as u32); // VirtualAddress
1097            w32(&mut buf, reloc.symbol as u32); // SymbolTableIndex
1098            let typ = match reloc.kind {
1099                RelocKind::Pc32 => 0x0004,  // IMAGE_REL_AMD64_REL32
1100                RelocKind::Abs64 => 0x0001, // IMAGE_REL_AMD64_ADDR64
1101            };
1102            w16(&mut buf, typ);
1103        }
1104    }
1105
1106    // symbol table
1107    for (i, sym) in obj.symbols.iter().enumerate() {
1108        write_coff_name_field(&mut buf, &sym.name, symbol_name_offs[i]);
1109        w32(&mut buf, sym.offset as u32); // Value
1110        w16(&mut buf, (sym.section + 1) as u16); // SectionNumber (1-based)
1111        w16(&mut buf, 0); // Type
1112        buf.push(if sym.global { 2 } else { 3 }); // StorageClass: EXTERNAL or STATIC
1113        buf.push(0); // NumberOfAuxSymbols
1114    }
1115
1116    // string table
1117    buf.extend_from_slice(&strtab);
1118    buf
1119}
1120
1121fn append_coff_string(strtab: &mut Vec<u8>, s: &str) -> u32 {
1122    let off = strtab.len() as u32;
1123    strtab.extend_from_slice(s.as_bytes());
1124    strtab.push(0);
1125    off
1126}
1127
1128fn write_coff_name_field(buf: &mut Vec<u8>, name: &str, strtab_off: u32) {
1129    if name.len() <= 8 {
1130        let mut raw = [0u8; 8];
1131        raw[..name.len()].copy_from_slice(name.as_bytes());
1132        buf.extend_from_slice(&raw);
1133    } else {
1134        let tag = format!("/{}", strtab_off);
1135        let mut raw = [0u8; 8];
1136        let bytes = tag.as_bytes();
1137        let n = bytes.len().min(8);
1138        raw[..n].copy_from_slice(&bytes[..n]);
1139        buf.extend_from_slice(&raw);
1140    }
1141}
1142
1143fn coff_section_characteristics(name: &str) -> u32 {
1144    if name == ".text" {
1145        0x60000020 // CNT_CODE | MEM_EXECUTE | MEM_READ
1146    } else if name.starts_with(".debug") {
1147        0x42000040 // CNT_INITIALIZED_DATA | MEM_READ | MEM_DISCARDABLE
1148    } else {
1149        0x40000040 // CNT_INITIALIZED_DATA | MEM_READ
1150    }
1151}
1152
1153fn build_codeview_debug_s(source_file: &str, rows: &[DebugLineRow]) -> Vec<u8> {
1154    // .debug$S starts with CV_SIGNATURE_C13.
1155    let mut out = Vec::new();
1156    w32(&mut out, 4);
1157
1158    // Minimal symbol payload carrying source identity and line span.
1159    // This is intentionally small but consumable by COFF/CodeView tooling.
1160    let mut payload = Vec::new();
1161    payload.extend_from_slice(
1162        source_file
1163            .rsplit('/')
1164            .next()
1165            .unwrap_or(source_file)
1166            .as_bytes(),
1167    );
1168    payload.push(0);
1169
1170    let min_line = rows.iter().map(|r| r.line).min().unwrap_or(1);
1171    let max_line = rows.iter().map(|r| r.line).max().unwrap_or(min_line);
1172    w32(&mut payload, min_line);
1173    w32(&mut payload, max_line);
1174
1175    // subsection type=0xF1 (DEBUG_S_SYMBOLS)
1176    w32(&mut out, 0xF1);
1177    w32(&mut out, payload.len() as u32);
1178    out.extend_from_slice(&payload);
1179    while out.len() % 4 != 0 {
1180        out.push(0);
1181    }
1182    out
1183}
1184
1185// ── byte-writing helpers ───────────────────────────────────────────────────
1186
1187#[inline]
1188fn w16(buf: &mut Vec<u8>, v: u16) {
1189    buf.extend_from_slice(&v.to_le_bytes());
1190}
1191#[inline]
1192fn w32(buf: &mut Vec<u8>, v: u32) {
1193    buf.extend_from_slice(&v.to_le_bytes());
1194}
1195#[inline]
1196fn w64(buf: &mut Vec<u8>, v: u64) {
1197    buf.extend_from_slice(&v.to_le_bytes());
1198}
1199
1200fn write_uleb128(buf: &mut Vec<u8>, mut v: u64) {
1201    loop {
1202        let mut byte = (v & 0x7f) as u8;
1203        v >>= 7;
1204        if v != 0 {
1205            byte |= 0x80;
1206        }
1207        buf.push(byte);
1208        if v == 0 {
1209            break;
1210        }
1211    }
1212}
1213
1214fn write_sleb128(buf: &mut Vec<u8>, mut v: i64) {
1215    loop {
1216        let byte = (v as u8) & 0x7f;
1217        let sign = (byte & 0x40) != 0;
1218        v >>= 7;
1219        let done = (v == 0 && !sign) || (v == -1 && sign);
1220        if done {
1221            buf.push(byte);
1222            break;
1223        }
1224        buf.push(byte | 0x80);
1225    }
1226}
1227
1228/// Append a null-terminated string to `table` and return its start offset.
1229fn push_str(table: &mut Vec<u8>, s: &[u8]) -> u32 {
1230    let off = table.len() as u32;
1231    table.extend_from_slice(s);
1232    table.push(0);
1233    off
1234}
1235
1236// ── tests ──────────────────────────────────────────────────────────────────
1237
1238#[cfg(test)]
1239mod tests {
1240    use super::*;
1241
1242    fn make_obj(fmt: ObjectFormat, code: Vec<u8>) -> ObjectFile {
1243        let section_name = match fmt {
1244            ObjectFormat::Elf => ".text",
1245            ObjectFormat::MachO => "__text",
1246            ObjectFormat::Coff => ".text",
1247        };
1248        ObjectFile {
1249            format: fmt,
1250            elf_machine: 62,
1251            coff_machine: 0x8664,
1252            sections: vec![Section {
1253                name: section_name.into(),
1254                data: code,
1255                relocs: vec![],
1256                debug_rows: vec![],
1257            }],
1258            symbols: vec![Symbol {
1259                name: "f".into(),
1260                section: 0,
1261                offset: 0,
1262                size: 1,
1263                global: true,
1264            }],
1265        }
1266    }
1267
1268    #[test]
1269    fn elf_magic_and_class() {
1270        let bytes = make_obj(ObjectFormat::Elf, vec![0x90]).to_bytes();
1271        assert_eq!(&bytes[0..4], b"\x7fELF", "ELF magic");
1272        assert_eq!(bytes[4], 2, "64-bit");
1273        assert_eq!(bytes[5], 1, "little-endian");
1274    }
1275
1276    #[test]
1277    fn elf_machine_x86_64() {
1278        let bytes = make_obj(ObjectFormat::Elf, vec![0x90]).to_bytes();
1279        let e_machine = u16::from_le_bytes([bytes[18], bytes[19]]);
1280        assert_eq!(e_machine, 62, "EM_X86_64 = 62");
1281    }
1282
1283    #[test]
1284    fn elf_relocatable_type() {
1285        let bytes = make_obj(ObjectFormat::Elf, vec![0x90]).to_bytes();
1286        let e_type = u16::from_le_bytes([bytes[16], bytes[17]]);
1287        assert_eq!(e_type, 1, "ET_REL = 1");
1288    }
1289
1290    #[test]
1291    fn macho_magic() {
1292        let bytes = make_obj(ObjectFormat::MachO, vec![0xc3]).to_bytes();
1293        assert_eq!(&bytes[0..4], &[0xcf, 0xfa, 0xed, 0xfe], "MH_MAGIC_64");
1294    }
1295
1296    #[test]
1297    fn macho_filetype_object() {
1298        let bytes = make_obj(ObjectFormat::MachO, vec![0xc3]).to_bytes();
1299        let filetype = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
1300        assert_eq!(filetype, 1, "MH_OBJECT = 1");
1301    }
1302
1303    #[test]
1304    fn macho_strtab_first_byte_is_null() {
1305        // Issue #37: Mach-O string table byte 0 must be \0 (null), not ' ' (space).
1306        // The string table is the last thing written in the object file.
1307        // For symbol "f", strtab = [\0, '_', 'f', \0] (4 bytes, aligned).
1308        // We verify the first byte of strtab (= last 4 bytes of the file) is \0.
1309        let bytes = make_obj(ObjectFormat::MachO, vec![0xc3]).to_bytes();
1310        // strtab is padded to 4 bytes: [\0, '_', 'f', \0] = 4 bytes.
1311        let strtab_first = bytes[bytes.len() - 4];
1312        assert_eq!(
1313            strtab_first, 0x00,
1314            "Mach-O strtab[0] must be null (\\0), was 0x{:02x}",
1315            strtab_first
1316        );
1317    }
1318
1319    #[test]
1320    fn coff_machine_x86_64() {
1321        let bytes = make_obj(ObjectFormat::Coff, vec![0x90]).to_bytes();
1322        let machine = u16::from_le_bytes([bytes[0], bytes[1]]);
1323        assert_eq!(machine, 0x8664, "IMAGE_FILE_MACHINE_AMD64");
1324    }
1325
1326    #[test]
1327    fn coff_has_text_section_header() {
1328        let bytes = make_obj(ObjectFormat::Coff, vec![0x90]).to_bytes();
1329        let sec_count = u16::from_le_bytes([bytes[2], bytes[3]]) as usize;
1330        assert_eq!(sec_count, 1);
1331        let sec_name = &bytes[20..28];
1332        assert_eq!(sec_name, b".text\0\0\0");
1333    }
1334
1335    #[test]
1336    fn emit_object_roundtrip() {
1337        use crate::isel::{MachineBlock, MachineFunction};
1338
1339        struct NopEmitter;
1340        impl Emitter for NopEmitter {
1341            fn emit_function(&mut self, mf: &MachineFunction) -> Section {
1342                let _ = mf;
1343                Section {
1344                    name: ".text".into(),
1345                    data: vec![0x90],
1346                    relocs: vec![],
1347                    debug_rows: vec![],
1348                }
1349            }
1350            fn object_format(&self) -> ObjectFormat {
1351                ObjectFormat::Elf
1352            }
1353        }
1354
1355        let mut mf = MachineFunction::new("test".into());
1356        mf.blocks.push(MachineBlock {
1357            label: "entry".into(),
1358            instrs: vec![],
1359        });
1360        let obj = emit_object(&mf, &mut NopEmitter);
1361        assert_eq!(obj.symbols[0].name, "test");
1362        assert_eq!(obj.sections[0].data, vec![0x90]);
1363        assert_eq!(obj.elf_machine, 62);
1364        assert_eq!(obj.coff_machine, 0x8664);
1365    }
1366
1367    #[test]
1368    fn emit_object_adds_debug_line_section_for_elf() {
1369        use crate::isel::{MachineBlock, MachineFunction};
1370
1371        struct NopEmitter;
1372        impl Emitter for NopEmitter {
1373            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1374                Section {
1375                    name: ".text".into(),
1376                    data: vec![0x90],
1377                    relocs: vec![],
1378                    debug_rows: vec![],
1379                }
1380            }
1381            fn object_format(&self) -> ObjectFormat {
1382                ObjectFormat::Elf
1383            }
1384        }
1385
1386        let mut mf = MachineFunction::new("dbg".into());
1387        mf.blocks.push(MachineBlock {
1388            label: "entry".into(),
1389            instrs: vec![],
1390        });
1391        mf.debug_source = Some("foo.c".into());
1392        mf.debug_line_start = Some(17);
1393
1394        let obj = emit_object(&mf, &mut NopEmitter);
1395        assert!(obj.sections.iter().any(|s| s.name == ".debug_line"));
1396        assert!(obj.sections.iter().any(|s| s.name == ".debug_info"));
1397        assert!(obj.sections.iter().any(|s| s.name == ".debug_abbrev"));
1398        assert!(obj.sections.iter().any(|s| s.name == ".debug_loclists"));
1399        let bytes = obj.to_bytes();
1400        assert!(bytes.windows(11).any(|w| w == b".debug_line"));
1401        assert!(bytes.windows(11).any(|w| w == b".debug_info"));
1402        assert!(bytes.windows(13).any(|w| w == b".debug_abbrev"));
1403        assert!(bytes.windows(15).any(|w| w == b".debug_loclists"));
1404    }
1405
1406    #[test]
1407    fn emit_object_adds_eh_frame_for_elf() {
1408        use crate::isel::{MachineBlock, MachineFunction};
1409
1410        struct NopEmitter;
1411        impl Emitter for NopEmitter {
1412            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1413                Section {
1414                    name: ".text".into(),
1415                    data: vec![0x90, 0xC3],
1416                    relocs: vec![],
1417                    debug_rows: vec![],
1418                }
1419            }
1420            fn object_format(&self) -> ObjectFormat {
1421                ObjectFormat::Elf
1422            }
1423        }
1424
1425        let mut mf = MachineFunction::new("eh".into());
1426        mf.blocks.push(MachineBlock {
1427            label: "entry".into(),
1428            instrs: vec![],
1429        });
1430
1431        let obj = emit_object(&mf, &mut NopEmitter);
1432        let eh = obj
1433            .sections
1434            .iter()
1435            .find(|s| s.name == ".eh_frame")
1436            .expect(".eh_frame section");
1437        assert!(!eh.data.is_empty());
1438        let bytes = obj.to_bytes();
1439        assert!(bytes.windows(9).any(|w| w == b".eh_frame"));
1440    }
1441
1442    #[test]
1443    fn emit_object_adds_unwind_tables_for_coff() {
1444        use crate::isel::{MachineBlock, MachineFunction};
1445
1446        struct NopEmitter;
1447        impl Emitter for NopEmitter {
1448            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1449                Section {
1450                    name: ".text".into(),
1451                    data: vec![0x90],
1452                    relocs: vec![],
1453                    debug_rows: vec![],
1454                }
1455            }
1456            fn object_format(&self) -> ObjectFormat {
1457                ObjectFormat::Coff
1458            }
1459        }
1460
1461        let mut mf = MachineFunction::new("seh".into());
1462        mf.blocks.push(MachineBlock {
1463            label: "entry".into(),
1464            instrs: vec![],
1465        });
1466
1467        let obj = emit_object(&mf, &mut NopEmitter);
1468        assert!(obj.sections.iter().any(|s| s.name == ".xdata"));
1469        assert!(obj.sections.iter().any(|s| s.name == ".pdata"));
1470        let bytes = obj.to_bytes();
1471        assert!(bytes.windows(6).any(|w| w == b".xdata"));
1472        assert!(bytes.windows(6).any(|w| w == b".pdata"));
1473    }
1474
1475    #[test]
1476    fn eh_frame_reflects_frame_facts_when_present() {
1477        use crate::isel::{MachineBlock, MachineFunction};
1478
1479        struct NopEmitter;
1480        impl Emitter for NopEmitter {
1481            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1482                Section {
1483                    name: ".text".into(),
1484                    data: vec![0x90, 0x90, 0xC3],
1485                    relocs: vec![],
1486                    debug_rows: vec![],
1487                }
1488            }
1489            fn object_format(&self) -> ObjectFormat {
1490                ObjectFormat::Elf
1491            }
1492        }
1493
1494        let mut mf = MachineFunction::new("eh-facts".into());
1495        mf.blocks.push(MachineBlock {
1496            label: "entry".into(),
1497            instrs: vec![],
1498        });
1499        mf.frame_size = 16;
1500        mf.used_callee_saved = vec![PReg(3), PReg(12)]; // rbx, r12
1501
1502        let obj = emit_object(&mf, &mut NopEmitter);
1503        let eh = obj
1504            .sections
1505            .iter()
1506            .find(|s| s.name == ".eh_frame")
1507            .expect(".eh_frame section")
1508            .data
1509            .clone();
1510
1511        // Expect def_cfa_offset opcode (0x0e) in FDE program when frame facts exist.
1512        assert!(eh.contains(&0x0e), "expected DW_CFA_def_cfa_offset in frame-aware FDE");
1513        // Expect def_cfa_register opcode (0x0d) when frame pointer model is active.
1514        assert!(eh.contains(&0x0d), "expected DW_CFA_def_cfa_register in frame-aware FDE");
1515    }
1516
1517    #[test]
1518    fn eh_frame_has_expected_cie_fde_shape() {
1519        use crate::isel::{MachineBlock, MachineFunction};
1520
1521        struct NopEmitter;
1522        impl Emitter for NopEmitter {
1523            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1524                Section {
1525                    name: ".text".into(),
1526                    data: vec![0x90, 0x90, 0xC3],
1527                    relocs: vec![],
1528                    debug_rows: vec![],
1529                }
1530            }
1531            fn object_format(&self) -> ObjectFormat {
1532                ObjectFormat::Elf
1533            }
1534        }
1535
1536        let mut mf = MachineFunction::new("eh-shape".into());
1537        mf.blocks.push(MachineBlock {
1538            label: "entry".into(),
1539            instrs: vec![],
1540        });
1541
1542        let obj = emit_object(&mf, &mut NopEmitter);
1543        let eh = obj
1544            .sections
1545            .iter()
1546            .find(|s| s.name == ".eh_frame")
1547            .expect(".eh_frame section")
1548            .data
1549            .clone();
1550
1551        // CIE starts at offset 0.
1552        let cie_len = u32::from_le_bytes([eh[0], eh[1], eh[2], eh[3]]) as usize;
1553        assert!(cie_len > 8, "CIE should have payload");
1554        let cie_id = u32::from_le_bytes([eh[4], eh[5], eh[6], eh[7]]);
1555        assert_eq!(cie_id, 0, "CIE id must be zero");
1556        assert_eq!(eh[8], 1, "CIE version");
1557        assert!(eh.windows(3).any(|w| w == b"zR\0"), "CIE augmentation zR");
1558
1559        // FDE starts at aligned boundary after first record.
1560        let fde_off = (4 + cie_len + 7) & !7;
1561        let fde_len = u32::from_le_bytes([eh[fde_off], eh[fde_off + 1], eh[fde_off + 2], eh[fde_off + 3]]) as usize;
1562        assert!(fde_len >= 12, "FDE should contain init loc + range + aug len");
1563
1564        // FDE payload: [CIE ptr][init loc][range][aug-len]
1565        let range_off = fde_off + 4 + 4 + 4;
1566        let range = u32::from_le_bytes([eh[range_off], eh[range_off + 1], eh[range_off + 2], eh[range_off + 3]]);
1567        assert_eq!(range, 3, "FDE range should match text size");
1568
1569        // .eh_frame terminator record exists.
1570        assert_eq!(&eh[eh.len() - 4..], &[0, 0, 0, 0]);
1571    }
1572
1573    #[test]
1574    fn coff_unwind_tables_encode_frame_facts_when_present() {
1575        use crate::isel::{MachineBlock, MachineFunction};
1576
1577        struct NopEmitter;
1578        impl Emitter for NopEmitter {
1579            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1580                Section {
1581                    name: ".text".into(),
1582                    data: vec![0x90, 0x90, 0xC3],
1583                    relocs: vec![],
1584                    debug_rows: vec![],
1585                }
1586            }
1587            fn object_format(&self) -> ObjectFormat {
1588                ObjectFormat::Coff
1589            }
1590        }
1591
1592        let mut mf = MachineFunction::new("coff-unwind-facts".into());
1593        mf.blocks.push(MachineBlock {
1594            label: "entry".into(),
1595            instrs: vec![],
1596        });
1597        mf.frame_size = 16;
1598        mf.used_callee_saved = vec![PReg(3)]; // rbx
1599
1600        let obj = emit_object(&mf, &mut NopEmitter);
1601        let xdata = obj
1602            .sections
1603            .iter()
1604            .find(|s| s.name == ".xdata")
1605            .expect(".xdata section")
1606            .data
1607            .clone();
1608        assert!(xdata.len() >= 8, "unwind info should include at least one code slot");
1609        assert_eq!(xdata[0] & 0x7, 1, "UNWIND_INFO version 1");
1610        assert!(xdata[2] >= 1, "count_of_codes should be non-zero when frame facts exist");
1611        assert_eq!(xdata[3] & 0x0f, 5, "frame register should be RBP");
1612    }
1613
1614    #[test]
1615    fn coff_unwind_tables_have_expected_layout() {
1616        use crate::isel::{MachineBlock, MachineFunction};
1617
1618        struct NopEmitter;
1619        impl Emitter for NopEmitter {
1620            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1621                Section {
1622                    name: ".text".into(),
1623                    data: vec![0x90, 0xC3],
1624                    relocs: vec![],
1625                    debug_rows: vec![],
1626                }
1627            }
1628            fn object_format(&self) -> ObjectFormat {
1629                ObjectFormat::Coff
1630            }
1631        }
1632
1633        let mut mf = MachineFunction::new("coff-unwind-shape".into());
1634        mf.blocks.push(MachineBlock {
1635            label: "entry".into(),
1636            instrs: vec![],
1637        });
1638
1639        let obj = emit_object(&mf, &mut NopEmitter);
1640        let xdata = obj
1641            .sections
1642            .iter()
1643            .find(|s| s.name == ".xdata")
1644            .expect(".xdata section")
1645            .data
1646            .clone();
1647        let pdata = obj
1648            .sections
1649            .iter()
1650            .find(|s| s.name == ".pdata")
1651            .expect(".pdata section")
1652            .data
1653            .clone();
1654
1655        assert_eq!(xdata.len(), 4, "minimal UNWIND_INFO header");
1656        assert_eq!(xdata[0] & 0x7, 1, "UNWIND_INFO version 1");
1657        assert_eq!(pdata.len(), 12, "single RUNTIME_FUNCTION entry");
1658
1659        let begin = u32::from_le_bytes([pdata[0], pdata[1], pdata[2], pdata[3]]);
1660        let end = u32::from_le_bytes([pdata[4], pdata[5], pdata[6], pdata[7]]);
1661        let unwind_info_rva = u32::from_le_bytes([pdata[8], pdata[9], pdata[10], pdata[11]]);
1662
1663        assert_eq!(begin, 0);
1664        assert_eq!(end, 2, "runtime function end should match text size");
1665        assert_eq!(unwind_info_rva, 0, "points at section-local xdata start in this object model");
1666    }
1667
1668    #[test]
1669    fn emit_object_adds_debug_s_section_for_coff() {
1670        use crate::isel::{MachineBlock, MachineFunction};
1671
1672        struct NopEmitter;
1673        impl Emitter for NopEmitter {
1674            fn emit_function(&mut self, _mf: &MachineFunction) -> Section {
1675                Section {
1676                    name: ".text".into(),
1677                    data: vec![0x90],
1678                    relocs: vec![],
1679                    debug_rows: vec![],
1680                }
1681            }
1682            fn object_format(&self) -> ObjectFormat {
1683                ObjectFormat::Coff
1684            }
1685        }
1686
1687        let mut mf = MachineFunction::new("dbg".into());
1688        mf.blocks.push(MachineBlock {
1689            label: "entry".into(),
1690            instrs: vec![],
1691        });
1692        mf.debug_source = Some("foo.c".into());
1693        mf.debug_line_start = Some(17);
1694
1695        let obj = emit_object(&mf, &mut NopEmitter);
1696        assert!(obj.sections.iter().any(|s| s.name == ".debug$S"));
1697        let bytes = obj.to_bytes();
1698        assert!(bytes.windows(8).any(|w| w == b".debug$S"));
1699    }
1700}