1use 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#[derive(Debug, Clone)]
12pub struct ObjectWriteOptions {
13 pub emit_minimal_dwarf_sections: bool,
15 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#[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#[derive(Debug, Clone)]
41pub struct ExternalReloc {
42 pub offset: usize,
43 pub symbol: String,
44}
45
46pub 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
60pub trait ObjectWriter {
62 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#[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)?; 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)?; w.write_all(§ion_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)?; Ok(())
312}
313
314fn 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
334fn 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]; 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 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 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 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); symtab.extend_from_slice(&e);
392 }
393
394 (symtab, strtab, first_global_index, extern_base_index)
395}
396
397fn build_rela_text(
399 relocations: &[ExternalReloc],
400 extern_names: &[&str],
401 extern_base_index: u32,
402 e_machine: u16,
403) -> Vec<u8> {
404 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, _ => 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()); out.extend_from_slice(&r_info.to_le_bytes()); out.extend_from_slice(&(-4i64).to_le_bytes()); }
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 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 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 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 write_elf64_section_header(&mut f, 0, SHT_NULL, 0, 0, 0, 0)?;
588 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 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, info: TEXT_SECTION_INDEX as u32, addralign: 8,
611 entsize: ELF64_RELA_SIZE,
612 },
613 )?;
614 }
615 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 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 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 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 let exported: Vec<&ObjectSymbol> = symbols
747 .iter()
748 .filter(|s| s.global && !s.name.starts_with(".L"))
749 .collect();
750
751 let mut strtab: Vec<u8> = vec![0u8; 4]; 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(§ion.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 push_sym(&mut sym_bytes, &mut strtab, ".text", 0, 1, 0, IMAGE_SYM_CLASS_STATIC);
778 for sym in &exported {
780 push_sym(&mut sym_bytes, &mut strtab, &sym.name,
781 sym.value as u32, 1, 0x20, 2 );
782 }
783 let extern_base_idx = 1 + exported.len(); 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 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()); reloc_bytes.extend_from_slice(&(sym_idx as u32).to_le_bytes()); reloc_bytes.extend_from_slice(&IMAGE_REL_AMD64_REL32.to_le_bytes()); }
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 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 f.write_all(text_name).map_err(map)?;
833 f.write_all(&0u32.to_le_bytes()).map_err(map)?; f.write_all(&0u32.to_le_bytes()).map_err(map)?; f.write_all(&padded_len.to_le_bytes()).map_err(map)?; f.write_all(&(raw_data_off as u32).to_le_bytes()).map_err(map)?; if n_relocs > 0 {
838 f.write_all(&(reloc_off as u32).to_le_bytes()).map_err(map)?; } else {
840 f.write_all(&0u32.to_le_bytes()).map_err(map)?;
841 }
842 f.write_all(&0u32.to_le_bytes()).map_err(map)?; f.write_all(&n_relocs.to_le_bytes()).map_err(map)?; f.write_all(&0u16.to_le_bytes()).map_err(map)?; f.write_all(&sec_flags.to_le_bytes()).map_err(map)?; 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 if !reloc_bytes.is_empty() {
856 f.write_all(&reloc_bytes).map_err(map)?;
857 }
858
859 f.write_all(&sym_bytes).map_err(map)?;
861 f.write_all(&strtab).map_err(map)?;
862
863 Ok(())
864 }
865}
866
867pub 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 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: §ions, 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: §ions, 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}