wasm_debug/
lib.rs

1//! Wasm DWARF transformation utilites.
2//!
3//! To use this, see functions like [`emit_debugsections_image`] and the
4//! [`types`] module.
5//!
6//! Many of the types exposed are equivilent to those in the `cranelift-codegen`
7//! crate, but maintaining exact compatibilty with `cranelift-codegen` is not
8//! a goal of this crate.
9//!
10//! Types may change in future releases if changes are needed to make this crate
11//! more generic.
12//!
13//! If you're interested in integrating your runtime with debuggers and this
14//! crate doesn't meet your needs, please file an issue and we can discuss how
15//! to adapt this crate to behave as needed.
16//!
17//! More documentation to come in future releases.
18
19#![allow(clippy::cast_ptr_alignment)]
20
21use crate::types::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
22use anyhow::Error;
23use faerie::{Artifact, Decl};
24use more_asserts::assert_gt;
25use target_lexicon::{BinaryFormat, Triple};
26
27pub use crate::read_debuginfo::{read_debuginfo, DebugInfoData, WasmFileInfo};
28pub use crate::transform::transform_dwarf;
29pub use crate::write_debuginfo::{emit_dwarf, ResolvedSymbol, SymbolResolver};
30
31mod gc;
32mod read_debuginfo;
33mod transform;
34pub mod types;
35mod write_debuginfo;
36
37struct FunctionRelocResolver {}
38impl SymbolResolver for FunctionRelocResolver {
39    fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
40        let name = format!("_wasm_function_{}", symbol);
41        ResolvedSymbol::Reloc { name, addend }
42    }
43}
44
45pub fn emit_debugsections(
46    obj: &mut Artifact,
47    vmctx_info: &ModuleVmctxInfo,
48    pointer_width_bytes: u8,
49    debuginfo_data: &DebugInfoData,
50    at: &ModuleAddressMap,
51    ranges: &ValueLabelsRanges,
52) -> Result<(), Error> {
53    let resolver = FunctionRelocResolver {};
54    let dwarf = transform_dwarf(pointer_width_bytes, debuginfo_data, at, vmctx_info, ranges)?;
55    emit_dwarf(obj, dwarf, &resolver)?;
56    Ok(())
57}
58
59struct ImageRelocResolver<'a> {
60    func_offsets: &'a Vec<u64>,
61}
62
63impl<'a> SymbolResolver for ImageRelocResolver<'a> {
64    fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
65        let func_start = self.func_offsets[symbol];
66        ResolvedSymbol::PhysicalAddress(func_start + addend as u64)
67    }
68}
69
70/// Top level function to get the debug information to give to a debugger.
71pub fn emit_debugsections_image(
72    triple: Triple,
73    pointer_width_bytes: u8,
74    debuginfo_data: &DebugInfoData,
75    vmctx_info: &ModuleVmctxInfo,
76    at: &ModuleAddressMap,
77    ranges: &ValueLabelsRanges,
78    funcs: &[(*const u8, usize)],
79) -> Result<Vec<u8>, Error> {
80    let func_offsets = &funcs
81        .iter()
82        .map(|(ptr, _)| *ptr as u64)
83        .collect::<Vec<u64>>();
84    let mut obj = Artifact::new(triple, String::from("module"));
85    let resolver = ImageRelocResolver { func_offsets };
86    let dwarf = transform_dwarf(pointer_width_bytes, debuginfo_data, at, vmctx_info, ranges)?;
87
88    // Assuming all functions in the same code block, looking min/max of its range.
89    assert_gt!(funcs.len(), 0);
90    let mut segment_body: (usize, usize) = (!0, 0);
91    for (body_ptr, body_len) in funcs {
92        segment_body.0 = std::cmp::min(segment_body.0, *body_ptr as usize);
93        segment_body.1 = std::cmp::max(segment_body.1, *body_ptr as usize + body_len);
94    }
95    let segment_body = (segment_body.0 as *const u8, segment_body.1 - segment_body.0);
96
97    let body = unsafe { std::slice::from_raw_parts(segment_body.0, segment_body.1) };
98    obj.declare_with("all", Decl::function(), body.to_vec())?;
99
100    emit_dwarf(&mut obj, dwarf, &resolver)?;
101
102    // LLDB is too "magical" about mach-o, generating elf
103    let mut bytes = obj.emit_as(BinaryFormat::Elf)?;
104    // elf is still missing details...
105    convert_faerie_elf_to_loadable_file(&mut bytes, segment_body.0);
106
107    // let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
108    // ::std::io::Write::write(&mut file, &bytes).expect("write");
109
110    Ok(bytes)
111}
112
113fn convert_faerie_elf_to_loadable_file(bytes: &mut Vec<u8>, code_ptr: *const u8) {
114    use std::ffi::CStr;
115    use std::os::raw::c_char;
116
117    assert!(
118        bytes[0x4] == 2 && bytes[0x5] == 1,
119        "bits and endianess in .ELF"
120    );
121    let e_phoff = unsafe { *(bytes.as_ptr().offset(0x20) as *const u64) };
122    let e_phnum = unsafe { *(bytes.as_ptr().offset(0x38) as *const u16) };
123    assert!(
124        e_phoff == 0 && e_phnum == 0,
125        "program header table is empty"
126    );
127    let e_phentsize = unsafe { *(bytes.as_ptr().offset(0x36) as *const u16) };
128    assert_eq!(e_phentsize, 0x38, "size of ph");
129    let e_shentsize = unsafe { *(bytes.as_ptr().offset(0x3A) as *const u16) };
130    assert_eq!(e_shentsize, 0x40, "size of sh");
131
132    let e_shoff = unsafe { *(bytes.as_ptr().offset(0x28) as *const u64) };
133    let e_shnum = unsafe { *(bytes.as_ptr().offset(0x3C) as *const u16) };
134    let mut shstrtab_off = 0;
135    let mut segment = None;
136    for i in 0..e_shnum {
137        let off = e_shoff as isize + i as isize * e_shentsize as isize;
138        let sh_type = unsafe { *(bytes.as_ptr().offset(off + 0x4) as *const u32) };
139        if sh_type == /* SHT_SYMTAB */ 3 {
140            shstrtab_off = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
141        }
142        if sh_type != /* SHT_PROGBITS */ 1 {
143            continue;
144        }
145        // It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
146        let sh_name = unsafe {
147            let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
148            CStr::from_ptr(
149                bytes
150                    .as_ptr()
151                    .offset((shstrtab_off + sh_name_off as u64) as isize)
152                    as *const c_char,
153            )
154            .to_str()
155            .expect("name")
156        };
157        if sh_name != ".text.all" {
158            continue;
159        }
160
161        assert!(segment.is_none());
162        // Functions was added at emit_debugsections_image as .text.all.
163        // Patch vaddr, and save file location and its size.
164        unsafe {
165            *(bytes.as_ptr().offset(off + 0x10) as *mut u64) = code_ptr as u64;
166        };
167        let sh_offset = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
168        let sh_size = unsafe { *(bytes.as_ptr().offset(off + 0x20) as *const u64) };
169        segment = Some((sh_offset, code_ptr, sh_size));
170        // Fix name too: cut it to just ".text"
171        unsafe {
172            let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
173            bytes[(shstrtab_off + sh_name_off as u64) as usize + ".text".len()] = 0;
174        }
175    }
176
177    // LLDB wants segment with virtual address set, placing them at the end of ELF.
178    let ph_off = bytes.len();
179    if let Some((sh_offset, v_offset, sh_size)) = segment {
180        let segment = vec![0; 0x38];
181        unsafe {
182            *(segment.as_ptr() as *mut u32) = /* PT_LOAD */ 0x1;
183            *(segment.as_ptr().offset(0x8) as *mut u64) = sh_offset;
184            *(segment.as_ptr().offset(0x10) as *mut u64) = v_offset as u64;
185            *(segment.as_ptr().offset(0x18) as *mut u64) = v_offset as u64;
186            *(segment.as_ptr().offset(0x20) as *mut u64) = sh_size;
187            *(segment.as_ptr().offset(0x28) as *mut u64) = sh_size;
188        }
189        bytes.extend_from_slice(&segment);
190    } else {
191        unreachable!();
192    }
193
194    // It is somewhat loadable ELF file at this moment.
195    // Update e_flags, e_phoff and e_phnum.
196    unsafe {
197        *(bytes.as_ptr().offset(0x10) as *mut u16) = /* ET_DYN */ 3;
198        *(bytes.as_ptr().offset(0x20) as *mut u64) = ph_off as u64;
199        *(bytes.as_ptr().offset(0x38) as *mut u16) = 1 as u16;
200    }
201}