Skip to main content

stylus_trace_core/parser/
source_map.rs

1//! Source mapping service for Stylus WASM binaries.
2//!
3//! # ⚠️ CURRENTLY NON-FUNCTIONAL
4//! This service translates binary offsets (PCs) to source locations (file:line) using DWARF.
5//! However, it is currently non-functional because the Arbitrum `stylusTracer` does not
6//! provide the required Program Counter (PC) offsets for WASM execution.
7//!
8//! This code is preserved for future use when tracer support is improved.
9
10//! Source mapping service for Stylus WASM binaries.
11//!
12//! Translates binary offsets (PCs) to source locations (file:line) using DWARF.
13
14use addr2line::Context;
15use log::{debug, info};
16use std::path::Path;
17
18/// A location in the source code
19#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
20pub struct SourceLocation {
21    pub file: String,
22    pub line: Option<u32>,
23    pub column: Option<u32>,
24    pub function: Option<String>,
25}
26
27type Reader = addr2line::gimli::EndianReader<addr2line::gimli::RunTimeEndian, std::rc::Rc<[u8]>>;
28
29/// Mapper that handles address translation
30pub struct SourceMapper {
31    context: Option<Context<Reader>>,
32}
33
34impl SourceMapper {
35    /// Create a new SourceMapper from a WASM file
36    pub fn new<P: AsRef<Path>>(wasm_path: P) -> anyhow::Result<Self> {
37        let path = wasm_path.as_ref();
38        debug!("Loading WASM binary for source mapping: {}", path.display());
39
40        let file_data = std::fs::read(path)?;
41        let obj = object::File::parse(&*file_data)?;
42
43        let context = Context::new(&obj).ok();
44
45        if context.is_none() {
46            info!("No debug information (DWARF) found in the WASM binary. Source-to-line mapping will not be available.");
47            info!("Tip: Compile your contract with `debug = true` in your Cargo.toml release profile.");
48        } else {
49            info!("Debug information loaded successfully. Source-to-line mapping enabled.");
50        }
51
52        Ok(Self { context })
53    }
54
55    /// Factory for an empty mapper (fallback)
56    pub fn empty() -> Self {
57        Self { context: None }
58    }
59
60    /// Lookup source location for a given offset
61    pub fn lookup(&self, offset: u64) -> Option<SourceLocation> {
62        let context = self.context.as_ref()?;
63
64        // In addr2line 0.21, find_frames returns a LookupResult.
65        // For synchronous use with all data loaded, we can use skip_all_loads().
66        let mut frames = context.find_frames(offset).skip_all_loads().ok()?;
67
68        if let Some(frame) = frames.next().ok().flatten() {
69            let function = frame
70                .function
71                .and_then(|f| f.demangle().ok().map(|d| d.into_owned()));
72
73            let location = frame.location;
74
75            return Some(SourceLocation {
76                file: location
77                    .as_ref()
78                    .and_then(|l| l.file)
79                    .map(|f| f.to_string())
80                    .unwrap_or_else(|| "unknown".to_string()),
81                line: location.as_ref().and_then(|l| l.line),
82                column: location.as_ref().and_then(|l| l.column),
83                function,
84            });
85        }
86
87        None
88    }
89}