Source mapping is the feature that connects WASM execution failures back to your original Rust source code. When your contract traps or fails, Erst can show you the exact file and line number where the problem occurred.
How it works
Source mapping relies on DWARF debug symbols embedded in your contract’s WASM:
Compile with debug symbols
Build your contract with debug information included in the WASM binary.
DWARF parsing
Erst reads the .debug_info and .debug_line sections from the WASM.
Instruction mapping
When a trap occurs, Erst maps the WASM instruction offset to source location.
Display source context
The exact file, line, and column are displayed in the trace.
Enabling debug symbols
Compile your Soroban contract with debug symbols:
Using Cargo features
Add debug features to your contract build:
[ profile . release ]
debug = true
Then build:
Alternative: Debug profile
Build with the debug profile:
stellar contract build --profile dev
Debug symbols increase WASM file size significantly. Only use them for development and debugging.
Verify debug symbols
Check if your WASM contains debug information:
wasm-objdump -h target/wasm32-unknown-unknown/release/contract.wasm | grep debug
You should see sections like:
.debug_info
.debug_line
.debug_str
.debug_abbrev
Using source mapping
When debugging with source-mapped WASM, Erst automatically shows source locations:
erst debug < transaction-has h > --network testnet
Output with source mapping
When a failure occurs, you’ll see:
❌ Error: Contract execution failed
WASM Instruction: i32.load offset=8
📄 Source: token.rs:45
│
45 │ let balance = storage.get(&recipient)?;
│ ^^^^^^^^^^^^^^^^^^^^^^^ Out of bounds access
Output without source mapping
Without debug symbols, you only see:
❌ Error: Contract execution failed
WASM Instruction: i32.load offset=8
Without debug symbols, you’ll have to manually correlate WASM instructions with your source code, which is extremely difficult.
Source mapping in the interactive viewer
The interactive trace viewer shows source locations at each step:
erst debug < transaction-has h > --interactive
Navigate to any step and see:
📍 Current State
════════════════════════════════════════════════════════════════════
Step: 125/248
Operation: ContractCall
Function: transfer
📄 Source: src/token.rs:45
🔗 GitHub: https://github.com/org/repo/blob/main/src/token.rs#L45
GitHub link generation
When in a Git repository, Erst automatically generates clickable GitHub links:
Git repository detection
Erst detects if you’re in a Git repository.
Remote URL extraction
Reads the GitHub remote URL from .git/config.
Commit hash
Gets the current commit hash to create a permanent link.
Line number
Appends #L<line> to the URL for direct navigation.
Example generated link:
https://github.com/myorg/myrepo/blob/abc123/contracts/token/src/lib.rs#L45
Split-pane source view
View trace and source code side by side:
erst debug < transaction-has h > --interactive
# Then in the viewer:
sp
The split pane shows:
┌─────────────────────────────────┬─────────────────────────────────┐
│ Step: 125 │ src/token.rs │
│ Function: transfer │ │
│ Contract: CDLZFC... │ 43: pub fn transfer( │
│ │ 44: amount: i128, │
│ Arguments: │ 45: recipient: Address, │
│ amount: i128(1000000) │ 46: ) -> Result<(), Error> { │
│ recipient: Address("GC...") │ 47: let balance = storage │
│ │ 48: .get(&recipient)?; │ ← Error
│ ❌ Error: Out of bounds │ 49: require!( │
│ at token.rs:48 │ 50: balance >= amount, │
│ │ 51: Error::Insufficient│
└─────────────────────────────────┴─────────────────────────────────┘
The split-pane view highlights the exact line where execution is or where an error occurred.
Source mapping architecture
Erst’s source mapping implementation consists of:
DWARF parser
Location: internal/dwarf/parser.go (Go) and src/source_mapper.rs (Rust)
Parses .debug_info sections for compilation units
Reads .debug_line sections for instruction-to-line mappings
Handles optimized code and inlined functions
Source mapper
Location: internal/trace/sourcemap.go
Maps WASM instruction offsets to source locations
Caches mappings for performance
Provides graceful fallback when debug info is missing
Source context loader
Location: internal/trace/splitpane.go
Reads source files from disk
Extracts context lines around the target line
Handles missing files gracefully
DWARF sections
Erst reads these DWARF sections from WASM:
Section Purpose .debug_infoCompilation units, types, variables .debug_lineLine number program for instruction mapping .debug_strString table for names and paths .debug_abbrevAbbreviation tables for compact encoding .debug_rangesAddress ranges for scopes
Optimization handling
Optimized builds present challenges:
Inlining : Functions may be inlined, making the call stack shallow
Dead code elimination : Some code may not exist in the WASM
Register allocation : Variables may not have stable locations
Unoptimized (best mapping)
Optimized (less accurate)
[ profile . dev ]
opt-level = 0
debug = true
For the most accurate source mapping during debugging, use unoptimized builds. For production, optimize without debug symbols.
Local variable inspection
With debug symbols, you can inspect local variables at trap points:
erst debug < transaction-has h > --interactive
# Navigate to the trap
t # Show trap info
Output:
❌ Memory Trap Detected
Type: i32.load offset=8 out of bounds
Step: 145/248
Source Location:
File: src/token.rs
Line: 45
Column: 12
Local Variables:
amount: i128(1000000)
recipient: Address("GC3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZ...")
balance_ptr: 0x10008 ← Out of bounds!
sender: Address("GDLZFC3SYJYDZT7K67VZ75HPJVI...")
Function Arguments:
env: Env
from: Address("GDLZFC...")
to: Address("GC3D5K...")
amount: i128(1000000)
Local variable inspection requires DWARF .debug_loc sections and works best with unoptimized builds.
Source mapping in error suggestions
The error suggestion engine uses source locations to provide context:
=== Potential Fixes (Heuristic Analysis) ===
1. 🔴 [Confidence: high]
Location: token.rs:45
Potential Fix: Array access at line 45 is out of bounds.
Ensure the pointer is within the allocated memory range.
See Transaction debugging for more on error suggestions.
Troubleshooting
No source locations shown
Check debug symbols
Verify your WASM has debug sections: wasm-objdump -h contract.wasm | grep debug
Rebuild with debug
Add debug = true to your Cargo.toml and rebuild.
Check WASM size
Debug symbols increase size significantly. If size is similar to release build, debug info may be missing.
Wrong line numbers
Verify build
Ensure the WASM you’re debugging matches your source code.
Check optimization
Highly optimized builds can have imprecise line mappings.
Use dev profile
Build with --profile dev for best accuracy.
Missing source files
Check file paths
DWARF sections contain absolute or relative paths to source files.
Run from repo root
Erst looks for source files relative to the current directory.
Check Git status
Ensure source files exist at the expected locations.
Best practices
Development builds
For debugging and development:
[ profile . dev ]
opt-level = 0
debug = true
stellar contract build --profile dev
Production builds
For deployment to mainnet:
[ profile . release ]
opt-level = 3
debug = false # Remove debug symbols
strip = true # Strip additional metadata
stellar contract build --release
CI/CD
Create separate build jobs:
.github/workflows/build.yml
- name : Build with debug symbols
run : stellar contract build --profile dev
- name : Build for production
run : stellar contract build --release
Future enhancements
Upcoming source mapping features:
Full stack trace with source locations for all frames
Instruction-level stepping through source code
Source-aware breakpoints in interactive viewer
Integration with IDEs for click-through debugging
Support for other debug formats beyond DWARF
Next steps