Remove wasmtime-environ's dependency on cranelift-codegen (#3199)
* Move `CompiledFunction` into wasmtime-cranelift This commit moves the `wasmtime_environ::CompiledFunction` type into the `wasmtime-cranelift` crate. This type has lots of Cranelift-specific pieces of compilation and doesn't need to be generated by all Wasmtime compilers. This replaces the usage in the `Compiler` trait with a `Box<Any>` type that each compiler can select. Each compiler must still produce a `FunctionInfo`, however, which is shared information we'll deserialize for each module. The `wasmtime-debug` crate is also folded into the `wasmtime-cranelift` crate as a result of this commit. One possibility was to move the `CompiledFunction` commit into its own crate and have `wasmtime-debug` depend on that, but since `wasmtime-debug` is Cranelift-specific at this time it didn't seem like it was too too necessary to keep it separate. If `wasmtime-debug` supports other backends in the future we can recreate a new crate, perhaps with it refactored to not depend on Cranelift. * Move wasmtime_environ::reference_type This now belongs in wasmtime-cranelift and nowhere else * Remove `Type` reexport in wasmtime-environ One less dependency on `cranelift-codegen`! * Remove `types` reexport from `wasmtime-environ` Less cranelift! * Remove `SourceLoc` from wasmtime-environ Change the `srcloc`, `start_srcloc`, and `end_srcloc` fields to a custom `FilePos` type instead of `ir::SourceLoc`. These are only used in a few places so there's not much to lose from an extra abstraction for these leaf use cases outside of cranelift. * Remove wasmtime-environ's dep on cranelift's `StackMap` This commit "clones" the `StackMap` data structure in to `wasmtime-environ` to have an independent representation that that chosen by Cranelift. This allows Wasmtime to decouple this runtime dependency of stack map information and let the two evolve independently, if necessary. An alternative would be to refactor cranelift's implementation into a separate crate and have wasmtime depend on that but it seemed a bit like overkill to do so and easier to clone just a few lines for this. * Define code offsets in wasmtime-environ with `u32` Don't use Cranelift's `binemit::CodeOffset` alias to define this field type since the `wasmtime-environ` crate will be losing the `cranelift-codegen` dependency soon. * Commit to using `cranelift-entity` in Wasmtime This commit removes the reexport of `cranelift-entity` from the `wasmtime-environ` crate and instead directly depends on the `cranelift-entity` crate in all referencing crates. The original reason for the reexport was to make cranelift version bumps easier since it's less versions to change, but nowadays we have a script to do that. Otherwise this encourages crates to use whatever they want from `cranelift-entity` since we'll always depend on the whole crate. It's expected that the `cranelift-entity` crate will continue to be a lean crate in dependencies and suitable for use at both runtime and compile time. Consequently there's no need to avoid its usage in Wasmtime at runtime, since "remove Cranelift at compile time" is primarily about the `cranelift-codegen` crate. * Remove most uses of `cranelift-codegen` in `wasmtime-environ` There's only one final use remaining, which is the reexport of `TrapCode`, which will get handled later. * Limit the glob-reexport of `cranelift_wasm` This commit removes the glob reexport of `cranelift-wasm` from the `wasmtime-environ` crate. This is intended to explicitly define what we're reexporting and is a transitionary step to curtail the amount of dependencies taken on `cranelift-wasm` throughout the codebase. For example some functions used by debuginfo mapping are better imported directly from the crate since they're Cranelift-specific. Note that this is intended to be a temporary state affairs, soon this reexport will be gone entirely. Additionally this commit reduces imports from `cranelift_wasm` and also primarily imports from `crate::wasm` within `wasmtime-environ` to get a better sense of what's imported from where and what will need to be shared. * Extract types from cranelift-wasm to cranelift-wasm-types This commit creates a new crate called `cranelift-wasm-types` and extracts type definitions from the `cranelift-wasm` crate into this new crate. The purpose of this crate is to be a shared definition of wasm types that can be shared both by compilers (like Cranelift) as well as wasm runtimes (e.g. Wasmtime). This new `cranelift-wasm-types` crate doesn't depend on `cranelift-codegen` and is the final step in severing the unconditional dependency from Wasmtime to `cranelift-codegen`. The final refactoring in this commit is to then reexport this crate from `wasmtime-environ`, delete the `cranelift-codegen` dependency, and then update all `use` paths to point to these new types. The main change of substance here is that the `TrapCode` enum is mirrored from Cranelift into this `cranelift-wasm-types` crate. While this unfortunately results in three definitions (one more which is non-exhaustive in Wasmtime itself) it's hopefully not too onerous and ideally something we can patch up in the future. * Get lightbeam compiling * Remove unnecessary dependency * Fix compile with uffd * Update publish script * Fix more uffd tests * Rename cranelift-wasm-types to wasmtime-types This reflects the purpose a bit more where it's types specifically intended for Wasmtime and its support. * Fix publish script
This commit is contained in:
799
crates/cranelift/src/debug/transform/address_transform.rs
Normal file
799
crates/cranelift/src/debug/transform/address_transform.rs
Normal file
@@ -0,0 +1,799 @@
|
||||
use crate::CompiledFunctions;
|
||||
use gimli::write;
|
||||
use more_asserts::assert_le;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::FromIterator;
|
||||
use wasmtime_environ::{
|
||||
DefinedFuncIndex, EntityRef, FilePos, FunctionAddressMap, PrimaryMap, WasmFileInfo,
|
||||
};
|
||||
|
||||
pub type GeneratedAddress = usize;
|
||||
pub type WasmAddress = u64;
|
||||
|
||||
/// Contains mapping of the generated address to its original
|
||||
/// source location.
|
||||
#[derive(Debug)]
|
||||
pub struct AddressMap {
|
||||
pub generated: GeneratedAddress,
|
||||
pub wasm: WasmAddress,
|
||||
}
|
||||
|
||||
/// Information about generated function code: its body start,
|
||||
/// length, and instructions addresses.
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionMap {
|
||||
pub offset: GeneratedAddress,
|
||||
pub len: GeneratedAddress,
|
||||
pub wasm_start: WasmAddress,
|
||||
pub wasm_end: WasmAddress,
|
||||
pub addresses: Box<[AddressMap]>,
|
||||
}
|
||||
|
||||
/// Mapping of the source location to its generated code range.
|
||||
#[derive(Debug)]
|
||||
struct Position {
|
||||
wasm_pos: WasmAddress,
|
||||
gen_start: GeneratedAddress,
|
||||
gen_end: GeneratedAddress,
|
||||
}
|
||||
|
||||
/// Mapping of continuous range of source location to its generated
|
||||
/// code. The positions are always in ascending order for search.
|
||||
#[derive(Debug)]
|
||||
struct Range {
|
||||
wasm_start: WasmAddress,
|
||||
wasm_end: WasmAddress,
|
||||
gen_start: GeneratedAddress,
|
||||
gen_end: GeneratedAddress,
|
||||
positions: Box<[Position]>,
|
||||
}
|
||||
|
||||
type RangeIndex = usize;
|
||||
|
||||
/// Helper function address lookup data. Contains ranges start positions
|
||||
/// index and ranges data. The multiple ranges can include the same
|
||||
/// original source position. The index (B-Tree) uses range start
|
||||
/// position as a key. The index values reference the ranges array.
|
||||
/// The item are ordered RangeIndex.
|
||||
#[derive(Debug)]
|
||||
struct FuncLookup {
|
||||
index: Vec<(WasmAddress, Box<[RangeIndex]>)>,
|
||||
ranges: Box<[Range]>,
|
||||
}
|
||||
|
||||
/// Mapping of original functions to generated code locations/ranges.
|
||||
#[derive(Debug)]
|
||||
struct FuncTransform {
|
||||
start: WasmAddress,
|
||||
end: WasmAddress,
|
||||
index: DefinedFuncIndex,
|
||||
lookup: FuncLookup,
|
||||
}
|
||||
|
||||
/// Module functions mapping to generated code.
|
||||
#[derive(Debug)]
|
||||
pub struct AddressTransform {
|
||||
map: PrimaryMap<DefinedFuncIndex, FunctionMap>,
|
||||
func: Vec<(WasmAddress, FuncTransform)>,
|
||||
}
|
||||
|
||||
/// Returns a wasm bytecode offset in the code section from SourceLoc.
|
||||
fn get_wasm_code_offset(loc: FilePos, code_section_offset: u64) -> WasmAddress {
|
||||
// Code section size <= 4GB, allow wrapped SourceLoc to recover the overflow.
|
||||
loc.file_offset()
|
||||
.unwrap()
|
||||
.wrapping_sub(code_section_offset as u32) as WasmAddress
|
||||
}
|
||||
|
||||
fn build_function_lookup(
|
||||
ft: &FunctionAddressMap,
|
||||
code_section_offset: u64,
|
||||
) -> (WasmAddress, WasmAddress, FuncLookup) {
|
||||
assert_le!(
|
||||
code_section_offset,
|
||||
ft.start_srcloc.file_offset().unwrap().into()
|
||||
);
|
||||
let fn_start = get_wasm_code_offset(ft.start_srcloc, code_section_offset);
|
||||
let fn_end = get_wasm_code_offset(ft.end_srcloc, code_section_offset);
|
||||
assert_le!(fn_start, fn_end);
|
||||
|
||||
// Build ranges of continuous source locations. The new ranges starts when
|
||||
// non-descending order is interrupted. Assuming the same origin location can
|
||||
// be present in multiple ranges.
|
||||
let mut range_wasm_start = fn_start;
|
||||
let mut range_gen_start = ft.body_offset;
|
||||
let mut last_wasm_pos = range_wasm_start;
|
||||
let mut ranges = Vec::new();
|
||||
let mut ranges_index = BTreeMap::new();
|
||||
let mut current_range = Vec::new();
|
||||
let mut last_gen_inst_empty = false;
|
||||
for (i, t) in ft.instructions.iter().enumerate() {
|
||||
if t.srcloc.file_offset().is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
|
||||
assert_le!(fn_start, offset);
|
||||
assert_le!(offset, fn_end);
|
||||
|
||||
let inst_gen_start = t.code_offset as usize;
|
||||
let inst_gen_end = match ft.instructions.get(i + 1) {
|
||||
Some(i) => i.code_offset as usize,
|
||||
None => ft.body_len as usize,
|
||||
};
|
||||
|
||||
if last_wasm_pos > offset {
|
||||
// Start new range.
|
||||
ranges_index.insert(range_wasm_start, ranges.len());
|
||||
ranges.push(Range {
|
||||
wasm_start: range_wasm_start,
|
||||
wasm_end: last_wasm_pos,
|
||||
gen_start: range_gen_start,
|
||||
gen_end: inst_gen_start,
|
||||
positions: current_range.into_boxed_slice(),
|
||||
});
|
||||
range_wasm_start = offset;
|
||||
range_gen_start = inst_gen_start;
|
||||
current_range = Vec::new();
|
||||
last_gen_inst_empty = false;
|
||||
}
|
||||
if last_gen_inst_empty && current_range.last().unwrap().gen_start == inst_gen_start {
|
||||
// It is possible that previous inst_gen_start == inst_gen_end, so
|
||||
// make an attempt to merge all such positions with current one.
|
||||
if inst_gen_start < inst_gen_end {
|
||||
let last = current_range.last_mut().unwrap();
|
||||
last.gen_end = inst_gen_end;
|
||||
last_gen_inst_empty = false;
|
||||
}
|
||||
} else {
|
||||
// Continue existing range: add new wasm->generated code position.
|
||||
current_range.push(Position {
|
||||
wasm_pos: offset,
|
||||
gen_start: inst_gen_start,
|
||||
gen_end: inst_gen_end,
|
||||
});
|
||||
// Track if last position was empty (see if-branch above).
|
||||
last_gen_inst_empty = inst_gen_start == inst_gen_end;
|
||||
}
|
||||
last_wasm_pos = offset;
|
||||
}
|
||||
let last_gen_addr = ft.body_offset + ft.body_len as usize;
|
||||
ranges_index.insert(range_wasm_start, ranges.len());
|
||||
ranges.push(Range {
|
||||
wasm_start: range_wasm_start,
|
||||
wasm_end: fn_end,
|
||||
gen_start: range_gen_start,
|
||||
gen_end: last_gen_addr,
|
||||
positions: current_range.into_boxed_slice(),
|
||||
});
|
||||
|
||||
// Making ranges lookup faster by building index: B-tree with every range
|
||||
// start position that maps into list of active ranges at this position.
|
||||
let ranges = ranges.into_boxed_slice();
|
||||
let mut active_ranges = Vec::new();
|
||||
let mut index = BTreeMap::new();
|
||||
let mut last_wasm_pos = None;
|
||||
for (wasm_start, range_index) in ranges_index {
|
||||
if Some(wasm_start) == last_wasm_pos {
|
||||
active_ranges.push(range_index);
|
||||
continue;
|
||||
}
|
||||
if let Some(position) = last_wasm_pos {
|
||||
let mut sorted_ranges = active_ranges.clone();
|
||||
sorted_ranges.sort();
|
||||
index.insert(position, sorted_ranges.into_boxed_slice());
|
||||
}
|
||||
active_ranges.retain(|r| ranges[*r].wasm_end.cmp(&wasm_start) != std::cmp::Ordering::Less);
|
||||
active_ranges.push(range_index);
|
||||
last_wasm_pos = Some(wasm_start);
|
||||
}
|
||||
active_ranges.sort();
|
||||
index.insert(last_wasm_pos.unwrap(), active_ranges.into_boxed_slice());
|
||||
let index = Vec::from_iter(index.into_iter());
|
||||
(fn_start, fn_end, FuncLookup { index, ranges })
|
||||
}
|
||||
|
||||
fn build_function_addr_map(
|
||||
funcs: &CompiledFunctions,
|
||||
code_section_offset: u64,
|
||||
) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
|
||||
let mut map = PrimaryMap::new();
|
||||
for (_, f) in funcs {
|
||||
let ft = &f.info.address_map;
|
||||
let mut fn_map = Vec::new();
|
||||
for t in ft.instructions.iter() {
|
||||
if t.srcloc.file_offset().is_none() {
|
||||
continue;
|
||||
}
|
||||
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
|
||||
fn_map.push(AddressMap {
|
||||
generated: t.code_offset as usize,
|
||||
wasm: offset,
|
||||
});
|
||||
}
|
||||
|
||||
if cfg!(debug) {
|
||||
// fn_map is sorted by the generated field -- see FunctionAddressMap::instructions.
|
||||
for i in 1..fn_map.len() {
|
||||
assert_le!(fn_map[i - 1].generated, fn_map[i].generated);
|
||||
}
|
||||
}
|
||||
|
||||
map.push(FunctionMap {
|
||||
offset: ft.body_offset,
|
||||
len: ft.body_len as usize,
|
||||
wasm_start: get_wasm_code_offset(ft.start_srcloc, code_section_offset),
|
||||
wasm_end: get_wasm_code_offset(ft.end_srcloc, code_section_offset),
|
||||
addresses: fn_map.into_boxed_slice(),
|
||||
});
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
// Utility iterator to find all ranges starts for specific Wasm address.
|
||||
// The iterator returns generated addresses sorted by RangeIndex.
|
||||
struct TransformRangeStartIter<'a> {
|
||||
addr: WasmAddress,
|
||||
indices: &'a [RangeIndex],
|
||||
ranges: &'a [Range],
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeStartIter<'a> {
|
||||
fn new(func: &'a FuncTransform, addr: WasmAddress) -> Self {
|
||||
let found = match func
|
||||
.lookup
|
||||
.index
|
||||
.binary_search_by(|entry| entry.0.cmp(&addr))
|
||||
{
|
||||
Ok(i) => Some(&func.lookup.index[i].1),
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
Some(&func.lookup.index[i - 1].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(range_indices) = found {
|
||||
TransformRangeStartIter {
|
||||
addr,
|
||||
indices: range_indices,
|
||||
ranges: &func.lookup.ranges,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TransformRangeStartIter<'a> {
|
||||
type Item = (GeneratedAddress, RangeIndex);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((first, tail)) = self.indices.split_first() {
|
||||
let range_index = *first;
|
||||
let range = &self.ranges[range_index];
|
||||
self.indices = tail;
|
||||
let address = match range
|
||||
.positions
|
||||
.binary_search_by(|a| a.wasm_pos.cmp(&self.addr))
|
||||
{
|
||||
Ok(i) => range.positions[i].gen_start,
|
||||
Err(i) => {
|
||||
if i == 0 {
|
||||
range.gen_start
|
||||
} else {
|
||||
range.positions[i - 1].gen_end
|
||||
}
|
||||
}
|
||||
};
|
||||
Some((address, range_index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Utility iterator to find all ranges ends for specific Wasm address.
|
||||
// The iterator returns generated addresses sorted by RangeIndex.
|
||||
struct TransformRangeEndIter<'a> {
|
||||
addr: WasmAddress,
|
||||
indices: &'a [RangeIndex],
|
||||
ranges: &'a [Range],
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeEndIter<'a> {
|
||||
fn new(func: &'a FuncTransform, addr: WasmAddress) -> Self {
|
||||
let found = match func
|
||||
.lookup
|
||||
.index
|
||||
.binary_search_by(|entry| entry.0.cmp(&addr))
|
||||
{
|
||||
Ok(i) => Some(&func.lookup.index[i].1),
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
Some(&func.lookup.index[i - 1].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(range_indices) = found {
|
||||
TransformRangeEndIter {
|
||||
addr,
|
||||
indices: range_indices,
|
||||
ranges: &func.lookup.ranges,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TransformRangeEndIter<'a> {
|
||||
type Item = (GeneratedAddress, RangeIndex);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some((first, tail)) = self.indices.split_first() {
|
||||
let range_index = *first;
|
||||
let range = &self.ranges[range_index];
|
||||
self.indices = tail;
|
||||
if range.wasm_start >= self.addr {
|
||||
continue;
|
||||
}
|
||||
let address = match range
|
||||
.positions
|
||||
.binary_search_by(|a| a.wasm_pos.cmp(&self.addr))
|
||||
{
|
||||
Ok(i) => range.positions[i].gen_end,
|
||||
Err(i) => {
|
||||
if i == range.positions.len() {
|
||||
range.gen_end
|
||||
} else {
|
||||
range.positions[i].gen_start
|
||||
}
|
||||
}
|
||||
};
|
||||
return Some((address, range_index));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Utility iterator to iterate by translated function ranges.
|
||||
pub struct TransformRangeIter<'a> {
|
||||
func: &'a FuncTransform,
|
||||
start_it: TransformRangeStartIter<'a>,
|
||||
end_it: TransformRangeEndIter<'a>,
|
||||
last_start: Option<(GeneratedAddress, RangeIndex)>,
|
||||
last_end: Option<(GeneratedAddress, RangeIndex)>,
|
||||
last_item: Option<(GeneratedAddress, GeneratedAddress)>,
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeIter<'a> {
|
||||
fn new(func: &'a FuncTransform, start: WasmAddress, end: WasmAddress) -> Self {
|
||||
let mut start_it = TransformRangeStartIter::new(func, start);
|
||||
let last_start = start_it.next();
|
||||
let mut end_it = TransformRangeEndIter::new(func, end);
|
||||
let last_end = end_it.next();
|
||||
TransformRangeIter {
|
||||
func,
|
||||
start_it,
|
||||
end_it,
|
||||
last_start,
|
||||
last_end,
|
||||
last_item: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TransformRangeIter<'a> {
|
||||
type Item = (GeneratedAddress, GeneratedAddress);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
// Merge TransformRangeStartIter and TransformRangeEndIter data using
|
||||
// FuncLookup index's field propery to be sorted by RangeIndex.
|
||||
let (start, end, range_index): (
|
||||
Option<GeneratedAddress>,
|
||||
Option<GeneratedAddress>,
|
||||
RangeIndex,
|
||||
) = {
|
||||
match (self.last_start.as_ref(), self.last_end.as_ref()) {
|
||||
(Some((s, sri)), Some((e, eri))) => {
|
||||
if sri == eri {
|
||||
// Start and end RangeIndex matched.
|
||||
(Some(*s), Some(*e), *sri)
|
||||
} else if sri < eri {
|
||||
(Some(*s), None, *sri)
|
||||
} else {
|
||||
(None, Some(*e), *eri)
|
||||
}
|
||||
}
|
||||
(Some((s, sri)), None) => (Some(*s), None, *sri),
|
||||
(None, Some((e, eri))) => (None, Some(*e), *eri),
|
||||
(None, None) => {
|
||||
// Reached ends for start and end iterators.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
let range_start = match start {
|
||||
Some(range_start) => {
|
||||
// Consume start iterator.
|
||||
self.last_start = self.start_it.next();
|
||||
range_start
|
||||
}
|
||||
None => {
|
||||
let range = &self.func.lookup.ranges[range_index];
|
||||
range.gen_start
|
||||
}
|
||||
};
|
||||
let range_end = match end {
|
||||
Some(range_end) => {
|
||||
// Consume end iterator.
|
||||
self.last_end = self.end_it.next();
|
||||
range_end
|
||||
}
|
||||
None => {
|
||||
let range = &self.func.lookup.ranges[range_index];
|
||||
range.gen_end
|
||||
}
|
||||
};
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
match self.last_item.replace((range_start, range_end)) {
|
||||
Some((_, last_end)) => debug_assert!(last_end <= range_start),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
if range_start < range_end {
|
||||
return Some((range_start, range_end));
|
||||
}
|
||||
// Throw away empty ranges.
|
||||
debug_assert!(range_start == range_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressTransform {
|
||||
pub fn new(funcs: &CompiledFunctions, wasm_file: &WasmFileInfo) -> Self {
|
||||
let code_section_offset = wasm_file.code_section_offset;
|
||||
|
||||
let mut func = BTreeMap::new();
|
||||
for (i, f) in funcs {
|
||||
let ft = &f.info.address_map;
|
||||
let (fn_start, fn_end, lookup) = build_function_lookup(ft, code_section_offset);
|
||||
|
||||
func.insert(
|
||||
fn_start,
|
||||
FuncTransform {
|
||||
start: fn_start,
|
||||
end: fn_end,
|
||||
index: i,
|
||||
lookup,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let map = build_function_addr_map(funcs, code_section_offset);
|
||||
let func = Vec::from_iter(func.into_iter());
|
||||
AddressTransform { map, func }
|
||||
}
|
||||
|
||||
fn find_func(&self, addr: u64) -> Option<&FuncTransform> {
|
||||
// TODO check if we need to include end address
|
||||
let func = match self.func.binary_search_by(|entry| entry.0.cmp(&addr)) {
|
||||
Ok(i) => &self.func[i].1,
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
&self.func[i - 1].1
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
if addr >= func.start {
|
||||
return Some(func);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_func_index(&self, addr: u64) -> Option<DefinedFuncIndex> {
|
||||
self.find_func(addr).map(|f| f.index)
|
||||
}
|
||||
|
||||
pub fn translate_raw(&self, addr: u64) -> Option<(DefinedFuncIndex, GeneratedAddress)> {
|
||||
if addr == 0 {
|
||||
// It's normally 0 for debug info without the linked code.
|
||||
return None;
|
||||
}
|
||||
if let Some(func) = self.find_func(addr) {
|
||||
if addr == func.end {
|
||||
// Clamp last address to the end to extend translation to the end
|
||||
// of the function.
|
||||
let map = &self.map[func.index];
|
||||
return Some((func.index, map.len));
|
||||
}
|
||||
let first_result = TransformRangeStartIter::new(func, addr).next();
|
||||
first_result.map(|(address, _)| (func.index, address))
|
||||
} else {
|
||||
// Address was not found: function was not compiled?
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_translate_address(&self, addr: u64) -> bool {
|
||||
self.translate(addr).is_some()
|
||||
}
|
||||
|
||||
pub fn translate(&self, addr: u64) -> Option<write::Address> {
|
||||
self.translate_raw(addr)
|
||||
.map(|(func_index, address)| write::Address::Symbol {
|
||||
symbol: func_index.index(),
|
||||
addend: address as i64,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn translate_ranges_raw<'a>(
|
||||
&'a self,
|
||||
start: u64,
|
||||
end: u64,
|
||||
) -> Option<(DefinedFuncIndex, impl Iterator<Item = (usize, usize)> + 'a)> {
|
||||
if start == 0 {
|
||||
// It's normally 0 for debug info without the linked code.
|
||||
return None;
|
||||
}
|
||||
if let Some(func) = self.find_func(start) {
|
||||
let result = TransformRangeIter::new(func, start, end);
|
||||
return Some((func.index, result));
|
||||
}
|
||||
// Address was not found: function was not compiled?
|
||||
None
|
||||
}
|
||||
|
||||
pub fn translate_ranges<'a>(
|
||||
&'a self,
|
||||
start: u64,
|
||||
end: u64,
|
||||
) -> impl Iterator<Item = (write::Address, u64)> + 'a {
|
||||
enum TranslateRangesResult<'a> {
|
||||
Empty,
|
||||
Raw {
|
||||
symbol: usize,
|
||||
it: Box<dyn Iterator<Item = (usize, usize)> + 'a>,
|
||||
},
|
||||
}
|
||||
impl<'a> Iterator for TranslateRangesResult<'a> {
|
||||
type Item = (write::Address, u64);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
TranslateRangesResult::Empty => None,
|
||||
TranslateRangesResult::Raw { symbol, it } => match it.next() {
|
||||
Some((start, end)) => {
|
||||
debug_assert!(start < end);
|
||||
Some((
|
||||
write::Address::Symbol {
|
||||
symbol: *symbol,
|
||||
addend: start as i64,
|
||||
},
|
||||
(end - start) as u64,
|
||||
))
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.translate_ranges_raw(start, end) {
|
||||
Some((func_index, ranges)) => TranslateRangesResult::Raw {
|
||||
symbol: func_index.index(),
|
||||
it: Box::new(ranges),
|
||||
},
|
||||
None => TranslateRangesResult::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map(&self) -> &PrimaryMap<DefinedFuncIndex, FunctionMap> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
pub fn func_range(&self, index: DefinedFuncIndex) -> (GeneratedAddress, GeneratedAddress) {
|
||||
let map = &self.map[index];
|
||||
(map.offset, map.offset + map.len)
|
||||
}
|
||||
|
||||
pub fn func_source_range(&self, index: DefinedFuncIndex) -> (WasmAddress, WasmAddress) {
|
||||
let map = &self.map[index];
|
||||
(map.wasm_start, map.wasm_end)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
|
||||
use crate::{CompiledFunction, CompiledFunctions};
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use gimli::write::Address;
|
||||
use std::iter::FromIterator;
|
||||
use std::mem;
|
||||
use wasmtime_environ::{
|
||||
FilePos, FunctionAddressMap, FunctionInfo, InstructionAddressMap, WasmFileInfo,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_get_wasm_code_offset() {
|
||||
let offset = get_wasm_code_offset(FilePos::new(3), 1);
|
||||
assert_eq!(2, offset);
|
||||
let offset = get_wasm_code_offset(FilePos::new(16), 0xF000_0000);
|
||||
assert_eq!(0x1000_0010, offset);
|
||||
let offset = get_wasm_code_offset(FilePos::new(1), 0x20_8000_0000);
|
||||
assert_eq!(0x8000_0001, offset);
|
||||
}
|
||||
|
||||
fn create_simple_func(wasm_offset: u32) -> FunctionAddressMap {
|
||||
FunctionAddressMap {
|
||||
instructions: vec![
|
||||
InstructionAddressMap {
|
||||
srcloc: FilePos::new(wasm_offset + 2),
|
||||
code_offset: 5,
|
||||
},
|
||||
InstructionAddressMap {
|
||||
srcloc: FilePos::default(),
|
||||
code_offset: 8,
|
||||
},
|
||||
InstructionAddressMap {
|
||||
srcloc: FilePos::new(wasm_offset + 7),
|
||||
code_offset: 15,
|
||||
},
|
||||
InstructionAddressMap {
|
||||
srcloc: FilePos::default(),
|
||||
code_offset: 23,
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
start_srcloc: FilePos::new(wasm_offset),
|
||||
end_srcloc: FilePos::new(wasm_offset + 10),
|
||||
body_offset: 0,
|
||||
body_len: 30,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_simple_module(address_map: FunctionAddressMap) -> CompiledFunctions {
|
||||
PrimaryMap::from_iter(vec![CompiledFunction {
|
||||
info: FunctionInfo {
|
||||
address_map,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_function_lookup_simple() {
|
||||
let input = create_simple_func(11);
|
||||
let (start, end, lookup) = build_function_lookup(&input, 1);
|
||||
assert_eq!(10, start);
|
||||
assert_eq!(20, end);
|
||||
|
||||
assert_eq!(1, lookup.index.len());
|
||||
let index_entry = lookup.index.into_iter().next().unwrap();
|
||||
assert_eq!((10u64, vec![0].into_boxed_slice()), index_entry);
|
||||
assert_eq!(1, lookup.ranges.len());
|
||||
let range = &lookup.ranges[0];
|
||||
assert_eq!(10, range.wasm_start);
|
||||
assert_eq!(20, range.wasm_end);
|
||||
assert_eq!(0, range.gen_start);
|
||||
assert_eq!(30, range.gen_end);
|
||||
let positions = &range.positions;
|
||||
assert_eq!(2, positions.len());
|
||||
assert_eq!(12, positions[0].wasm_pos);
|
||||
assert_eq!(5, positions[0].gen_start);
|
||||
assert_eq!(8, positions[0].gen_end);
|
||||
assert_eq!(17, positions[1].wasm_pos);
|
||||
assert_eq!(15, positions[1].gen_start);
|
||||
assert_eq!(23, positions[1].gen_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_function_lookup_two_ranges() {
|
||||
let mut input = create_simple_func(11);
|
||||
// append instruction with same srcloc as input.instructions[0]
|
||||
let mut list = Vec::from(mem::take(&mut input.instructions));
|
||||
list.push(InstructionAddressMap {
|
||||
srcloc: FilePos::new(11 + 2),
|
||||
code_offset: 23,
|
||||
});
|
||||
list.push(InstructionAddressMap {
|
||||
srcloc: FilePos::default(),
|
||||
code_offset: 26,
|
||||
});
|
||||
input.instructions = list.into();
|
||||
let (start, end, lookup) = build_function_lookup(&input, 1);
|
||||
assert_eq!(10, start);
|
||||
assert_eq!(20, end);
|
||||
|
||||
assert_eq!(2, lookup.index.len());
|
||||
let index_entries = Vec::from_iter(lookup.index.into_iter());
|
||||
assert_eq!((10u64, vec![0].into_boxed_slice()), index_entries[0]);
|
||||
assert_eq!((12u64, vec![0, 1].into_boxed_slice()), index_entries[1]);
|
||||
assert_eq!(2, lookup.ranges.len());
|
||||
|
||||
let range = &lookup.ranges[0];
|
||||
assert_eq!(10, range.wasm_start);
|
||||
assert_eq!(17, range.wasm_end);
|
||||
assert_eq!(0, range.gen_start);
|
||||
assert_eq!(23, range.gen_end);
|
||||
let positions = &range.positions;
|
||||
assert_eq!(2, positions.len());
|
||||
assert_eq!(12, positions[0].wasm_pos);
|
||||
assert_eq!(5, positions[0].gen_start);
|
||||
assert_eq!(8, positions[0].gen_end);
|
||||
assert_eq!(17, positions[1].wasm_pos);
|
||||
assert_eq!(15, positions[1].gen_start);
|
||||
assert_eq!(23, positions[1].gen_end);
|
||||
|
||||
let range = &lookup.ranges[1];
|
||||
assert_eq!(12, range.wasm_start);
|
||||
assert_eq!(20, range.wasm_end);
|
||||
assert_eq!(23, range.gen_start);
|
||||
assert_eq!(30, range.gen_end);
|
||||
let positions = &range.positions;
|
||||
assert_eq!(1, positions.len());
|
||||
assert_eq!(12, positions[0].wasm_pos);
|
||||
assert_eq!(23, positions[0].gen_start);
|
||||
assert_eq!(26, positions[0].gen_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_translate() {
|
||||
let input = create_simple_module(create_simple_func(11));
|
||||
let at = AddressTransform::new(
|
||||
&input,
|
||||
&WasmFileInfo {
|
||||
path: None,
|
||||
code_section_offset: 1,
|
||||
imported_func_count: 0,
|
||||
funcs: Vec::new(),
|
||||
},
|
||||
);
|
||||
|
||||
let addr = at.translate(10);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 0,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
|
||||
let addr = at.translate(20);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 30,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
|
||||
let addr = at.translate(0);
|
||||
assert_eq!(None, addr);
|
||||
|
||||
let addr = at.translate(12);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 5,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
|
||||
let addr = at.translate(18);
|
||||
assert_eq!(
|
||||
Some(Address::Symbol {
|
||||
symbol: 0,
|
||||
addend: 23,
|
||||
}),
|
||||
addr
|
||||
);
|
||||
}
|
||||
}
|
||||
339
crates/cranelift/src/debug/transform/attr.rs
Normal file
339
crates/cranelift/src/debug/transform/attr.rs
Normal file
@@ -0,0 +1,339 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::expression::{compile_expression, CompiledExpression, FunctionFrameInfo};
|
||||
use super::range_info_builder::RangeInfoBuilder;
|
||||
use super::refs::{PendingDebugInfoRefs, PendingUnitRefs};
|
||||
use super::{DebugInputContext, Reader, TransformError};
|
||||
use anyhow::{bail, Error};
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use gimli::{
|
||||
write, AttributeValue, DebugLineOffset, DebugLineStr, DebugStr, DebugStrOffsets,
|
||||
DebuggingInformationEntry, Unit,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum FileAttributeContext<'a> {
|
||||
Root(Option<DebugLineOffset>),
|
||||
Children {
|
||||
file_map: &'a [write::FileId],
|
||||
file_index_base: u64,
|
||||
frame_base: Option<&'a CompiledExpression>,
|
||||
},
|
||||
}
|
||||
|
||||
fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool {
|
||||
match attr_name {
|
||||
gimli::DW_AT_location
|
||||
| gimli::DW_AT_string_length
|
||||
| gimli::DW_AT_return_addr
|
||||
| gimli::DW_AT_data_member_location
|
||||
| gimli::DW_AT_frame_base
|
||||
| gimli::DW_AT_segment
|
||||
| gimli::DW_AT_static_link
|
||||
| gimli::DW_AT_use_location
|
||||
| gimli::DW_AT_vtable_elem_location => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clone_die_attributes<'a, R>(
|
||||
dwarf: &gimli::Dwarf<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&FunctionFrameInfo>,
|
||||
out_unit: &mut write::Unit,
|
||||
current_scope_id: write::UnitEntryId,
|
||||
subprogram_range_builder: Option<RangeInfoBuilder>,
|
||||
scope_ranges: Option<&Vec<(u64, u64)>>,
|
||||
cu_low_pc: u64,
|
||||
out_strings: &mut write::StringTable,
|
||||
pending_die_refs: &mut PendingUnitRefs,
|
||||
pending_di_refs: &mut PendingDebugInfoRefs,
|
||||
file_context: FileAttributeContext<'a>,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let unit_encoding = unit.encoding();
|
||||
|
||||
let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {
|
||||
subprogram_range_builder
|
||||
} else {
|
||||
// FIXME for CU: currently address_transform operate on a single
|
||||
// function range, and when CU spans multiple ranges the
|
||||
// transformation may be incomplete.
|
||||
RangeInfoBuilder::from(dwarf, unit, entry, context, cu_low_pc)?
|
||||
};
|
||||
range_info.build(addr_tr, out_unit, current_scope_id);
|
||||
|
||||
let mut attrs = entry.attrs();
|
||||
while let Some(attr) = attrs.next()? {
|
||||
let attr_value = match attr.value() {
|
||||
AttributeValue::Addr(_) | AttributeValue::DebugAddrIndex(_)
|
||||
if attr.name() == gimli::DW_AT_low_pc =>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AttributeValue::Udata(_) if attr.name() == gimli::DW_AT_high_pc => {
|
||||
continue;
|
||||
}
|
||||
AttributeValue::RangeListsRef(_) if attr.name() == gimli::DW_AT_ranges => {
|
||||
continue;
|
||||
}
|
||||
AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
|
||||
continue;
|
||||
}
|
||||
AttributeValue::DebugAddrBase(_) | AttributeValue::DebugStrOffsetsBase(_) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
AttributeValue::Addr(u) => {
|
||||
let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
|
||||
write::AttributeValue::Address(addr)
|
||||
}
|
||||
AttributeValue::DebugAddrIndex(i) => {
|
||||
let u = context.debug_addr.get_address(4, unit.addr_base, i)?;
|
||||
let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
|
||||
write::AttributeValue::Address(addr)
|
||||
}
|
||||
AttributeValue::Udata(u) => write::AttributeValue::Udata(u),
|
||||
AttributeValue::Data1(d) => write::AttributeValue::Data1(d),
|
||||
AttributeValue::Data2(d) => write::AttributeValue::Data2(d),
|
||||
AttributeValue::Data4(d) => write::AttributeValue::Data4(d),
|
||||
AttributeValue::Sdata(d) => write::AttributeValue::Sdata(d),
|
||||
AttributeValue::Flag(f) => write::AttributeValue::Flag(f),
|
||||
AttributeValue::DebugLineRef(line_program_offset) => {
|
||||
if let FileAttributeContext::Root(o) = file_context {
|
||||
if o != Some(line_program_offset) {
|
||||
return Err(TransformError("invalid debug_line offset").into());
|
||||
}
|
||||
write::AttributeValue::LineProgramRef
|
||||
} else {
|
||||
return Err(TransformError("unexpected debug_line index attribute").into());
|
||||
}
|
||||
}
|
||||
AttributeValue::FileIndex(i) => {
|
||||
if let FileAttributeContext::Children {
|
||||
file_map,
|
||||
file_index_base,
|
||||
..
|
||||
} = file_context
|
||||
{
|
||||
write::AttributeValue::FileIndex(Some(file_map[(i - file_index_base) as usize]))
|
||||
} else {
|
||||
return Err(TransformError("unexpected file index attribute").into());
|
||||
}
|
||||
}
|
||||
AttributeValue::DebugStrRef(str_offset) => {
|
||||
let s = context.debug_str.get_str(str_offset)?.to_slice()?.to_vec();
|
||||
write::AttributeValue::StringRef(out_strings.add(s))
|
||||
}
|
||||
AttributeValue::DebugStrOffsetsIndex(i) => {
|
||||
let str_offset = context.debug_str_offsets.get_str_offset(
|
||||
gimli::Format::Dwarf32,
|
||||
unit.str_offsets_base,
|
||||
i,
|
||||
)?;
|
||||
let s = context.debug_str.get_str(str_offset)?.to_slice()?.to_vec();
|
||||
write::AttributeValue::StringRef(out_strings.add(s))
|
||||
}
|
||||
AttributeValue::RangeListsRef(r) => {
|
||||
let r = dwarf.ranges_offset_from_raw(unit, r);
|
||||
let range_info = RangeInfoBuilder::from_ranges_ref(unit, r, context, cu_low_pc)?;
|
||||
let range_list_id = range_info.build_ranges(addr_tr, &mut out_unit.ranges);
|
||||
write::AttributeValue::RangeListRef(range_list_id)
|
||||
}
|
||||
AttributeValue::LocationListsRef(r) => {
|
||||
let low_pc = 0;
|
||||
let mut locs = context.loclists.locations(
|
||||
r,
|
||||
unit_encoding,
|
||||
low_pc,
|
||||
&context.debug_addr,
|
||||
unit.addr_base,
|
||||
)?;
|
||||
let frame_base =
|
||||
if let FileAttributeContext::Children { frame_base, .. } = file_context {
|
||||
frame_base
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut result: Option<Vec<_>> = None;
|
||||
while let Some(loc) = locs.next()? {
|
||||
if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {
|
||||
let chunk = expr
|
||||
.build_with_locals(
|
||||
&[(loc.range.begin, loc.range.end)],
|
||||
addr_tr,
|
||||
frame_info,
|
||||
isa,
|
||||
)
|
||||
.filter(|i| {
|
||||
// Ignore empty range
|
||||
if let Ok((_, 0, _)) = i {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.map(|i| {
|
||||
i.map(|(start, len, expr)| write::Location::StartLength {
|
||||
begin: start,
|
||||
length: len,
|
||||
data: expr,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
match &mut result {
|
||||
Some(r) => r.extend(chunk),
|
||||
x @ None => *x = Some(chunk),
|
||||
}
|
||||
} else {
|
||||
// FIXME _expr contains invalid expression
|
||||
continue; // ignore entry
|
||||
}
|
||||
}
|
||||
if result.is_none() {
|
||||
continue; // no valid locations
|
||||
}
|
||||
let list_id = out_unit.locations.add(write::LocationList(result.unwrap()));
|
||||
write::AttributeValue::LocationListRef(list_id)
|
||||
}
|
||||
AttributeValue::Exprloc(ref expr) => {
|
||||
let frame_base =
|
||||
if let FileAttributeContext::Children { frame_base, .. } = file_context {
|
||||
frame_base
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {
|
||||
if expr.is_simple() {
|
||||
if let Some(expr) = expr.build() {
|
||||
write::AttributeValue::Exprloc(expr)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Conversion to loclist is required.
|
||||
if let Some(scope_ranges) = scope_ranges {
|
||||
let exprs = expr
|
||||
.build_with_locals(scope_ranges, addr_tr, frame_info, isa)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if exprs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let found_single_expr = {
|
||||
// Micro-optimization all expressions alike, use one exprloc.
|
||||
let mut found_expr: Option<write::Expression> = None;
|
||||
for (_, _, expr) in &exprs {
|
||||
if let Some(ref prev_expr) = found_expr {
|
||||
if expr == prev_expr {
|
||||
continue; // the same expression
|
||||
}
|
||||
found_expr = None;
|
||||
break;
|
||||
}
|
||||
found_expr = Some(expr.clone())
|
||||
}
|
||||
found_expr
|
||||
};
|
||||
if let Some(expr) = found_single_expr {
|
||||
write::AttributeValue::Exprloc(expr)
|
||||
} else if is_exprloc_to_loclist_allowed(attr.name()) {
|
||||
// Converting exprloc to loclist.
|
||||
let mut locs = Vec::new();
|
||||
for (begin, length, data) in exprs {
|
||||
if length == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
locs.push(write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
});
|
||||
}
|
||||
let list_id = out_unit.locations.add(write::LocationList(locs));
|
||||
write::AttributeValue::LocationListRef(list_id)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FIXME _expr contains invalid expression
|
||||
continue; // ignore attribute
|
||||
}
|
||||
}
|
||||
AttributeValue::Encoding(e) => write::AttributeValue::Encoding(e),
|
||||
AttributeValue::DecimalSign(e) => write::AttributeValue::DecimalSign(e),
|
||||
AttributeValue::Endianity(e) => write::AttributeValue::Endianity(e),
|
||||
AttributeValue::Accessibility(e) => write::AttributeValue::Accessibility(e),
|
||||
AttributeValue::Visibility(e) => write::AttributeValue::Visibility(e),
|
||||
AttributeValue::Virtuality(e) => write::AttributeValue::Virtuality(e),
|
||||
AttributeValue::Language(e) => write::AttributeValue::Language(e),
|
||||
AttributeValue::AddressClass(e) => write::AttributeValue::AddressClass(e),
|
||||
AttributeValue::IdentifierCase(e) => write::AttributeValue::IdentifierCase(e),
|
||||
AttributeValue::CallingConvention(e) => write::AttributeValue::CallingConvention(e),
|
||||
AttributeValue::Inline(e) => write::AttributeValue::Inline(e),
|
||||
AttributeValue::Ordering(e) => write::AttributeValue::Ordering(e),
|
||||
AttributeValue::UnitRef(offset) => {
|
||||
pending_die_refs.insert(current_scope_id, attr.name(), offset);
|
||||
continue;
|
||||
}
|
||||
AttributeValue::DebugInfoRef(offset) => {
|
||||
pending_di_refs.insert(current_scope_id, attr.name(), offset);
|
||||
continue;
|
||||
}
|
||||
a => bail!("Unexpected attribute: {:?}", a),
|
||||
};
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(attr.name(), attr_value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn clone_attr_string<R>(
|
||||
attr_value: &AttributeValue<R>,
|
||||
form: gimli::DwForm,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
debug_str: &DebugStr<R>,
|
||||
debug_str_offsets: &DebugStrOffsets<R>,
|
||||
debug_line_str: &DebugLineStr<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<write::LineString, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let content = match attr_value {
|
||||
AttributeValue::DebugStrRef(str_offset) => {
|
||||
debug_str.get_str(*str_offset)?.to_slice()?.to_vec()
|
||||
}
|
||||
AttributeValue::DebugStrOffsetsIndex(i) => {
|
||||
let str_offset = debug_str_offsets.get_str_offset(
|
||||
gimli::Format::Dwarf32,
|
||||
unit.str_offsets_base,
|
||||
*i,
|
||||
)?;
|
||||
debug_str.get_str(str_offset)?.to_slice()?.to_vec()
|
||||
}
|
||||
AttributeValue::DebugLineStrRef(str_offset) => {
|
||||
debug_line_str.get_str(*str_offset)?.to_slice()?.to_vec()
|
||||
}
|
||||
AttributeValue::String(b) => b.to_slice()?.to_vec(),
|
||||
v => bail!("Unexpected attribute value: {:?}", v),
|
||||
};
|
||||
Ok(match form {
|
||||
gimli::DW_FORM_strp => {
|
||||
let id = out_strings.add(content);
|
||||
write::LineString::StringRef(id)
|
||||
}
|
||||
gimli::DW_FORM_string => write::LineString::String(content),
|
||||
_ => bail!("DW_FORM_line_strp or other not supported"),
|
||||
})
|
||||
}
|
||||
1313
crates/cranelift/src/debug/transform/expression.rs
Normal file
1313
crates/cranelift/src/debug/transform/expression.rs
Normal file
File diff suppressed because it is too large
Load Diff
283
crates/cranelift/src/debug/transform/line_program.rs
Normal file
283
crates/cranelift/src/debug/transform/line_program.rs
Normal file
@@ -0,0 +1,283 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::attr::clone_attr_string;
|
||||
use super::{Reader, TransformError};
|
||||
use anyhow::{Context, Error};
|
||||
use gimli::{
|
||||
write, DebugLine, DebugLineOffset, DebugLineStr, DebugStr, DebugStrOffsets,
|
||||
DebuggingInformationEntry, LineEncoding, Unit,
|
||||
};
|
||||
use more_asserts::assert_le;
|
||||
use wasmtime_environ::{DefinedFuncIndex, EntityRef};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SavedLineProgramRow {
|
||||
Normal {
|
||||
address: u64,
|
||||
op_index: u64,
|
||||
file_index: u64,
|
||||
line: u64,
|
||||
column: u64,
|
||||
discriminator: u64,
|
||||
is_stmt: bool,
|
||||
basic_block: bool,
|
||||
prologue_end: bool,
|
||||
epilogue_begin: bool,
|
||||
isa: u64,
|
||||
},
|
||||
EndOfSequence(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FuncRows {
|
||||
index: DefinedFuncIndex,
|
||||
sorted_rows: Vec<(u64, SavedLineProgramRow)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum ReadLineProgramState {
|
||||
SequenceEnded,
|
||||
ReadSequence(DefinedFuncIndex),
|
||||
IgnoreSequence,
|
||||
}
|
||||
|
||||
pub(crate) fn clone_line_program<R>(
|
||||
unit: &Unit<R, R::Offset>,
|
||||
root: &DebuggingInformationEntry<R>,
|
||||
addr_tr: &AddressTransform,
|
||||
out_encoding: gimli::Encoding,
|
||||
debug_str: &DebugStr<R>,
|
||||
debug_str_offsets: &DebugStrOffsets<R>,
|
||||
debug_line_str: &DebugLineStr<R>,
|
||||
debug_line: &DebugLine<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>, u64), Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let offset = match root.attr_value(gimli::DW_AT_stmt_list)? {
|
||||
Some(gimli::AttributeValue::DebugLineRef(offset)) => offset,
|
||||
_ => {
|
||||
return Err(TransformError("Debug line offset is not found").into());
|
||||
}
|
||||
};
|
||||
let comp_dir = root.attr_value(gimli::DW_AT_comp_dir)?;
|
||||
let comp_name = root.attr_value(gimli::DW_AT_name)?;
|
||||
let out_comp_dir = match &comp_dir {
|
||||
Some(comp_dir) => Some(clone_attr_string(
|
||||
comp_dir,
|
||||
gimli::DW_FORM_strp,
|
||||
unit,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_line_str,
|
||||
out_strings,
|
||||
)?),
|
||||
None => None,
|
||||
};
|
||||
let out_comp_name = clone_attr_string(
|
||||
comp_name.as_ref().context("missing DW_AT_name attribute")?,
|
||||
gimli::DW_FORM_strp,
|
||||
unit,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_line_str,
|
||||
out_strings,
|
||||
)?;
|
||||
|
||||
let program = debug_line.program(
|
||||
offset,
|
||||
unit.header.address_size(),
|
||||
comp_dir.and_then(|val| val.string_value(&debug_str)),
|
||||
comp_name.and_then(|val| val.string_value(&debug_str)),
|
||||
);
|
||||
if let Ok(program) = program {
|
||||
let header = program.header();
|
||||
let file_index_base = if header.version() < 5 { 1 } else { 0 };
|
||||
assert_le!(header.version(), 5, "not supported 6");
|
||||
let line_encoding = LineEncoding {
|
||||
minimum_instruction_length: header.minimum_instruction_length(),
|
||||
maximum_operations_per_instruction: header.maximum_operations_per_instruction(),
|
||||
default_is_stmt: header.default_is_stmt(),
|
||||
line_base: header.line_base(),
|
||||
line_range: header.line_range(),
|
||||
};
|
||||
let mut out_program = write::LineProgram::new(
|
||||
out_encoding,
|
||||
line_encoding,
|
||||
out_comp_dir.unwrap_or_else(|| write::LineString::String(Vec::new())),
|
||||
out_comp_name,
|
||||
None,
|
||||
);
|
||||
let mut dirs = Vec::new();
|
||||
dirs.push(out_program.default_directory());
|
||||
for dir_attr in header.include_directories() {
|
||||
let dir_id = out_program.add_directory(clone_attr_string(
|
||||
dir_attr,
|
||||
gimli::DW_FORM_string,
|
||||
unit,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_line_str,
|
||||
out_strings,
|
||||
)?);
|
||||
dirs.push(dir_id);
|
||||
}
|
||||
let mut files = Vec::new();
|
||||
// Since we are outputting DWARF-4, perform base change.
|
||||
let directory_index_correction = if header.version() >= 5 { 1 } else { 0 };
|
||||
for file_entry in header.file_names() {
|
||||
let dir_index = file_entry.directory_index() + directory_index_correction;
|
||||
let dir_id = dirs[dir_index as usize];
|
||||
let file_id = out_program.add_file(
|
||||
clone_attr_string(
|
||||
&file_entry.path_name(),
|
||||
gimli::DW_FORM_string,
|
||||
unit,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_line_str,
|
||||
out_strings,
|
||||
)?,
|
||||
dir_id,
|
||||
None,
|
||||
);
|
||||
files.push(file_id);
|
||||
}
|
||||
|
||||
let mut rows = program.rows();
|
||||
let mut func_rows = Vec::new();
|
||||
let mut saved_rows: Vec<(u64, SavedLineProgramRow)> = Vec::new();
|
||||
let mut state = ReadLineProgramState::SequenceEnded;
|
||||
while let Some((_header, row)) = rows.next_row()? {
|
||||
if state == ReadLineProgramState::IgnoreSequence {
|
||||
if row.end_sequence() {
|
||||
state = ReadLineProgramState::SequenceEnded;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let saved_row = if row.end_sequence() {
|
||||
let index = match state {
|
||||
ReadLineProgramState::ReadSequence(index) => index,
|
||||
_ => panic!(),
|
||||
};
|
||||
saved_rows.sort_by_key(|r| r.0);
|
||||
func_rows.push(FuncRows {
|
||||
index,
|
||||
sorted_rows: saved_rows,
|
||||
});
|
||||
|
||||
saved_rows = Vec::new();
|
||||
state = ReadLineProgramState::SequenceEnded;
|
||||
SavedLineProgramRow::EndOfSequence(row.address())
|
||||
} else {
|
||||
if state == ReadLineProgramState::SequenceEnded {
|
||||
// Discard sequences for non-existent code.
|
||||
if row.address() == 0 {
|
||||
state = ReadLineProgramState::IgnoreSequence;
|
||||
continue;
|
||||
}
|
||||
match addr_tr.find_func_index(row.address()) {
|
||||
Some(index) => {
|
||||
state = ReadLineProgramState::ReadSequence(index);
|
||||
}
|
||||
None => {
|
||||
// Some non-existent address found.
|
||||
state = ReadLineProgramState::IgnoreSequence;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
SavedLineProgramRow::Normal {
|
||||
address: row.address(),
|
||||
op_index: row.op_index(),
|
||||
file_index: row.file_index(),
|
||||
line: row.line().map(|nonzero| nonzero.get()).unwrap_or(0),
|
||||
column: match row.column() {
|
||||
gimli::ColumnType::LeftEdge => 0,
|
||||
gimli::ColumnType::Column(val) => val.get(),
|
||||
},
|
||||
discriminator: row.discriminator(),
|
||||
is_stmt: row.is_stmt(),
|
||||
basic_block: row.basic_block(),
|
||||
prologue_end: row.prologue_end(),
|
||||
epilogue_begin: row.epilogue_begin(),
|
||||
isa: row.isa(),
|
||||
}
|
||||
};
|
||||
saved_rows.push((row.address(), saved_row));
|
||||
}
|
||||
|
||||
for FuncRows {
|
||||
index,
|
||||
sorted_rows: saved_rows,
|
||||
} in func_rows
|
||||
{
|
||||
let map = match addr_tr.map().get(index) {
|
||||
Some(map) if map.len > 0 => map,
|
||||
_ => {
|
||||
continue; // no code generated
|
||||
}
|
||||
};
|
||||
let symbol = index.index();
|
||||
let base_addr = map.offset;
|
||||
out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
|
||||
// TODO track and place function declaration line here
|
||||
let mut last_address = None;
|
||||
for addr_map in map.addresses.iter() {
|
||||
let saved_row = match saved_rows.binary_search_by_key(&addr_map.wasm, |i| i.0) {
|
||||
Ok(i) => Some(&saved_rows[i].1),
|
||||
Err(i) => {
|
||||
if i > 0 {
|
||||
Some(&saved_rows[i - 1].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(SavedLineProgramRow::Normal {
|
||||
address,
|
||||
op_index,
|
||||
file_index,
|
||||
line,
|
||||
column,
|
||||
discriminator,
|
||||
is_stmt,
|
||||
basic_block,
|
||||
prologue_end,
|
||||
epilogue_begin,
|
||||
isa,
|
||||
}) = saved_row
|
||||
{
|
||||
// Ignore duplicates
|
||||
if Some(*address) != last_address {
|
||||
let address_offset = if last_address.is_none() {
|
||||
// Extend first entry to the function declaration
|
||||
// TODO use the function declaration line instead
|
||||
0
|
||||
} else {
|
||||
(addr_map.generated - base_addr) as u64
|
||||
};
|
||||
out_program.row().address_offset = address_offset;
|
||||
out_program.row().op_index = *op_index;
|
||||
out_program.row().file = files[(file_index - file_index_base) as usize];
|
||||
out_program.row().line = *line;
|
||||
out_program.row().column = *column;
|
||||
out_program.row().discriminator = *discriminator;
|
||||
out_program.row().is_statement = *is_stmt;
|
||||
out_program.row().basic_block = *basic_block;
|
||||
out_program.row().prologue_end = *prologue_end;
|
||||
out_program.row().epilogue_begin = *epilogue_begin;
|
||||
out_program.row().isa = *isa;
|
||||
out_program.generate_row();
|
||||
last_address = Some(*address);
|
||||
}
|
||||
}
|
||||
}
|
||||
let end_addr = (map.offset + map.len) as u64;
|
||||
out_program.end_sequence(end_addr);
|
||||
}
|
||||
Ok((out_program, offset, files, file_index_base))
|
||||
} else {
|
||||
Err(TransformError("Valid line program not found").into())
|
||||
}
|
||||
}
|
||||
125
crates/cranelift/src/debug/transform/mod.rs
Normal file
125
crates/cranelift/src/debug/transform/mod.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use self::refs::DebugInfoRefsMap;
|
||||
use self::simulate::generate_simulated_dwarf;
|
||||
use self::unit::clone_unit;
|
||||
use crate::debug::gc::build_dependencies;
|
||||
use crate::CompiledFunctions;
|
||||
use anyhow::Error;
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use gimli::{
|
||||
write, DebugAddr, DebugLine, DebugLineStr, DebugStr, DebugStrOffsets, LocationLists,
|
||||
RangeLists, UnitSectionOffset,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use thiserror::Error;
|
||||
use wasmtime_environ::{DebugInfoData, ModuleMemoryOffset};
|
||||
|
||||
pub use address_transform::AddressTransform;
|
||||
|
||||
mod address_transform;
|
||||
mod attr;
|
||||
mod expression;
|
||||
mod line_program;
|
||||
mod range_info_builder;
|
||||
mod refs;
|
||||
mod simulate;
|
||||
mod unit;
|
||||
mod utils;
|
||||
|
||||
pub(crate) trait Reader: gimli::Reader<Offset = usize> {}
|
||||
|
||||
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where Endian: gimli::Endianity {}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Debug info transform error: {0}")]
|
||||
pub struct TransformError(&'static str);
|
||||
|
||||
pub(crate) struct DebugInputContext<'a, R>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
debug_str: &'a DebugStr<R>,
|
||||
debug_str_offsets: &'a DebugStrOffsets<R>,
|
||||
debug_line_str: &'a DebugLineStr<R>,
|
||||
debug_line: &'a DebugLine<R>,
|
||||
debug_addr: &'a DebugAddr<R>,
|
||||
rnglists: &'a RangeLists<R>,
|
||||
loclists: &'a LocationLists<R>,
|
||||
reachable: &'a HashSet<UnitSectionOffset>,
|
||||
}
|
||||
|
||||
pub fn transform_dwarf(
|
||||
isa: &dyn TargetIsa,
|
||||
di: &DebugInfoData,
|
||||
funcs: &CompiledFunctions,
|
||||
memory_offset: &ModuleMemoryOffset,
|
||||
) -> Result<write::Dwarf, Error> {
|
||||
let addr_tr = AddressTransform::new(funcs, &di.wasm_file);
|
||||
let reachable = build_dependencies(&di.dwarf, &addr_tr)?.get_reachable();
|
||||
|
||||
let context = DebugInputContext {
|
||||
debug_str: &di.dwarf.debug_str,
|
||||
debug_str_offsets: &di.dwarf.debug_str_offsets,
|
||||
debug_line_str: &di.dwarf.debug_line_str,
|
||||
debug_line: &di.dwarf.debug_line,
|
||||
debug_addr: &di.dwarf.debug_addr,
|
||||
rnglists: &di.dwarf.ranges,
|
||||
loclists: &di.dwarf.locations,
|
||||
reachable: &reachable,
|
||||
};
|
||||
|
||||
let out_encoding = gimli::Encoding {
|
||||
format: gimli::Format::Dwarf32,
|
||||
// TODO: this should be configurable
|
||||
version: 4,
|
||||
address_size: isa.pointer_bytes(),
|
||||
};
|
||||
|
||||
let mut out_strings = write::StringTable::default();
|
||||
let mut out_units = write::UnitTable::default();
|
||||
|
||||
let out_line_strings = write::LineStringTable::default();
|
||||
let mut pending_di_refs = Vec::new();
|
||||
let mut di_ref_map = DebugInfoRefsMap::new();
|
||||
|
||||
let mut translated = HashSet::new();
|
||||
let mut iter = di.dwarf.debug_info.units();
|
||||
while let Some(header) = iter.next().unwrap_or(None) {
|
||||
let unit = di.dwarf.unit(header)?;
|
||||
if let Some((id, ref_map, pending_refs)) = clone_unit(
|
||||
&di.dwarf,
|
||||
unit,
|
||||
&context,
|
||||
&addr_tr,
|
||||
funcs,
|
||||
memory_offset,
|
||||
out_encoding,
|
||||
&mut out_units,
|
||||
&mut out_strings,
|
||||
&mut translated,
|
||||
isa,
|
||||
)? {
|
||||
di_ref_map.insert(&header, id, ref_map);
|
||||
pending_di_refs.push((id, pending_refs));
|
||||
}
|
||||
}
|
||||
di_ref_map.patch(pending_di_refs.into_iter(), &mut out_units);
|
||||
|
||||
generate_simulated_dwarf(
|
||||
&addr_tr,
|
||||
di,
|
||||
memory_offset,
|
||||
funcs,
|
||||
&translated,
|
||||
out_encoding,
|
||||
&mut out_units,
|
||||
&mut out_strings,
|
||||
isa,
|
||||
)?;
|
||||
|
||||
Ok(write::Dwarf {
|
||||
units: out_units,
|
||||
line_programs: vec![],
|
||||
line_strings: out_line_strings,
|
||||
strings: out_strings,
|
||||
})
|
||||
}
|
||||
222
crates/cranelift/src/debug/transform/range_info_builder.rs
Normal file
222
crates/cranelift/src/debug/transform/range_info_builder.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::{DebugInputContext, Reader};
|
||||
use anyhow::Error;
|
||||
use gimli::{write, AttributeValue, DebuggingInformationEntry, RangeListsOffset, Unit};
|
||||
use more_asserts::assert_lt;
|
||||
use wasmtime_environ::{DefinedFuncIndex, EntityRef};
|
||||
|
||||
pub(crate) enum RangeInfoBuilder {
|
||||
Undefined,
|
||||
Position(u64),
|
||||
Ranges(Vec<(u64, u64)>),
|
||||
Function(DefinedFuncIndex),
|
||||
}
|
||||
|
||||
impl RangeInfoBuilder {
|
||||
pub(crate) fn from<R>(
|
||||
dwarf: &gimli::Dwarf<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
if let Some(AttributeValue::RangeListsRef(r)) = entry.attr_value(gimli::DW_AT_ranges)? {
|
||||
let r = dwarf.ranges_offset_from_raw(unit, r);
|
||||
return RangeInfoBuilder::from_ranges_ref(unit, r, context, cu_low_pc);
|
||||
};
|
||||
|
||||
let low_pc =
|
||||
if let Some(AttributeValue::Addr(addr)) = entry.attr_value(gimli::DW_AT_low_pc)? {
|
||||
addr
|
||||
} else if let Some(AttributeValue::DebugAddrIndex(i)) =
|
||||
entry.attr_value(gimli::DW_AT_low_pc)?
|
||||
{
|
||||
context.debug_addr.get_address(4, unit.addr_base, i)?
|
||||
} else {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
};
|
||||
|
||||
Ok(
|
||||
if let Some(AttributeValue::Udata(u)) = entry.attr_value(gimli::DW_AT_high_pc)? {
|
||||
RangeInfoBuilder::Ranges(vec![(low_pc, low_pc + u)])
|
||||
} else {
|
||||
RangeInfoBuilder::Position(low_pc)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_ranges_ref<R>(
|
||||
unit: &Unit<R, R::Offset>,
|
||||
ranges: RangeListsOffset,
|
||||
context: &DebugInputContext<R>,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let unit_encoding = unit.encoding();
|
||||
let mut ranges = context.rnglists.ranges(
|
||||
ranges,
|
||||
unit_encoding,
|
||||
cu_low_pc,
|
||||
&context.debug_addr,
|
||||
unit.addr_base,
|
||||
)?;
|
||||
let mut result = Vec::new();
|
||||
while let Some(range) = ranges.next()? {
|
||||
if range.begin >= range.end {
|
||||
// ignore empty ranges
|
||||
}
|
||||
result.push((range.begin, range.end));
|
||||
}
|
||||
|
||||
Ok(if result.is_empty() {
|
||||
RangeInfoBuilder::Undefined
|
||||
} else {
|
||||
RangeInfoBuilder::Ranges(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_subprogram_die<R>(
|
||||
dwarf: &gimli::Dwarf<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &AddressTransform,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let unit_encoding = unit.encoding();
|
||||
let addr =
|
||||
if let Some(AttributeValue::Addr(addr)) = entry.attr_value(gimli::DW_AT_low_pc)? {
|
||||
addr
|
||||
} else if let Some(AttributeValue::DebugAddrIndex(i)) =
|
||||
entry.attr_value(gimli::DW_AT_low_pc)?
|
||||
{
|
||||
context.debug_addr.get_address(4, unit.addr_base, i)?
|
||||
} else if let Some(AttributeValue::RangeListsRef(r)) =
|
||||
entry.attr_value(gimli::DW_AT_ranges)?
|
||||
{
|
||||
let r = dwarf.ranges_offset_from_raw(unit, r);
|
||||
let mut ranges = context.rnglists.ranges(
|
||||
r,
|
||||
unit_encoding,
|
||||
cu_low_pc,
|
||||
&context.debug_addr,
|
||||
unit.addr_base,
|
||||
)?;
|
||||
if let Some(range) = ranges.next()? {
|
||||
range.begin
|
||||
} else {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
}
|
||||
} else {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
};
|
||||
|
||||
let index = addr_tr.find_func_index(addr);
|
||||
if index.is_none() {
|
||||
return Ok(RangeInfoBuilder::Undefined);
|
||||
}
|
||||
Ok(RangeInfoBuilder::Function(index.unwrap()))
|
||||
}
|
||||
|
||||
pub(crate) fn build(
|
||||
&self,
|
||||
addr_tr: &AddressTransform,
|
||||
out_unit: &mut write::Unit,
|
||||
current_scope_id: write::UnitEntryId,
|
||||
) {
|
||||
match self {
|
||||
RangeInfoBuilder::Undefined => (),
|
||||
RangeInfoBuilder::Position(pc) => {
|
||||
let addr = addr_tr
|
||||
.translate(*pc)
|
||||
.unwrap_or(write::Address::Constant(0));
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(addr));
|
||||
}
|
||||
RangeInfoBuilder::Ranges(ranges) => {
|
||||
let mut result = Vec::new();
|
||||
for (begin, end) in ranges {
|
||||
result.extend(addr_tr.translate_ranges(*begin, *end));
|
||||
}
|
||||
if result.len() != 1 {
|
||||
let range_list = result
|
||||
.iter()
|
||||
.map(|tr| write::Range::StartLength {
|
||||
begin: tr.0,
|
||||
length: tr.1,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let range_list_id = out_unit.ranges.add(write::RangeList(range_list));
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_ranges,
|
||||
write::AttributeValue::RangeListRef(range_list_id),
|
||||
);
|
||||
} else {
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_low_pc,
|
||||
write::AttributeValue::Address(result[0].0),
|
||||
);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_high_pc,
|
||||
write::AttributeValue::Udata(result[0].1),
|
||||
);
|
||||
}
|
||||
}
|
||||
RangeInfoBuilder::Function(index) => {
|
||||
let range = addr_tr.func_range(*index);
|
||||
let symbol = index.index();
|
||||
let addr = write::Address::Symbol {
|
||||
symbol,
|
||||
addend: range.0 as i64,
|
||||
};
|
||||
let len = (range.1 - range.0) as u64;
|
||||
let current_scope = out_unit.get_mut(current_scope_id);
|
||||
current_scope.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(addr));
|
||||
current_scope.set(gimli::DW_AT_high_pc, write::AttributeValue::Udata(len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_ranges(&self, addr_tr: &AddressTransform) -> Vec<(u64, u64)> {
|
||||
match self {
|
||||
RangeInfoBuilder::Undefined | RangeInfoBuilder::Position(_) => vec![],
|
||||
RangeInfoBuilder::Ranges(ranges) => ranges.clone(),
|
||||
RangeInfoBuilder::Function(index) => {
|
||||
let range = addr_tr.func_source_range(*index);
|
||||
vec![(range.0, range.1)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_ranges(
|
||||
&self,
|
||||
addr_tr: &AddressTransform,
|
||||
out_range_lists: &mut write::RangeListTable,
|
||||
) -> write::RangeListId {
|
||||
if let RangeInfoBuilder::Ranges(ranges) = self {
|
||||
let mut range_list = Vec::new();
|
||||
for (begin, end) in ranges {
|
||||
assert_lt!(begin, end);
|
||||
range_list.extend(addr_tr.translate_ranges(*begin, *end).map(|tr| {
|
||||
write::Range::StartLength {
|
||||
begin: tr.0,
|
||||
length: tr.1,
|
||||
}
|
||||
}));
|
||||
}
|
||||
out_range_lists.add(write::RangeList(range_list))
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
crates/cranelift/src/debug/transform/refs.rs
Normal file
111
crates/cranelift/src/debug/transform/refs.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Helper utils for tracking and patching intra unit or section references.
|
||||
|
||||
use gimli::write;
|
||||
use gimli::{DebugInfoOffset, Reader, UnitHeader, UnitOffset};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Stores compiled unit references: UnitEntryId+DwAt denotes a patch location
|
||||
/// and UnitOffset is a location in original DWARF.
|
||||
pub struct PendingUnitRefs {
|
||||
refs: Vec<(write::UnitEntryId, gimli::DwAt, UnitOffset)>,
|
||||
}
|
||||
|
||||
impl PendingUnitRefs {
|
||||
pub fn new() -> Self {
|
||||
Self { refs: Vec::new() }
|
||||
}
|
||||
pub fn insert(&mut self, entry_id: write::UnitEntryId, attr: gimli::DwAt, offset: UnitOffset) {
|
||||
self.refs.push((entry_id, attr, offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores .debug_info references: UnitEntryId+DwAt denotes a patch location
|
||||
/// and DebugInfoOffset is a location in original DWARF.
|
||||
pub struct PendingDebugInfoRefs {
|
||||
refs: Vec<(write::UnitEntryId, gimli::DwAt, DebugInfoOffset)>,
|
||||
}
|
||||
|
||||
impl PendingDebugInfoRefs {
|
||||
pub fn new() -> Self {
|
||||
Self { refs: Vec::new() }
|
||||
}
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
entry_id: write::UnitEntryId,
|
||||
attr: gimli::DwAt,
|
||||
offset: DebugInfoOffset,
|
||||
) {
|
||||
self.refs.push((entry_id, attr, offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores map between read and written references of DWARF entries of
|
||||
/// a compiled unit.
|
||||
pub struct UnitRefsMap {
|
||||
map: HashMap<UnitOffset, write::UnitEntryId>,
|
||||
}
|
||||
|
||||
impl UnitRefsMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn insert(&mut self, offset: UnitOffset, entry_id: write::UnitEntryId) {
|
||||
self.map.insert(offset, entry_id);
|
||||
}
|
||||
pub fn patch(&self, refs: PendingUnitRefs, comp_unit: &mut write::Unit) {
|
||||
for (die_id, attr_name, offset) in refs.refs {
|
||||
let die = comp_unit.get_mut(die_id);
|
||||
if let Some(unit_id) = self.map.get(&offset) {
|
||||
die.set(attr_name, write::AttributeValue::UnitRef(*unit_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores map between read and written references of DWARF entries of
|
||||
/// the entire .debug_info.
|
||||
pub struct DebugInfoRefsMap {
|
||||
map: HashMap<DebugInfoOffset, (write::UnitId, write::UnitEntryId)>,
|
||||
}
|
||||
|
||||
impl DebugInfoRefsMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn insert<R>(&mut self, unit: &UnitHeader<R>, unit_id: write::UnitId, unit_map: UnitRefsMap)
|
||||
where
|
||||
R: Reader<Offset = usize>,
|
||||
{
|
||||
self.map
|
||||
.extend(unit_map.map.into_iter().map(|(off, entry_id)| {
|
||||
let off = off
|
||||
.to_debug_info_offset(unit)
|
||||
.expect("should be in debug_info section");
|
||||
(off, (unit_id, entry_id))
|
||||
}));
|
||||
}
|
||||
pub fn patch(
|
||||
&self,
|
||||
refs: impl Iterator<Item = (write::UnitId, PendingDebugInfoRefs)>,
|
||||
units: &mut write::UnitTable,
|
||||
) {
|
||||
for (id, refs) in refs {
|
||||
let unit = units.get_mut(id);
|
||||
for (die_id, attr_name, offset) in refs.refs {
|
||||
let die = unit.get_mut(die_id);
|
||||
if let Some((id, entry_id)) = self.map.get(&offset) {
|
||||
die.set(
|
||||
attr_name,
|
||||
write::AttributeValue::DebugInfoRef(write::Reference::Entry(
|
||||
*id, *entry_id,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
410
crates/cranelift/src/debug/transform/simulate.rs
Normal file
410
crates/cranelift/src/debug/transform/simulate.rs
Normal file
@@ -0,0 +1,410 @@
|
||||
use super::expression::{CompiledExpression, FunctionFrameInfo};
|
||||
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
|
||||
use super::AddressTransform;
|
||||
use crate::CompiledFunctions;
|
||||
use anyhow::{Context, Error};
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use cranelift_wasm::get_vmctx_value_label;
|
||||
use gimli::write;
|
||||
use gimli::{self, LineEncoding};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
|
||||
use wasmparser::Type as WasmType;
|
||||
use wasmtime_environ::{
|
||||
DebugInfoData, DefinedFuncIndex, EntityRef, FunctionMetadata, ModuleMemoryOffset, WasmFileInfo,
|
||||
};
|
||||
|
||||
const PRODUCER_NAME: &str = "wasmtime";
|
||||
|
||||
macro_rules! assert_dwarf_str {
|
||||
($s:expr) => {{
|
||||
let s = $s;
|
||||
if cfg!(debug_assertions) {
|
||||
// Perform check the same way as gimli does it.
|
||||
let bytes: Vec<u8> = s.clone().into();
|
||||
debug_assert!(!bytes.contains(&0), "DWARF string shall not have NULL byte");
|
||||
}
|
||||
s
|
||||
}};
|
||||
}
|
||||
|
||||
fn generate_line_info(
|
||||
addr_tr: &AddressTransform,
|
||||
translated: &HashSet<DefinedFuncIndex>,
|
||||
out_encoding: gimli::Encoding,
|
||||
w: &WasmFileInfo,
|
||||
comp_dir_id: write::StringId,
|
||||
name_id: write::StringId,
|
||||
name: &str,
|
||||
) -> Result<write::LineProgram, Error> {
|
||||
let out_comp_dir = write::LineString::StringRef(comp_dir_id);
|
||||
let out_comp_name = write::LineString::StringRef(name_id);
|
||||
|
||||
let line_encoding = LineEncoding::default();
|
||||
|
||||
let mut out_program = write::LineProgram::new(
|
||||
out_encoding,
|
||||
line_encoding,
|
||||
out_comp_dir,
|
||||
out_comp_name,
|
||||
None,
|
||||
);
|
||||
|
||||
let file_index = out_program.add_file(
|
||||
write::LineString::String(name.as_bytes().to_vec()),
|
||||
out_program.default_directory(),
|
||||
None,
|
||||
);
|
||||
|
||||
for (i, map) in addr_tr.map() {
|
||||
let symbol = i.index();
|
||||
if translated.contains(&i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let base_addr = map.offset;
|
||||
out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
|
||||
for addr_map in map.addresses.iter() {
|
||||
let address_offset = (addr_map.generated - base_addr) as u64;
|
||||
out_program.row().address_offset = address_offset;
|
||||
out_program.row().op_index = 0;
|
||||
out_program.row().file = file_index;
|
||||
let wasm_offset = w.code_section_offset + addr_map.wasm as u64;
|
||||
out_program.row().line = wasm_offset;
|
||||
out_program.row().column = 0;
|
||||
out_program.row().discriminator = 1;
|
||||
out_program.row().is_statement = true;
|
||||
out_program.row().basic_block = false;
|
||||
out_program.row().prologue_end = false;
|
||||
out_program.row().epilogue_begin = false;
|
||||
out_program.row().isa = 0;
|
||||
out_program.generate_row();
|
||||
}
|
||||
let end_addr = (map.offset + map.len - 1) as u64;
|
||||
out_program.end_sequence(end_addr);
|
||||
}
|
||||
|
||||
Ok(out_program)
|
||||
}
|
||||
|
||||
fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
|
||||
if s.contains('\x00') {
|
||||
None
|
||||
} else {
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
let module_name = di
|
||||
.name_section
|
||||
.module_name
|
||||
.and_then(check_invalid_chars_in_name)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| format!("<gen-{}>", NEXT_ID.fetch_add(1, SeqCst)));
|
||||
let path = format!("/<wasm-module>/{}.wasm", module_name);
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
struct WasmTypesDieRefs {
|
||||
vmctx: write::UnitEntryId,
|
||||
i32: write::UnitEntryId,
|
||||
i64: write::UnitEntryId,
|
||||
f32: write::UnitEntryId,
|
||||
f64: write::UnitEntryId,
|
||||
}
|
||||
|
||||
fn add_wasm_types(
|
||||
unit: &mut write::Unit,
|
||||
root_id: write::UnitEntryId,
|
||||
out_strings: &mut write::StringTable,
|
||||
memory_offset: &ModuleMemoryOffset,
|
||||
) -> WasmTypesDieRefs {
|
||||
let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, memory_offset);
|
||||
|
||||
macro_rules! def_type {
|
||||
($id:literal, $size:literal, $enc:path) => {{
|
||||
let die_id = unit.add(root_id, gimli::DW_TAG_base_type);
|
||||
let die = unit.get_mut(die_id);
|
||||
die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add($id)),
|
||||
);
|
||||
die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));
|
||||
die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));
|
||||
die_id
|
||||
}};
|
||||
}
|
||||
|
||||
let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);
|
||||
let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);
|
||||
let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
|
||||
let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
|
||||
|
||||
WasmTypesDieRefs {
|
||||
vmctx: vmctx_die_id,
|
||||
i32: i32_die_id,
|
||||
i64: i64_die_id,
|
||||
f32: f32_die_id,
|
||||
f64: f64_die_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_var_type(
|
||||
index: usize,
|
||||
wasm_types: &WasmTypesDieRefs,
|
||||
func_meta: &FunctionMetadata,
|
||||
) -> Option<(write::UnitEntryId, bool)> {
|
||||
let (ty, is_param) = if index < func_meta.params.len() {
|
||||
(func_meta.params[index], true)
|
||||
} else {
|
||||
let mut i = (index - func_meta.params.len()) as u32;
|
||||
let mut j = 0;
|
||||
while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
|
||||
i -= func_meta.locals[j].0;
|
||||
j += 1;
|
||||
}
|
||||
if j >= func_meta.locals.len() {
|
||||
// Ignore the var index out of bound.
|
||||
return None;
|
||||
}
|
||||
(func_meta.locals[j].1, false)
|
||||
};
|
||||
let type_die_id = match ty {
|
||||
WasmType::I32 => wasm_types.i32,
|
||||
WasmType::I64 => wasm_types.i64,
|
||||
WasmType::F32 => wasm_types.f32,
|
||||
WasmType::F64 => wasm_types.f64,
|
||||
_ => {
|
||||
// Ignore unsupported types.
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some((type_die_id, is_param))
|
||||
}
|
||||
|
||||
fn generate_vars(
|
||||
unit: &mut write::Unit,
|
||||
die_id: write::UnitEntryId,
|
||||
addr_tr: &AddressTransform,
|
||||
frame_info: &FunctionFrameInfo,
|
||||
scope_ranges: &[(u64, u64)],
|
||||
wasm_types: &WasmTypesDieRefs,
|
||||
func_meta: &FunctionMetadata,
|
||||
locals_names: Option<&HashMap<u32, &str>>,
|
||||
out_strings: &mut write::StringTable,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> Result<(), Error> {
|
||||
let vmctx_label = get_vmctx_value_label();
|
||||
|
||||
// Normalize order of ValueLabelsRanges keys to have reproducable results.
|
||||
let mut vars = frame_info.value_ranges.keys().collect::<Vec<_>>();
|
||||
vars.sort_by(|a, b| a.index().cmp(&b.index()));
|
||||
|
||||
for label in vars {
|
||||
if label.index() == vmctx_label.index() {
|
||||
append_vmctx_info(
|
||||
unit,
|
||||
die_id,
|
||||
wasm_types.vmctx,
|
||||
addr_tr,
|
||||
Some(frame_info),
|
||||
scope_ranges,
|
||||
out_strings,
|
||||
isa,
|
||||
)?;
|
||||
} else {
|
||||
let var_index = label.index();
|
||||
let (type_die_id, is_param) =
|
||||
if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {
|
||||
result
|
||||
} else {
|
||||
// Skipping if type of local cannot be detected.
|
||||
continue;
|
||||
};
|
||||
|
||||
let loc_list_id = {
|
||||
let locs = CompiledExpression::from_label(*label)
|
||||
.build_with_locals(scope_ranges, addr_tr, Some(frame_info), isa)
|
||||
.map(|i| {
|
||||
i.map(|(begin, length, data)| write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
unit.locations.add(write::LocationList(locs))
|
||||
};
|
||||
|
||||
let var_id = unit.add(
|
||||
die_id,
|
||||
if is_param {
|
||||
gimli::DW_TAG_formal_parameter
|
||||
} else {
|
||||
gimli::DW_TAG_variable
|
||||
},
|
||||
);
|
||||
let var = unit.get_mut(var_id);
|
||||
|
||||
let name_id = match locals_names
|
||||
.and_then(|m| m.get(&(var_index as u32)))
|
||||
.and_then(|s| check_invalid_chars_in_name(s))
|
||||
{
|
||||
Some(n) => out_strings.add(assert_dwarf_str!(n)),
|
||||
None => out_strings.add(format!("var{}", var_index)),
|
||||
};
|
||||
|
||||
var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
|
||||
var.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::UnitRef(type_die_id),
|
||||
);
|
||||
var.set(
|
||||
gimli::DW_AT_location,
|
||||
write::AttributeValue::LocationListRef(loc_list_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {
|
||||
path.clone()
|
||||
.to_str()
|
||||
.and_then(move |s| if s.contains('\x00') { None } else { Some(path) })
|
||||
}
|
||||
|
||||
pub fn generate_simulated_dwarf(
|
||||
addr_tr: &AddressTransform,
|
||||
di: &DebugInfoData,
|
||||
memory_offset: &ModuleMemoryOffset,
|
||||
funcs: &CompiledFunctions,
|
||||
translated: &HashSet<DefinedFuncIndex>,
|
||||
out_encoding: gimli::Encoding,
|
||||
out_units: &mut write::UnitTable,
|
||||
out_strings: &mut write::StringTable,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> Result<(), Error> {
|
||||
let path = di
|
||||
.wasm_file
|
||||
.path
|
||||
.to_owned()
|
||||
.and_then(check_invalid_chars_in_path)
|
||||
.unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
|
||||
|
||||
let func_names = &di.name_section.func_names;
|
||||
let locals_names = &di.name_section.locals_names;
|
||||
let imported_func_count = di.wasm_file.imported_func_count;
|
||||
|
||||
let (unit, root_id, name_id) = {
|
||||
let comp_dir_id = out_strings.add(assert_dwarf_str!(path
|
||||
.parent()
|
||||
.context("path dir")?
|
||||
.to_str()
|
||||
.context("path dir encoding")?));
|
||||
let name = path
|
||||
.file_name()
|
||||
.context("path name")?
|
||||
.to_str()
|
||||
.context("path name encoding")?;
|
||||
let name_id = out_strings.add(assert_dwarf_str!(name));
|
||||
|
||||
let out_program = generate_line_info(
|
||||
addr_tr,
|
||||
translated,
|
||||
out_encoding,
|
||||
&di.wasm_file,
|
||||
comp_dir_id,
|
||||
name_id,
|
||||
name,
|
||||
)?;
|
||||
|
||||
let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));
|
||||
let unit = out_units.get_mut(unit_id);
|
||||
|
||||
let root_id = unit.root();
|
||||
let root = unit.get_mut(root_id);
|
||||
|
||||
let id = out_strings.add(PRODUCER_NAME);
|
||||
root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));
|
||||
root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
|
||||
root.set(
|
||||
gimli::DW_AT_stmt_list,
|
||||
write::AttributeValue::LineProgramRef,
|
||||
);
|
||||
root.set(
|
||||
gimli::DW_AT_comp_dir,
|
||||
write::AttributeValue::StringRef(comp_dir_id),
|
||||
);
|
||||
(unit, root_id, name_id)
|
||||
};
|
||||
|
||||
let wasm_types = add_wasm_types(unit, root_id, out_strings, memory_offset);
|
||||
|
||||
for (i, map) in addr_tr.map().iter() {
|
||||
let index = i.index();
|
||||
if translated.contains(&i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let start = map.offset as u64;
|
||||
let end = start + map.len as u64;
|
||||
let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
|
||||
let die = unit.get_mut(die_id);
|
||||
die.set(
|
||||
gimli::DW_AT_low_pc,
|
||||
write::AttributeValue::Address(write::Address::Symbol {
|
||||
symbol: index,
|
||||
addend: start as i64,
|
||||
}),
|
||||
);
|
||||
die.set(
|
||||
gimli::DW_AT_high_pc,
|
||||
write::AttributeValue::Udata((end - start) as u64),
|
||||
);
|
||||
|
||||
let func_index = imported_func_count + (index as u32);
|
||||
let id = match func_names
|
||||
.get(&func_index)
|
||||
.and_then(|s| check_invalid_chars_in_name(s))
|
||||
{
|
||||
Some(n) => out_strings.add(assert_dwarf_str!(n)),
|
||||
None => out_strings.add(format!("wasm-function[{}]", func_index)),
|
||||
};
|
||||
|
||||
die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
|
||||
|
||||
die.set(
|
||||
gimli::DW_AT_decl_file,
|
||||
write::AttributeValue::StringRef(name_id),
|
||||
);
|
||||
|
||||
let f_start = map.addresses[0].wasm;
|
||||
let wasm_offset = di.wasm_file.code_section_offset + f_start as u64;
|
||||
die.set(
|
||||
gimli::DW_AT_decl_file,
|
||||
write::AttributeValue::Udata(wasm_offset),
|
||||
);
|
||||
|
||||
if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, i) {
|
||||
let source_range = addr_tr.func_source_range(i);
|
||||
generate_vars(
|
||||
unit,
|
||||
die_id,
|
||||
addr_tr,
|
||||
&frame_info,
|
||||
&[(source_range.0, source_range.1)],
|
||||
&wasm_types,
|
||||
&di.wasm_file.funcs[index],
|
||||
locals_names.get(&(index as u32)),
|
||||
out_strings,
|
||||
isa,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
500
crates/cranelift/src/debug/transform/unit.rs
Normal file
500
crates/cranelift/src/debug/transform/unit.rs
Normal file
@@ -0,0 +1,500 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::attr::{clone_die_attributes, FileAttributeContext};
|
||||
use super::expression::compile_expression;
|
||||
use super::line_program::clone_line_program;
|
||||
use super::range_info_builder::RangeInfoBuilder;
|
||||
use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
|
||||
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
|
||||
use super::{DebugInputContext, Reader, TransformError};
|
||||
use crate::CompiledFunctions;
|
||||
use anyhow::{Context, Error};
|
||||
use cranelift_codegen::ir::Endianness;
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use gimli::write;
|
||||
use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
|
||||
use std::collections::HashSet;
|
||||
use wasmtime_environ::{DefinedFuncIndex, ModuleMemoryOffset};
|
||||
|
||||
struct InheritedAttr<T> {
|
||||
stack: Vec<(usize, T)>,
|
||||
}
|
||||
|
||||
impl<T> InheritedAttr<T> {
|
||||
fn new() -> Self {
|
||||
InheritedAttr { stack: Vec::new() }
|
||||
}
|
||||
|
||||
fn update(&mut self, depth: usize) {
|
||||
while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
|
||||
self.stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, depth: usize, value: T) {
|
||||
self.stack.push((depth, value));
|
||||
}
|
||||
|
||||
fn top(&self) -> Option<&T> {
|
||||
self.stack.last().map(|entry| &entry.1)
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.stack.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_base_type_name<R>(
|
||||
type_entry: &DebuggingInformationEntry<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
// FIXME remove recursion.
|
||||
if let Some(AttributeValue::UnitRef(ref offset)) = type_entry.attr_value(gimli::DW_AT_type)? {
|
||||
let mut entries = unit.entries_at_offset(*offset)?;
|
||||
entries.next_entry()?;
|
||||
if let Some(die) = entries.current() {
|
||||
if let Some(AttributeValue::DebugStrRef(str_offset)) =
|
||||
die.attr_value(gimli::DW_AT_name)?
|
||||
{
|
||||
return Ok(String::from(
|
||||
context.debug_str.get_str(str_offset)?.to_string()?,
|
||||
));
|
||||
}
|
||||
match die.tag() {
|
||||
gimli::DW_TAG_const_type => {
|
||||
return Ok(format!("const {}", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
gimli::DW_TAG_pointer_type => {
|
||||
return Ok(format!("{}*", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
gimli::DW_TAG_reference_type => {
|
||||
return Ok(format!("{}&", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
gimli::DW_TAG_array_type => {
|
||||
return Ok(format!("{}[]", get_base_type_name(die, unit, context)?));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(String::from("??"))
|
||||
}
|
||||
|
||||
enum WebAssemblyPtrKind {
|
||||
Reference,
|
||||
Pointer,
|
||||
}
|
||||
|
||||
/// Replaces WebAssembly pointer type DIE with the wrapper
|
||||
/// which natively represented by offset in a Wasm memory.
|
||||
///
|
||||
/// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`),
|
||||
/// which refers its base type (e.g. `T`), or is a
|
||||
/// DW_TAG_reference_type (e.g. `T&`).
|
||||
///
|
||||
/// The generated wrapper is a structure that contains only the
|
||||
/// `__ptr` field. The utility operators overloads is added to
|
||||
/// provide better debugging experience.
|
||||
///
|
||||
/// Wrappers of pointer and reference types are identical except for
|
||||
/// their name -- they are formatted and accessed from a debugger
|
||||
/// the same way.
|
||||
///
|
||||
/// Notice that "resolve_vmctx_memory_ptr" is external/builtin
|
||||
/// subprogram that is not part of Wasm code.
|
||||
fn replace_pointer_type<R>(
|
||||
parent_id: write::UnitEntryId,
|
||||
kind: WebAssemblyPtrKind,
|
||||
comp_unit: &mut write::Unit,
|
||||
wp_die_id: write::UnitEntryId,
|
||||
pointer_type_entry: &DebuggingInformationEntry<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
pending_die_refs: &mut PendingUnitRefs,
|
||||
) -> Result<write::UnitEntryId, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
const WASM_PTR_LEN: u8 = 4;
|
||||
|
||||
macro_rules! add_tag {
|
||||
($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
|
||||
let $die_id = comp_unit.add($parent_id, $tag);
|
||||
#[allow(unused_variables)]
|
||||
let $die = comp_unit.get_mut($die_id);
|
||||
$( $die.set($a, $v); )*
|
||||
};
|
||||
}
|
||||
|
||||
// Build DW_TAG_structure_type for the wrapper:
|
||||
// .. DW_AT_name = "WebAssemblyPtrWrapper<T>",
|
||||
// .. DW_AT_byte_size = 4,
|
||||
let name = match kind {
|
||||
WebAssemblyPtrKind::Pointer => format!(
|
||||
"WebAssemblyPtrWrapper<{}>",
|
||||
get_base_type_name(pointer_type_entry, unit, context)?
|
||||
),
|
||||
WebAssemblyPtrKind::Reference => format!(
|
||||
"WebAssemblyRefWrapper<{}>",
|
||||
get_base_type_name(pointer_type_entry, unit, context)?
|
||||
),
|
||||
};
|
||||
add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(name.as_str())),
|
||||
gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN)
|
||||
});
|
||||
|
||||
// Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`:
|
||||
// .. DW_AT_type = <wrapper_die>
|
||||
add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id {
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_die_id)
|
||||
});
|
||||
|
||||
let base_type_id = pointer_type_entry.attr_value(gimli::DW_AT_type)?;
|
||||
// Build DW_TAG_reference_type for `T&`:
|
||||
// .. DW_AT_type = <base_type>
|
||||
add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {});
|
||||
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
|
||||
pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset);
|
||||
}
|
||||
|
||||
// Build DW_TAG_pointer_type for `T*`:
|
||||
// .. DW_AT_type = <base_type>
|
||||
add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {});
|
||||
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
|
||||
pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset);
|
||||
}
|
||||
|
||||
// Build wrapper_die's DW_TAG_template_type_parameter:
|
||||
// .. DW_AT_name = "T"
|
||||
// .. DW_AT_type = <base_type>
|
||||
add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T"))
|
||||
});
|
||||
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
|
||||
pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset);
|
||||
}
|
||||
|
||||
// Build wrapper_die's DW_TAG_member for `__ptr`:
|
||||
// .. DW_AT_name = "__ptr"
|
||||
// .. DW_AT_type = <wp_die>
|
||||
// .. DW_AT_location = 0
|
||||
add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(wp_die_id),
|
||||
gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0)
|
||||
});
|
||||
|
||||
// Build wrapper_die's DW_TAG_subprogram for `ptr()`:
|
||||
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
|
||||
// .. DW_AT_name = "ptr"
|
||||
// .. DW_AT_type = <ptr_type>
|
||||
// .. DW_TAG_formal_parameter
|
||||
// .. .. DW_AT_type = <wrapper_ptr_type>
|
||||
// .. .. DW_AT_artificial = 1
|
||||
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
|
||||
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
|
||||
});
|
||||
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
|
||||
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
|
||||
});
|
||||
|
||||
// Build wrapper_die's DW_TAG_subprogram for `operator*`:
|
||||
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
|
||||
// .. DW_AT_name = "operator*"
|
||||
// .. DW_AT_type = <ref_type>
|
||||
// .. DW_TAG_formal_parameter
|
||||
// .. .. DW_AT_type = <wrapper_ptr_type>
|
||||
// .. .. DW_AT_artificial = 1
|
||||
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
|
||||
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(ref_type_id)
|
||||
});
|
||||
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
|
||||
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
|
||||
});
|
||||
|
||||
// Build wrapper_die's DW_TAG_subprogram for `operator->`:
|
||||
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
|
||||
// .. DW_AT_name = "operator->"
|
||||
// .. DW_AT_type = <ptr_type>
|
||||
// .. DW_TAG_formal_parameter
|
||||
// .. .. DW_AT_type = <wrapper_ptr_type>
|
||||
// .. .. DW_AT_artificial = 1
|
||||
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
|
||||
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
|
||||
});
|
||||
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
|
||||
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
|
||||
});
|
||||
|
||||
Ok(wrapper_die_id)
|
||||
}
|
||||
|
||||
pub(crate) fn clone_unit<'a, R>(
|
||||
dwarf: &gimli::Dwarf<R>,
|
||||
unit: Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
funcs: &'a CompiledFunctions,
|
||||
memory_offset: &ModuleMemoryOffset,
|
||||
out_encoding: gimli::Encoding,
|
||||
out_units: &mut write::UnitTable,
|
||||
out_strings: &mut write::StringTable,
|
||||
translated: &mut HashSet<DefinedFuncIndex>,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut die_ref_map = UnitRefsMap::new();
|
||||
let mut pending_die_refs = PendingUnitRefs::new();
|
||||
let mut pending_di_refs = PendingDebugInfoRefs::new();
|
||||
let mut stack = Vec::new();
|
||||
|
||||
// Iterate over all of this compilation unit's entries.
|
||||
let mut entries = unit.entries();
|
||||
let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
|
||||
if let Some((depth_delta, entry)) = entries.next_dfs()? {
|
||||
assert_eq!(depth_delta, 0);
|
||||
let (out_line_program, debug_line_offset, file_map, file_index_base) =
|
||||
clone_line_program(
|
||||
&unit,
|
||||
entry,
|
||||
addr_tr,
|
||||
out_encoding,
|
||||
context.debug_str,
|
||||
context.debug_str_offsets,
|
||||
context.debug_line_str,
|
||||
context.debug_line,
|
||||
out_strings,
|
||||
)?;
|
||||
|
||||
if entry.tag() == gimli::DW_TAG_compile_unit {
|
||||
let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
|
||||
let comp_unit = out_units.get_mut(unit_id);
|
||||
|
||||
let root_id = comp_unit.root();
|
||||
die_ref_map.insert(entry.offset(), root_id);
|
||||
|
||||
let cu_low_pc = if let Some(AttributeValue::Addr(addr)) =
|
||||
entry.attr_value(gimli::DW_AT_low_pc)?
|
||||
{
|
||||
addr
|
||||
} else if let Some(AttributeValue::DebugAddrIndex(i)) =
|
||||
entry.attr_value(gimli::DW_AT_low_pc)?
|
||||
{
|
||||
context.debug_addr.get_address(4, unit.addr_base, i)?
|
||||
} else {
|
||||
// FIXME? return Err(TransformError("No low_pc for unit header").into());
|
||||
0
|
||||
};
|
||||
|
||||
clone_die_attributes(
|
||||
dwarf,
|
||||
&unit,
|
||||
entry,
|
||||
context,
|
||||
addr_tr,
|
||||
None,
|
||||
comp_unit,
|
||||
root_id,
|
||||
None,
|
||||
None,
|
||||
cu_low_pc,
|
||||
out_strings,
|
||||
&mut pending_die_refs,
|
||||
&mut pending_di_refs,
|
||||
FileAttributeContext::Root(Some(debug_line_offset)),
|
||||
isa,
|
||||
)?;
|
||||
|
||||
let (wp_die_id, vmctx_die_id) =
|
||||
add_internal_types(comp_unit, root_id, out_strings, memory_offset);
|
||||
|
||||
stack.push(root_id);
|
||||
(
|
||||
comp_unit,
|
||||
unit_id,
|
||||
file_map,
|
||||
file_index_base,
|
||||
cu_low_pc,
|
||||
wp_die_id,
|
||||
vmctx_die_id,
|
||||
)
|
||||
} else {
|
||||
return Err(TransformError("Unexpected unit header").into());
|
||||
}
|
||||
} else {
|
||||
return Ok(None); // empty
|
||||
};
|
||||
let mut skip_at_depth = None;
|
||||
let mut current_frame_base = InheritedAttr::new();
|
||||
let mut current_value_range = InheritedAttr::new();
|
||||
let mut current_scope_ranges = InheritedAttr::new();
|
||||
while let Some((depth_delta, entry)) = entries.next_dfs()? {
|
||||
let depth_delta = if let Some((depth, cached)) = skip_at_depth {
|
||||
let new_depth = depth + depth_delta;
|
||||
if new_depth > 0 {
|
||||
skip_at_depth = Some((new_depth, cached));
|
||||
continue;
|
||||
}
|
||||
skip_at_depth = None;
|
||||
new_depth + cached
|
||||
} else {
|
||||
depth_delta
|
||||
};
|
||||
|
||||
if !context
|
||||
.reachable
|
||||
.contains(&entry.offset().to_unit_section_offset(&unit))
|
||||
{
|
||||
// entry is not reachable: discarding all its info.
|
||||
skip_at_depth = Some((0, depth_delta));
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
|
||||
current_frame_base.update(new_stack_len);
|
||||
current_scope_ranges.update(new_stack_len);
|
||||
current_value_range.update(new_stack_len);
|
||||
let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
|
||||
let range_builder = RangeInfoBuilder::from_subprogram_die(
|
||||
dwarf, &unit, entry, context, addr_tr, cu_low_pc,
|
||||
)?;
|
||||
if let RangeInfoBuilder::Function(func_index) = range_builder {
|
||||
if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, func_index)
|
||||
{
|
||||
current_value_range.push(new_stack_len, frame_info);
|
||||
}
|
||||
translated.insert(func_index);
|
||||
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
|
||||
Some(range_builder)
|
||||
} else {
|
||||
// FIXME current_scope_ranges.push()
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
|
||||
let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
|
||||
if high_pc.is_some() || ranges.is_some() {
|
||||
let range_builder =
|
||||
RangeInfoBuilder::from(dwarf, &unit, entry, context, cu_low_pc)?;
|
||||
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
|
||||
Some(range_builder)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if depth_delta <= 0 {
|
||||
for _ in depth_delta..1 {
|
||||
stack.pop();
|
||||
}
|
||||
} else {
|
||||
assert_eq!(depth_delta, 1);
|
||||
}
|
||||
|
||||
if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
|
||||
if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
|
||||
current_frame_base.push(new_stack_len, expr);
|
||||
}
|
||||
}
|
||||
|
||||
let parent = stack.last().unwrap();
|
||||
|
||||
if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
|
||||
{
|
||||
// Wrap pointer types.
|
||||
let pointer_kind = match entry.tag() {
|
||||
gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
|
||||
gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
|
||||
_ => panic!(),
|
||||
};
|
||||
let die_id = replace_pointer_type(
|
||||
*parent,
|
||||
pointer_kind,
|
||||
comp_unit,
|
||||
wp_die_id,
|
||||
entry,
|
||||
&unit,
|
||||
context,
|
||||
out_strings,
|
||||
&mut pending_die_refs,
|
||||
)?;
|
||||
stack.push(die_id);
|
||||
assert_eq!(stack.len(), new_stack_len);
|
||||
die_ref_map.insert(entry.offset(), die_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let die_id = comp_unit.add(*parent, entry.tag());
|
||||
|
||||
stack.push(die_id);
|
||||
assert_eq!(stack.len(), new_stack_len);
|
||||
die_ref_map.insert(entry.offset(), die_id);
|
||||
|
||||
clone_die_attributes(
|
||||
dwarf,
|
||||
&unit,
|
||||
entry,
|
||||
context,
|
||||
addr_tr,
|
||||
current_value_range.top(),
|
||||
&mut comp_unit,
|
||||
die_id,
|
||||
range_builder,
|
||||
current_scope_ranges.top(),
|
||||
cu_low_pc,
|
||||
out_strings,
|
||||
&mut pending_die_refs,
|
||||
&mut pending_di_refs,
|
||||
FileAttributeContext::Children {
|
||||
file_map: &file_map,
|
||||
file_index_base,
|
||||
frame_base: current_frame_base.top(),
|
||||
},
|
||||
isa,
|
||||
)?;
|
||||
|
||||
// Data in WebAssembly memory always uses little-endian byte order.
|
||||
// If the native architecture is big-endian, we need to mark all
|
||||
// base types used to refer to WebAssembly memory as little-endian
|
||||
// using the DW_AT_endianity attribute, so that the debugger will
|
||||
// be able to correctly access them.
|
||||
if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {
|
||||
let current_scope = comp_unit.get_mut(die_id);
|
||||
current_scope.set(
|
||||
gimli::DW_AT_endianity,
|
||||
write::AttributeValue::Endianity(gimli::DW_END_little),
|
||||
);
|
||||
}
|
||||
|
||||
if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
|
||||
append_vmctx_info(
|
||||
comp_unit,
|
||||
die_id,
|
||||
vmctx_die_id,
|
||||
addr_tr,
|
||||
current_value_range.top(),
|
||||
current_scope_ranges.top().context("range")?,
|
||||
out_strings,
|
||||
isa,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
die_ref_map.patch(pending_die_refs, comp_unit);
|
||||
Ok(Some((unit_id, die_ref_map, pending_di_refs)))
|
||||
}
|
||||
186
crates/cranelift/src/debug/transform/utils.rs
Normal file
186
crates/cranelift/src/debug/transform/utils.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::expression::{CompiledExpression, FunctionFrameInfo};
|
||||
use crate::CompiledFunctions;
|
||||
use anyhow::Error;
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use gimli::write;
|
||||
use wasmtime_environ::{DefinedFuncIndex, ModuleMemoryOffset};
|
||||
|
||||
/// Adds internal Wasm utility types DIEs such as WebAssemblyPtr and
|
||||
/// WasmtimeVMContext.
|
||||
///
|
||||
/// For unwrapping Wasm pointer, the WasmtimeVMContext has the `set()` method
|
||||
/// that allows to control current Wasm memory to inspect.
|
||||
/// Notice that "set_vmctx_memory" is an external/builtin subprogram that
|
||||
/// is not part of Wasm code.
|
||||
pub(crate) fn add_internal_types(
|
||||
comp_unit: &mut write::Unit,
|
||||
root_id: write::UnitEntryId,
|
||||
out_strings: &mut write::StringTable,
|
||||
memory_offset: &ModuleMemoryOffset,
|
||||
) -> (write::UnitEntryId, write::UnitEntryId) {
|
||||
const WASM_PTR_LEN: u8 = 4;
|
||||
|
||||
macro_rules! add_tag {
|
||||
($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
|
||||
let $die_id = comp_unit.add($parent_id, $tag);
|
||||
let $die = comp_unit.get_mut($die_id);
|
||||
$( $die.set($a, $v); )*
|
||||
};
|
||||
}
|
||||
|
||||
// Build DW_TAG_base_type for generic `WebAssemblyPtr`.
|
||||
// .. DW_AT_name = "WebAssemblyPtr"
|
||||
// .. DW_AT_byte_size = 4
|
||||
// .. DW_AT_encoding = DW_ATE_unsigned
|
||||
// let wp_die_id = comp_unit.add(root_id, gimli::DW_TAG_base_type);
|
||||
// let wp_die = comp_unit.get_mut(wp_die_id);
|
||||
add_tag!(root_id, gimli::DW_TAG_base_type => wp_die as wp_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("WebAssemblyPtr")),
|
||||
gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN),
|
||||
gimli::DW_AT_encoding = write::AttributeValue::Encoding(gimli::DW_ATE_unsigned)
|
||||
});
|
||||
|
||||
// Build DW_TAG_base_type for Wasm byte:
|
||||
// .. DW_AT_name = u8
|
||||
// .. DW_AT_encoding = DW_ATE_unsigned
|
||||
// .. DW_AT_byte_size = 1
|
||||
add_tag!(root_id, gimli::DW_TAG_base_type => memory_byte_die as memory_byte_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("u8")),
|
||||
gimli::DW_AT_encoding = write::AttributeValue::Encoding(gimli::DW_ATE_unsigned),
|
||||
gimli::DW_AT_byte_size = write::AttributeValue::Data1(1)
|
||||
});
|
||||
|
||||
// Build DW_TAG_pointer_type that references Wasm bytes:
|
||||
// .. DW_AT_name = "u8*"
|
||||
// .. DW_AT_type = <memory_byte_die>
|
||||
add_tag!(root_id, gimli::DW_TAG_pointer_type => memory_bytes_die as memory_bytes_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("u8*")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(memory_byte_die_id)
|
||||
});
|
||||
|
||||
// Create artificial VMContext type and its reference for convinience viewing
|
||||
// its fields (such as memory ref) in a debugger. Build DW_TAG_structure_type:
|
||||
// .. DW_AT_name = "WasmtimeVMContext"
|
||||
let vmctx_die_id = comp_unit.add(root_id, gimli::DW_TAG_structure_type);
|
||||
let vmctx_die = comp_unit.get_mut(vmctx_die_id);
|
||||
vmctx_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("WasmtimeVMContext")),
|
||||
);
|
||||
|
||||
// TODO multiple memories
|
||||
match *memory_offset {
|
||||
ModuleMemoryOffset::Defined(memory_offset) => {
|
||||
// The context has defined memory: extend the WasmtimeVMContext size
|
||||
// past the "memory" field.
|
||||
const MEMORY_FIELD_SIZE_PLUS_PADDING: u32 = 8;
|
||||
vmctx_die.set(
|
||||
gimli::DW_AT_byte_size,
|
||||
write::AttributeValue::Data4(memory_offset + MEMORY_FIELD_SIZE_PLUS_PADDING),
|
||||
);
|
||||
|
||||
// Define the "memory" field which is a direct pointer to allocated Wasm memory.
|
||||
// Build DW_TAG_member:
|
||||
// .. DW_AT_name = "memory"
|
||||
// .. DW_AT_type = <memory_bytes_die>
|
||||
// .. DW_AT_data_member_location = `memory_offset`
|
||||
add_tag!(vmctx_die_id, gimli::DW_TAG_member => m_die as m_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("memory")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(memory_bytes_die_id),
|
||||
gimli::DW_AT_data_member_location = write::AttributeValue::Udata(memory_offset as u64)
|
||||
});
|
||||
}
|
||||
ModuleMemoryOffset::Imported(_) => {
|
||||
// TODO implement convinience pointer to and additional types for VMMemoryImport.
|
||||
}
|
||||
ModuleMemoryOffset::None => (),
|
||||
}
|
||||
|
||||
// Build DW_TAG_pointer_type for `WasmtimeVMContext*`:
|
||||
// .. DW_AT_name = "WasmtimeVMContext*"
|
||||
// .. DW_AT_type = <vmctx_die>
|
||||
add_tag!(root_id, gimli::DW_TAG_pointer_type => vmctx_ptr_die as vmctx_ptr_die_id {
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("WasmtimeVMContext*")),
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(vmctx_die_id)
|
||||
});
|
||||
|
||||
// Build vmctx_die's DW_TAG_subprogram for `set` method:
|
||||
// .. DW_AT_linkage_name = "set_vmctx_memory"
|
||||
// .. DW_AT_name = "set"
|
||||
// .. DW_TAG_formal_parameter
|
||||
// .. .. DW_AT_type = <vmctx_ptr_die>
|
||||
// .. .. DW_AT_artificial = 1
|
||||
add_tag!(vmctx_die_id, gimli::DW_TAG_subprogram => vmctx_set as vmctx_set_id {
|
||||
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("set_vmctx_memory")),
|
||||
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("set"))
|
||||
});
|
||||
add_tag!(vmctx_set_id, gimli::DW_TAG_formal_parameter => vmctx_set_this_param as vmctx_set_this_param_id {
|
||||
gimli::DW_AT_type = write::AttributeValue::UnitRef(vmctx_ptr_die_id),
|
||||
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
|
||||
});
|
||||
|
||||
(wp_die_id, vmctx_ptr_die_id)
|
||||
}
|
||||
|
||||
pub(crate) fn append_vmctx_info(
|
||||
comp_unit: &mut write::Unit,
|
||||
parent_id: write::UnitEntryId,
|
||||
vmctx_die_id: write::UnitEntryId,
|
||||
addr_tr: &AddressTransform,
|
||||
frame_info: Option<&FunctionFrameInfo>,
|
||||
scope_ranges: &[(u64, u64)],
|
||||
out_strings: &mut write::StringTable,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> Result<(), Error> {
|
||||
let loc = {
|
||||
let expr = CompiledExpression::vmctx();
|
||||
let locs = expr
|
||||
.build_with_locals(scope_ranges, addr_tr, frame_info, isa)
|
||||
.map(|i| {
|
||||
i.map(|(begin, length, data)| write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let list_id = comp_unit.locations.add(write::LocationList(locs));
|
||||
write::AttributeValue::LocationListRef(list_id)
|
||||
};
|
||||
|
||||
let var_die_id = comp_unit.add(parent_id, gimli::DW_TAG_variable);
|
||||
let var_die = comp_unit.get_mut(var_die_id);
|
||||
var_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("__vmctx")),
|
||||
);
|
||||
var_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::UnitRef(vmctx_die_id),
|
||||
);
|
||||
var_die.set(gimli::DW_AT_location, loc);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_function_frame_info<'a, 'b, 'c>(
|
||||
memory_offset: &ModuleMemoryOffset,
|
||||
funcs: &'b CompiledFunctions,
|
||||
func_index: DefinedFuncIndex,
|
||||
) -> Option<FunctionFrameInfo<'a>>
|
||||
where
|
||||
'b: 'a,
|
||||
'c: 'a,
|
||||
{
|
||||
if let Some(func) = funcs.get(func_index) {
|
||||
let frame_info = FunctionFrameInfo {
|
||||
value_ranges: &func.value_labels_ranges,
|
||||
memory_offset: memory_offset.clone(),
|
||||
stack_slots: &func.stack_slots,
|
||||
};
|
||||
Some(frame_info)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user