1 use crate::prelude::*;
2 use core::mem::size_of;
3 use object::elf::*;
4 use object::endian::{BigEndian, Endian, Endianness, LittleEndian};
5 use object::read::elf::{FileHeader, SectionHeader};
6 use object::{
7     File, NativeEndian as NE, Object, ObjectSection, ObjectSymbol, RelocationEncoding,
8     RelocationKind, RelocationTarget, U64Bytes,
9 };
10 use wasmtime_environ::obj;
11 
12 pub(crate) fn create_gdbjit_image(
13     mut bytes: Vec<u8>,
14     code_region: (*const u8, usize),
15 ) -> Result<Vec<u8>, Error> {
16     let e = ensure_supported_elf_format(&bytes)?;
17 
18     // patch relocs
19     relocate_dwarf_sections(&mut bytes, code_region)?;
20 
21     // elf is still missing details...
22     match e {
23         Endianness::Little => {
24             convert_object_elf_to_loadable_file::<LittleEndian>(&mut bytes, code_region)
25         }
26         Endianness::Big => {
27             convert_object_elf_to_loadable_file::<BigEndian>(&mut bytes, code_region)
28         }
29     }
30 
31     Ok(bytes)
32 }
33 
34 fn relocate_dwarf_sections(bytes: &mut [u8], code_region: (*const u8, usize)) -> Result<(), Error> {
35     let mut relocations = Vec::new();
36     let obj = File::parse(&bytes[..]).map_err(obj::ObjectCrateErrorWrapper)?;
37     for section in obj.sections() {
38         let section_start = match section.file_range() {
39             Some((start, _)) => start,
40             None => continue,
41         };
42         for (off, r) in section.relocations() {
43             if r.kind() != RelocationKind::Absolute
44                 || r.encoding() != RelocationEncoding::Generic
45                 || r.size() != 64
46             {
47                 continue;
48             }
49 
50             let sym = match r.target() {
51                 RelocationTarget::Symbol(index) => match obj.symbol_by_index(index) {
52                     Ok(sym) => sym,
53                     Err(_) => continue,
54                 },
55                 _ => continue,
56             };
57             relocations.push((
58                 section_start + off,
59                 (code_region.0 as u64)
60                     .wrapping_add(sym.address())
61                     .wrapping_add(r.addend() as u64),
62             ));
63         }
64     }
65 
66     for (offset, value) in relocations {
67         let (loc, _) = offset
68             .try_into()
69             .ok()
70             .and_then(|offset| object::from_bytes_mut::<U64Bytes<NE>>(&mut bytes[offset..]).ok())
71             .ok_or_else(|| format_err!("invalid dwarf relocations"))?;
72         loc.set(NE, value);
73     }
74     Ok(())
75 }
76 
77 fn ensure_supported_elf_format(bytes: &[u8]) -> Result<Endianness, Error> {
78     use object::elf::*;
79     use object::read::elf::*;
80 
81     let kind = match object::FileKind::parse(bytes) {
82         Ok(file) => file,
83         Err(err) => {
84             bail!("Failed to parse file: {err}");
85         }
86     };
87     let header = match kind {
88         object::FileKind::Elf64 => match object::elf::FileHeader64::<Endianness>::parse(bytes) {
89             Ok(header) => header,
90             Err(err) => {
91                 bail!("Unsupported ELF file: {err}");
92             }
93         },
94         _ => {
95             bail!("only 64-bit ELF files currently supported")
96         }
97     };
98     let e = header.endian().unwrap();
99 
100     match header.e_machine.get(e) {
101         EM_AARCH64 => (),
102         EM_X86_64 => (),
103         EM_S390 => (),
104         EM_RISCV => (),
105         machine => {
106             bail!("Unsupported ELF target machine: {machine:x}");
107         }
108     }
109     ensure!(
110         header.e_phoff.get(e) == 0 && header.e_phnum.get(e) == 0,
111         "program header table is empty"
112     );
113     let e_shentsize = header.e_shentsize.get(e);
114     let req_shentsize = match e {
115         Endianness::Little => size_of::<SectionHeader64<LittleEndian>>(),
116         Endianness::Big => size_of::<SectionHeader64<BigEndian>>(),
117     };
118     ensure!(e_shentsize as usize == req_shentsize, "size of sh");
119     Ok(e)
120 }
121 
122 fn convert_object_elf_to_loadable_file<E: Endian>(
123     bytes: &mut Vec<u8>,
124     code_region: (*const u8, usize),
125 ) {
126     let e = E::default();
127 
128     let header = FileHeader64::<E>::parse(&bytes[..]).unwrap();
129     let sections = header.sections(e, &bytes[..]).unwrap();
130     let text_range = match sections.section_by_name(e, b".text") {
131         Some((i, text)) => {
132             let range = text.file_range(e);
133             let e_shoff = usize::try_from(header.e_shoff.get(e)).unwrap();
134             let off = e_shoff + i.0 * header.e_shentsize.get(e) as usize;
135 
136             let section: &mut SectionHeader64<E> =
137                 object::from_bytes_mut(&mut bytes[off..]).unwrap().0;
138             // Patch vaddr, and save file location and its size.
139             section.sh_addr.set(e, code_region.0 as u64);
140             range
141         }
142         None => None,
143     };
144 
145     // LLDB wants segment with virtual address set, placing them at the end of ELF.
146     let ph_off = bytes.len();
147     let e_phentsize = size_of::<ProgramHeader64<E>>();
148     let e_phnum = 1;
149     bytes.resize(ph_off + e_phentsize * e_phnum, 0);
150     if let Some((sh_offset, sh_size)) = text_range {
151         let (v_offset, size) = code_region;
152         let program: &mut ProgramHeader64<E> =
153             object::from_bytes_mut(&mut bytes[ph_off..]).unwrap().0;
154         program.p_type.set(e, PT_LOAD);
155         program.p_offset.set(e, sh_offset);
156         program.p_vaddr.set(e, v_offset as u64);
157         program.p_paddr.set(e, v_offset as u64);
158         program.p_filesz.set(e, sh_size);
159         program.p_memsz.set(e, size as u64);
160     } else {
161         unreachable!();
162     }
163 
164     // It is somewhat loadable ELF file at this moment.
165     let header: &mut FileHeader64<E> = object::from_bytes_mut(bytes).unwrap().0;
166     header.e_type.set(e, ET_DYN);
167     header.e_phoff.set(e, ph_off as u64);
168     header
169         .e_phentsize
170         .set(e, u16::try_from(e_phentsize).unwrap());
171     header.e_phnum.set(e, u16::try_from(e_phnum).unwrap());
172 }
173