wasmer_compiler/
source_map.rs1use addr2line::gimli::{Dwarf, EndianRcSlice, LittleEndian, SectionId};
2use std::{collections::BTreeMap, path::PathBuf, rc::Rc};
3use wasmer_types::{LocalFunctionIndex, ModuleInfo, entity::PrimaryMap};
4
5use crate::{
6 FunctionBodyData, ModuleTranslationState,
7 wasmparser::{BinaryReader, FunctionBody},
8};
9
10#[derive(Clone, Debug)]
12pub struct SourceLocation {
13 pub file: String,
15 pub directory: String,
17 pub line: u32,
19 pub column: u32,
21}
22
23#[derive(Default)]
29pub struct WasmSourceMap {
30 locations: BTreeMap<usize, SourceLocation>,
31}
32
33impl WasmSourceMap {
34 pub fn new(
36 module: &ModuleInfo,
37 translation: &ModuleTranslationState,
38 functions: &PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
39 ) -> Result<Self, String> {
40 let Some(code_base) = translation.code_section_offset() else {
41 return Ok(Self::default());
43 };
44
45 type Reader = EndianRcSlice<LittleEndian>;
46 let dwarf = match Dwarf::<Reader>::load(|id: SectionId| {
47 let data = module.custom_sections(id.name()).next().unwrap_or_default();
48 Ok::<_, addr2line::gimli::Error>(Reader::new(Rc::from(data), LittleEndian))
49 }) {
50 Ok(dwarf) => dwarf,
51 Err(err) => return Err(format!("cannot parse DWARF file: {err}")),
52 };
53 let context = match addr2line::Context::from_dwarf(dwarf) {
54 Ok(context) => context,
55 Err(err) => return Err(format!("cannot construct addr2line Context: {err}")),
56 };
57
58 let mut locations = BTreeMap::new();
59 for (_, input) in functions.iter() {
60 let body = FunctionBody::new(BinaryReader::new(input.data, input.module_offset));
61 let Ok(mut operators) = body.get_operators_reader() else {
62 continue;
63 };
64 while !operators.eof() {
65 let offset = operators.original_position();
66 if operators.read().is_err() {
67 return Err("Wasm parsing error".to_string());
68 }
69 let address = offset.checked_sub(code_base).ok_or_else(|| {
70 format!(
71 "Wasm operator offset {offset} precedes code section offset {code_base}"
72 )
73 })? as u64;
74 let Ok(Some(location)) = context.find_location(address) else {
75 continue;
76 };
77 let (Some(file), Some(line)) = (location.file, location.line) else {
78 continue;
79 };
80 let path = PathBuf::from(file);
81 let directory = path
82 .parent()
83 .and_then(|p| p.to_str().map(|p| p.to_string()))
84 .unwrap_or_default();
85 let filename = path
86 .file_name()
87 .and_then(|p| p.to_str().map(|p| p.to_string()))
88 .unwrap_or_default();
89 locations.insert(
90 offset,
91 SourceLocation {
92 file: filename,
93 directory,
94 line,
95 column: location.column.unwrap_or(0),
96 },
97 );
98 }
99 }
100
101 Ok(Self { locations })
102 }
103
104 pub fn get(&self, wasm_offset: usize) -> Option<&SourceLocation> {
106 self.locations.get(&wasm_offset)
107 }
108
109 pub fn first_in_function(&self, body: &FunctionBodyData<'_>) -> Option<&SourceLocation> {
111 self.locations
112 .range(body.module_offset..body.module_offset.saturating_add(body.data.len()))
113 .next()
114 .map(|(_, location)| location)
115 }
116}