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