wasmtime_debug/
lib.rs

1//! Debug utils for WebAssembly using Cranelift.
2
3#![allow(clippy::cast_ptr_alignment)]
4
5use anyhow::{bail, ensure, Error};
6use object::endian::{BigEndian, Endian, Endianness, LittleEndian};
7use object::{RelocationEncoding, RelocationKind};
8use std::collections::HashMap;
9
10pub use crate::write_debuginfo::{emit_dwarf, DwarfSection, DwarfSectionRelocTarget};
11
12mod gc;
13mod transform;
14mod write_debuginfo;
15
16pub fn create_gdbjit_image(
17    mut bytes: Vec<u8>,
18    code_region: (*const u8, usize),
19    defined_funcs_offset: usize,
20    funcs: &[*const u8],
21) -> Result<Vec<u8>, Error> {
22    let e = ensure_supported_elf_format(&bytes)?;
23
24    // patch relocs
25    relocate_dwarf_sections(&bytes, defined_funcs_offset, funcs)?;
26
27    // elf is still missing details...
28    match e {
29        Endianness::Little => {
30            convert_object_elf_to_loadable_file::<LittleEndian>(&mut bytes, code_region)
31        }
32        Endianness::Big => {
33            convert_object_elf_to_loadable_file::<BigEndian>(&mut bytes, code_region)
34        }
35    }
36
37    // let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
38    // ::std::io::Write::write_all(&mut file, &bytes).expect("write");
39
40    Ok(bytes)
41}
42
43fn relocate_dwarf_sections(
44    bytes: &[u8],
45    defined_funcs_offset: usize,
46    funcs: &[*const u8],
47) -> Result<(), Error> {
48    use object::read::{File, Object, ObjectSection, ObjectSymbol, RelocationTarget};
49
50    let obj = File::parse(bytes)?;
51    let mut func_symbols = HashMap::new();
52    for sym in obj.symbols() {
53        match (sym.name(), sym.section_index()) {
54            (Ok(name), Some(_section_index)) if name.starts_with("_wasm_function_") => {
55                let index = name["_wasm_function_".len()..].parse::<usize>()?;
56                let data = funcs[index - defined_funcs_offset];
57                func_symbols.insert(sym.index(), data);
58            }
59            _ => (),
60        }
61    }
62
63    for section in obj.sections() {
64        for (off, r) in section.relocations() {
65            if r.kind() != RelocationKind::Absolute
66                || r.encoding() != RelocationEncoding::Generic
67                || r.size() != 64
68            {
69                continue;
70            }
71
72            let data = match r.target() {
73                RelocationTarget::Symbol(ref index) => func_symbols.get(index),
74                _ => None,
75            };
76            let data: *const u8 = match data {
77                Some(data) => *data,
78                None => {
79                    continue;
80                }
81            };
82
83            let target = (data as u64).wrapping_add(r.addend() as u64);
84
85            let entry_ptr = section.data_range(off, 8).unwrap().unwrap().as_ptr();
86            unsafe {
87                std::ptr::write(entry_ptr as *mut u64, target);
88            }
89        }
90    }
91    Ok(())
92}
93
94fn ensure_supported_elf_format(bytes: &[u8]) -> Result<Endianness, Error> {
95    use object::elf::*;
96    use object::read::elf::*;
97    use std::mem::size_of;
98
99    let kind = match object::FileKind::parse(bytes) {
100        Ok(file) => file,
101        Err(err) => {
102            bail!("Failed to parse file: {}", err);
103        }
104    };
105    let header = match kind {
106        object::FileKind::Elf64 => match object::elf::FileHeader64::<Endianness>::parse(bytes) {
107            Ok(header) => header,
108            Err(err) => {
109                bail!("Unsupported ELF file: {}", err);
110            }
111        },
112        _ => {
113            bail!("only 64-bit ELF files currently supported")
114        }
115    };
116    let e = header.endian().unwrap();
117
118    match header.e_machine.get(e) {
119        EM_AARCH64 => (),
120        EM_X86_64 => (),
121        EM_S390 => (),
122        machine => {
123            bail!("Unsupported ELF target machine: {:x}", machine);
124        }
125    }
126    ensure!(
127        header.e_phoff.get(e) == 0 && header.e_phnum.get(e) == 0,
128        "program header table is empty"
129    );
130    let e_shentsize = header.e_shentsize.get(e);
131    let req_shentsize = match e {
132        Endianness::Little => size_of::<SectionHeader64<LittleEndian>>(),
133        Endianness::Big => size_of::<SectionHeader64<BigEndian>>(),
134    };
135    ensure!(e_shentsize as usize == req_shentsize, "size of sh");
136    Ok(e)
137}
138
139fn convert_object_elf_to_loadable_file<E: Endian>(
140    bytes: &mut Vec<u8>,
141    code_region: (*const u8, usize),
142) {
143    use object::elf::*;
144    use std::ffi::CStr;
145    use std::mem::size_of;
146    use std::os::raw::c_char;
147
148    let e = E::default();
149    let header: &FileHeader64<E> = unsafe { &*(bytes.as_mut_ptr() as *const FileHeader64<_>) };
150
151    let e_shentsize = header.e_shentsize.get(e);
152    let e_shoff = header.e_shoff.get(e);
153    let e_shnum = header.e_shnum.get(e);
154    let mut shstrtab_off = 0;
155    for i in 0..e_shnum {
156        let off = e_shoff as isize + i as isize * e_shentsize as isize;
157        let section: &SectionHeader64<E> =
158            unsafe { &*(bytes.as_ptr().offset(off) as *const SectionHeader64<_>) };
159        if section.sh_type.get(e) != SHT_STRTAB {
160            continue;
161        }
162        shstrtab_off = section.sh_offset.get(e);
163    }
164    let mut segment: Option<_> = None;
165    for i in 0..e_shnum {
166        let off = e_shoff as isize + i as isize * e_shentsize as isize;
167        let section: &mut SectionHeader64<E> =
168            unsafe { &mut *(bytes.as_mut_ptr().offset(off) as *mut SectionHeader64<_>) };
169        if section.sh_type.get(e) != SHT_PROGBITS {
170            continue;
171        }
172        // It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
173        let sh_name_off = section.sh_name.get(e);
174        let sh_name = unsafe {
175            CStr::from_ptr(
176                bytes
177                    .as_ptr()
178                    .offset((shstrtab_off + sh_name_off as u64) as isize)
179                    as *const c_char,
180            )
181            .to_str()
182            .expect("name")
183        };
184        if sh_name != ".text" {
185            continue;
186        }
187
188        assert!(segment.is_none());
189        // Patch vaddr, and save file location and its size.
190        section.sh_addr.set(e, code_region.0 as u64);
191        let sh_offset = section.sh_offset.get(e);
192        let sh_size = section.sh_size.get(e);
193        segment = Some((sh_offset, sh_size));
194    }
195
196    // LLDB wants segment with virtual address set, placing them at the end of ELF.
197    let ph_off = bytes.len();
198    let e_phentsize = size_of::<ProgramHeader64<E>>();
199    let e_phnum = 1;
200    bytes.resize(ph_off + e_phentsize * e_phnum, 0);
201    if let Some((sh_offset, sh_size)) = segment {
202        let (v_offset, size) = code_region;
203        let program: &mut ProgramHeader64<E> =
204            unsafe { &mut *(bytes.as_ptr().add(ph_off) as *mut ProgramHeader64<_>) };
205        program.p_type.set(e, PT_LOAD);
206        program.p_offset.set(e, sh_offset);
207        program.p_vaddr.set(e, v_offset as u64);
208        program.p_paddr.set(e, v_offset as u64);
209        program.p_filesz.set(e, sh_size as u64);
210        program.p_memsz.set(e, size as u64);
211    } else {
212        unreachable!();
213    }
214
215    // It is somewhat loadable ELF file at this moment.
216    let header: &mut FileHeader64<E> =
217        unsafe { &mut *(bytes.as_mut_ptr() as *mut FileHeader64<_>) };
218    header.e_type.set(e, ET_DYN);
219    header.e_phoff.set(e, ph_off as u64);
220    header.e_phentsize.set(e, e_phentsize as u16);
221    header.e_phnum.set(e, e_phnum as u16);
222}