1 //! Debug utils for WebAssembly using Cranelift.
2 
3 #![allow(clippy::cast_ptr_alignment)]
4 
5 use anyhow::Error;
6 use faerie::{Artifact, Decl};
7 use gimli::write::{Address, FrameTable};
8 use more_asserts::assert_gt;
9 use target_lexicon::BinaryFormat;
10 use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
11 use wasmtime_environ::{Compilation, ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
12 
13 pub use crate::read_debuginfo::{read_debuginfo, DebugInfoData, WasmFileInfo};
14 pub use crate::transform::transform_dwarf;
15 pub use crate::write_debuginfo::{emit_dwarf, ResolvedSymbol, SymbolResolver};
16 
17 mod gc;
18 mod read_debuginfo;
19 mod transform;
20 mod write_debuginfo;
21 
22 struct FunctionRelocResolver {}
23 impl SymbolResolver for FunctionRelocResolver {
resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol24     fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
25         let name = format!("_wasm_function_{}", symbol);
26         ResolvedSymbol::Reloc { name, addend }
27     }
28 }
29 
create_frame_table<'a>( isa: &dyn TargetIsa, infos: impl Iterator<Item = &'a Option<UnwindInfo>>, ) -> Option<FrameTable>30 fn create_frame_table<'a>(
31     isa: &dyn TargetIsa,
32     infos: impl Iterator<Item = &'a Option<UnwindInfo>>,
33 ) -> Option<FrameTable> {
34     let mut table = FrameTable::default();
35 
36     let cie_id = table.add_cie(isa.create_systemv_cie()?);
37 
38     for (i, info) in infos.enumerate() {
39         if let Some(UnwindInfo::SystemV(info)) = info {
40             table.add_fde(
41                 cie_id,
42                 info.to_fde(Address::Symbol {
43                     symbol: i,
44                     addend: 0,
45                 }),
46             );
47         }
48     }
49 
50     Some(table)
51 }
52 
emit_debugsections( obj: &mut Artifact, vmctx_info: &ModuleVmctxInfo, isa: &dyn TargetIsa, debuginfo_data: &DebugInfoData, at: &ModuleAddressMap, ranges: &ValueLabelsRanges, compilation: &Compilation, ) -> Result<(), Error>53 pub fn emit_debugsections(
54     obj: &mut Artifact,
55     vmctx_info: &ModuleVmctxInfo,
56     isa: &dyn TargetIsa,
57     debuginfo_data: &DebugInfoData,
58     at: &ModuleAddressMap,
59     ranges: &ValueLabelsRanges,
60     compilation: &Compilation,
61 ) -> Result<(), Error> {
62     let resolver = FunctionRelocResolver {};
63     let dwarf = transform_dwarf(isa, debuginfo_data, at, vmctx_info, ranges)?;
64     let frame_table = create_frame_table(isa, compilation.into_iter().map(|f| &f.unwind_info));
65 
66     emit_dwarf(obj, dwarf, &resolver, frame_table)?;
67     Ok(())
68 }
69 
70 struct ImageRelocResolver<'a> {
71     func_offsets: &'a Vec<u64>,
72 }
73 
74 impl<'a> SymbolResolver for ImageRelocResolver<'a> {
resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol75     fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
76         let func_start = self.func_offsets[symbol];
77         ResolvedSymbol::PhysicalAddress(func_start + addend as u64)
78     }
79 }
80 
emit_debugsections_image( isa: &dyn TargetIsa, debuginfo_data: &DebugInfoData, vmctx_info: &ModuleVmctxInfo, at: &ModuleAddressMap, ranges: &ValueLabelsRanges, funcs: &[(*const u8, usize)], compilation: &Compilation, ) -> Result<Vec<u8>, Error>81 pub fn emit_debugsections_image(
82     isa: &dyn TargetIsa,
83     debuginfo_data: &DebugInfoData,
84     vmctx_info: &ModuleVmctxInfo,
85     at: &ModuleAddressMap,
86     ranges: &ValueLabelsRanges,
87     funcs: &[(*const u8, usize)],
88     compilation: &Compilation,
89 ) -> Result<Vec<u8>, Error> {
90     let func_offsets = &funcs
91         .iter()
92         .map(|(ptr, _)| *ptr as u64)
93         .collect::<Vec<u64>>();
94     let mut obj = Artifact::new(isa.triple().clone(), String::from("module"));
95     let resolver = ImageRelocResolver { func_offsets };
96     let dwarf = transform_dwarf(isa, debuginfo_data, at, vmctx_info, ranges)?;
97 
98     // Assuming all functions in the same code block, looking min/max of its range.
99     assert_gt!(funcs.len(), 0);
100     let mut segment_body: (usize, usize) = (!0, 0);
101     for (body_ptr, body_len) in funcs {
102         segment_body.0 = std::cmp::min(segment_body.0, *body_ptr as usize);
103         segment_body.1 = std::cmp::max(segment_body.1, *body_ptr as usize + body_len);
104     }
105     let segment_body = (segment_body.0 as *const u8, segment_body.1 - segment_body.0);
106 
107     let body = unsafe { std::slice::from_raw_parts(segment_body.0, segment_body.1) };
108     obj.declare_with("all", Decl::function(), body.to_vec())?;
109 
110     let frame_table = create_frame_table(isa, compilation.into_iter().map(|f| &f.unwind_info));
111     emit_dwarf(&mut obj, dwarf, &resolver, frame_table)?;
112 
113     // LLDB is too "magical" about mach-o, generating elf
114     let mut bytes = obj.emit_as(BinaryFormat::Elf)?;
115     // elf is still missing details...
116     convert_faerie_elf_to_loadable_file(&mut bytes, segment_body.0);
117 
118     // let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
119     // ::std::io::Write::write(&mut file, &bytes).expect("write");
120 
121     Ok(bytes)
122 }
123 
convert_faerie_elf_to_loadable_file(bytes: &mut Vec<u8>, code_ptr: *const u8)124 fn convert_faerie_elf_to_loadable_file(bytes: &mut Vec<u8>, code_ptr: *const u8) {
125     use std::ffi::CStr;
126     use std::os::raw::c_char;
127 
128     assert!(
129         bytes[0x4] == 2 && bytes[0x5] == 1,
130         "bits and endianess in .ELF"
131     );
132     let e_phoff = unsafe { *(bytes.as_ptr().offset(0x20) as *const u64) };
133     let e_phnum = unsafe { *(bytes.as_ptr().offset(0x38) as *const u16) };
134     assert!(
135         e_phoff == 0 && e_phnum == 0,
136         "program header table is empty"
137     );
138     let e_phentsize = unsafe { *(bytes.as_ptr().offset(0x36) as *const u16) };
139     assert_eq!(e_phentsize, 0x38, "size of ph");
140     let e_shentsize = unsafe { *(bytes.as_ptr().offset(0x3A) as *const u16) };
141     assert_eq!(e_shentsize, 0x40, "size of sh");
142 
143     let e_shoff = unsafe { *(bytes.as_ptr().offset(0x28) as *const u64) };
144     let e_shnum = unsafe { *(bytes.as_ptr().offset(0x3C) as *const u16) };
145     let mut shstrtab_off = 0;
146     let mut segment = None;
147     for i in 0..e_shnum {
148         let off = e_shoff as isize + i as isize * e_shentsize as isize;
149         let sh_type = unsafe { *(bytes.as_ptr().offset(off + 0x4) as *const u32) };
150         if sh_type == /* SHT_SYMTAB */ 3 {
151             shstrtab_off = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
152         }
153         if sh_type != /* SHT_PROGBITS */ 1 {
154             continue;
155         }
156         // It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
157         let sh_name = unsafe {
158             let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
159             CStr::from_ptr(
160                 bytes
161                     .as_ptr()
162                     .offset((shstrtab_off + sh_name_off as u64) as isize)
163                     as *const c_char,
164             )
165             .to_str()
166             .expect("name")
167         };
168         if sh_name != ".text.all" {
169             continue;
170         }
171 
172         assert!(segment.is_none());
173         // Functions was added at emit_debugsections_image as .text.all.
174         // Patch vaddr, and save file location and its size.
175         unsafe {
176             *(bytes.as_ptr().offset(off + 0x10) as *mut u64) = code_ptr as u64;
177         };
178         let sh_offset = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
179         let sh_size = unsafe { *(bytes.as_ptr().offset(off + 0x20) as *const u64) };
180         segment = Some((sh_offset, code_ptr, sh_size));
181         // Fix name too: cut it to just ".text"
182         unsafe {
183             let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
184             bytes[(shstrtab_off + sh_name_off as u64) as usize + ".text".len()] = 0;
185         }
186     }
187 
188     // LLDB wants segment with virtual address set, placing them at the end of ELF.
189     let ph_off = bytes.len();
190     if let Some((sh_offset, v_offset, sh_size)) = segment {
191         let segment = vec![0; 0x38];
192         unsafe {
193             *(segment.as_ptr() as *mut u32) = /* PT_LOAD */ 0x1;
194             *(segment.as_ptr().offset(0x8) as *mut u64) = sh_offset;
195             *(segment.as_ptr().offset(0x10) as *mut u64) = v_offset as u64;
196             *(segment.as_ptr().offset(0x18) as *mut u64) = v_offset as u64;
197             *(segment.as_ptr().offset(0x20) as *mut u64) = sh_size;
198             *(segment.as_ptr().offset(0x28) as *mut u64) = sh_size;
199         }
200         bytes.extend_from_slice(&segment);
201     } else {
202         unreachable!();
203     }
204 
205     // It is somewhat loadable ELF file at this moment.
206     // Update e_flags, e_phoff and e_phnum.
207     unsafe {
208         *(bytes.as_ptr().offset(0x10) as *mut u16) = /* ET_DYN */ 3;
209         *(bytes.as_ptr().offset(0x20) as *mut u64) = ph_off as u64;
210         *(bytes.as_ptr().offset(0x38) as *mut u16) = 1 as u16;
211     }
212 }
213