mirror of
https://github.com/fluencelabs/wasmer
synced 2025-04-25 10:22:19 +00:00
Add debug prototype 2 (fork of wasmtime-debug)
This commit is contained in:
parent
589a99452c
commit
800b2a42cc
135
Cargo.lock
generated
135
Cargo.lock
generated
@ -18,6 +18,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
@ -228,8 +234,8 @@ dependencies = [
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
"log",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
"smallvec 1.1.0",
|
||||
"target-lexicon 0.9.0",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@ -263,7 +269,7 @@ checksum = "21398a0bc6ba389ea86964ac4a495426dd61080f2ddd306184777a8560fe9976"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"raw-cpuid",
|
||||
"target-lexicon",
|
||||
"target-lexicon 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -450,6 +456,28 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "faerie"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74b9ed6159e4a6212c61d9c6a86bee01876b192a64accecf58d5b5ae3b667b52"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"goblin 0.1.3",
|
||||
"indexmap",
|
||||
"log",
|
||||
"scroll 0.10.1",
|
||||
"string-interner",
|
||||
"target-lexicon 0.10.0",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.5.9"
|
||||
@ -514,6 +542,20 @@ dependencies = [
|
||||
"syn 1.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"byteorder",
|
||||
"fallible-iterator",
|
||||
"indexmap",
|
||||
"smallvec 1.1.0",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.2.11"
|
||||
@ -534,7 +576,18 @@ checksum = "e3fa261d919c1ae9d1e4533c4a2f99e10938603c4208d56c05bec7a872b661b0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"plain",
|
||||
"scroll",
|
||||
"scroll 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "goblin"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"plain",
|
||||
"scroll 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -870,7 +923,7 @@ dependencies = [
|
||||
"cloudabi",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"smallvec 1.1.0",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@ -1255,7 +1308,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"scroll_derive",
|
||||
"scroll_derive 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1"
|
||||
dependencies = [
|
||||
"scroll_derive 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1269,6 +1331,17 @@ dependencies = [
|
||||
"syn 0.15.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll_derive"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6",
|
||||
"quote 1.0.2",
|
||||
"syn 1.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.32.2"
|
||||
@ -1358,6 +1431,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
|
||||
dependencies = [
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.1.0"
|
||||
@ -1370,6 +1452,15 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||
|
||||
[[package]]
|
||||
name = "string-interner"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd710eadff449a1531351b0e43eb81ea404336fa2f56c777427ab0e32a4cf183"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
@ -1439,6 +1530,12 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.1.0"
|
||||
@ -1643,6 +1740,7 @@ dependencies = [
|
||||
"typetag",
|
||||
"wabt",
|
||||
"wasmer-clif-backend",
|
||||
"wasmer-debug-writer",
|
||||
"wasmer-dev-utils",
|
||||
"wasmer-emscripten",
|
||||
"wasmer-emscripten-tests",
|
||||
@ -1673,7 +1771,7 @@ dependencies = [
|
||||
"serde-bench",
|
||||
"serde_bytes",
|
||||
"serde_derive",
|
||||
"target-lexicon",
|
||||
"target-lexicon 0.9.0",
|
||||
"wasmer-clif-fork-frontend",
|
||||
"wasmer-clif-fork-wasm",
|
||||
"wasmer-runtime-core",
|
||||
@ -1690,8 +1788,8 @@ checksum = "6d2e13201ef9ef527ad30a6bf1b08e3e024a40cf2731f393d80375dc88506207"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"log",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
"smallvec 1.1.0",
|
||||
"target-lexicon 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1708,6 +1806,17 @@ dependencies = [
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-debug-writer"
|
||||
version = "0.13.1"
|
||||
dependencies = [
|
||||
"faerie",
|
||||
"gimli",
|
||||
"target-lexicon 0.10.0",
|
||||
"wasmer-runtime-core",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-dev-utils"
|
||||
version = "0.13.1"
|
||||
@ -1756,7 +1865,7 @@ version = "0.13.1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cc",
|
||||
"goblin",
|
||||
"goblin 0.0.24",
|
||||
"inkwell",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
@ -1764,7 +1873,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc_version",
|
||||
"semver",
|
||||
"smallvec",
|
||||
"smallvec 0.6.13",
|
||||
"wabt",
|
||||
"wasmer-runtime-core",
|
||||
"wasmparser",
|
||||
@ -1851,7 +1960,7 @@ dependencies = [
|
||||
"serde-bench",
|
||||
"serde_bytes",
|
||||
"serde_derive",
|
||||
"smallvec",
|
||||
"smallvec 0.6.13",
|
||||
"wasmparser",
|
||||
"winapi",
|
||||
]
|
||||
@ -1880,7 +1989,7 @@ dependencies = [
|
||||
"nix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"smallvec",
|
||||
"smallvec 0.6.13",
|
||||
"wasmer-runtime-core",
|
||||
]
|
||||
|
||||
|
@ -41,6 +41,7 @@ wasmer-wasi-tests = { path = "lib/wasi-tests", optional = true }
|
||||
wasmer-middleware-common-tests = { path = "lib/middleware-common-tests", optional = true }
|
||||
wasmer-emscripten-tests = { path = "lib/emscripten-tests", optional = true }
|
||||
wasmer-wasi-experimental-io-devices = { path = "lib/wasi-experimental-io-devices", optional = true }
|
||||
wasmer-debug-writer = { path = "lib/debug-writer", optional = true }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
@ -64,6 +65,7 @@ members = [
|
||||
"lib/wasi-tests",
|
||||
"lib/emscripten-tests",
|
||||
"lib/middleware-common-tests",
|
||||
"lib/debug-writer",
|
||||
"examples/parallel",
|
||||
"examples/plugin-for-example",
|
||||
"examples/parallel-guest",
|
||||
@ -79,7 +81,7 @@ serde = { version = "1", features = ["derive"] } # used by the plugin example
|
||||
typetag = "0.1" # used by the plugin example
|
||||
|
||||
[features]
|
||||
default = ["fast-tests", "wasi", "backend-cranelift", "wabt"]
|
||||
default = ["fast-tests", "wasi", "backend-cranelift", "wabt", "wasmer-debug-writer"]
|
||||
"loader-kernel" = ["wasmer-kernel-loader"]
|
||||
debug = ["fern", "log/max_level_debug", "log/release_max_level_debug"]
|
||||
trace = ["fern", "log/max_level_trace", "log/release_max_level_trace"]
|
||||
|
@ -45,6 +45,7 @@ impl Module {
|
||||
runnable_module: Arc::new(Box::new(runnable_module)),
|
||||
cache_gen,
|
||||
info,
|
||||
debug_info: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
16
lib/debug-writer/Cargo.toml
Normal file
16
lib/debug-writer/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "wasmer-debug-writer"
|
||||
version = "0.13.1"
|
||||
authors = ["The Wasmer Engineering Team <engineering@wasmer.io>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/wasmerio/wasmer"
|
||||
publish = false
|
||||
description = "Library for writing debug information from Wasm"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
faerie = "0.14"
|
||||
gimli = "0.20"
|
||||
target-lexicon = "0.10"
|
||||
wasmer-runtime-core = { path = "../runtime-core", version = "0.13.1" }
|
||||
wasmparser = "0.45"
|
63
lib/debug-writer/README.md
Normal file
63
lib/debug-writer/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# Wasmer debug info writer
|
||||
|
||||
This crate deals with passing DWARF debug information along from
|
||||
compiled Wasm modules to the machine code that we generate.
|
||||
|
||||
This crate is effectively a derivative work of WasmTime's
|
||||
[`wasmtime-debug`](https://github.com/bytecodealliance/wasmtime/tree/master/crates/debug)
|
||||
crate. After beginning work on a clean reimplementation we realized
|
||||
that the WasmTime implementation is high quality and it didn't make
|
||||
sense for us to duplicate their hard work.
|
||||
|
||||
Additionally by keeping the code structure of `wasmer-debug-writer`
|
||||
similar to `wasmtime-debug`, we hope to upstream bug fixes and
|
||||
improvements to `wasmtime-debug`.
|
||||
|
||||
Copied files include the copyright notice as well, but as a catch all,
|
||||
this crate is a derivative work of WasmTime's `wasmtime-debug`
|
||||
|
||||
```
|
||||
Copyright 2020 WasmTime Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
||||
|
||||
The latest revision at the time of cloning is `3992b8669f9b9e185abe81e9998ce2ff4d40ff68`.
|
||||
|
||||
Changes to this crate are copyright of Wasmer inc. unless otherwise indicated
|
||||
and are licensed under the Wasmer project's license:
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Wasmer, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
250
lib/debug-writer/src/gc.rs
Normal file
250
lib/debug-writer/src/gc.rs
Normal file
@ -0,0 +1,250 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `39e57e3e9ac9c15bef45eb77a2544a7c0b76501a`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use crate::transform::AddressTransform;
|
||||
use gimli::constants;
|
||||
use gimli::read;
|
||||
use gimli::{Reader, UnitSectionOffset};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Dependencies {
|
||||
edges: HashMap<UnitSectionOffset, HashSet<UnitSectionOffset>>,
|
||||
roots: HashSet<UnitSectionOffset>,
|
||||
}
|
||||
|
||||
impl Dependencies {
|
||||
fn new() -> Dependencies {
|
||||
Dependencies {
|
||||
edges: HashMap::new(),
|
||||
roots: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, a: UnitSectionOffset, b: UnitSectionOffset) {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.edges.entry(a) {
|
||||
Entry::Occupied(mut o) => {
|
||||
o.get_mut().insert(b);
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(b);
|
||||
v.insert(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_root(&mut self, root: UnitSectionOffset) {
|
||||
self.roots.insert(root);
|
||||
}
|
||||
|
||||
pub fn get_reachable(&self) -> HashSet<UnitSectionOffset> {
|
||||
let mut reachable = self.roots.clone();
|
||||
let mut queue = Vec::new();
|
||||
for i in self.roots.iter() {
|
||||
if let Some(deps) = self.edges.get(i) {
|
||||
for j in deps {
|
||||
if reachable.contains(j) {
|
||||
continue;
|
||||
}
|
||||
reachable.insert(*j);
|
||||
queue.push(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(i) = queue.pop() {
|
||||
if let Some(deps) = self.edges.get(&i) {
|
||||
for j in deps {
|
||||
if reachable.contains(j) {
|
||||
continue;
|
||||
}
|
||||
reachable.insert(*j);
|
||||
queue.push(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
reachable
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_dependencies<R: Reader<Offset = usize>>(
|
||||
dwarf: &read::Dwarf<R>,
|
||||
at: &AddressTransform,
|
||||
) -> read::Result<Dependencies> {
|
||||
let mut deps = Dependencies::new();
|
||||
let mut units = dwarf.units();
|
||||
while let Some(unit) = units.next()? {
|
||||
build_unit_dependencies(unit, dwarf, at, &mut deps)?;
|
||||
}
|
||||
Ok(deps)
|
||||
}
|
||||
|
||||
fn build_unit_dependencies<R: Reader<Offset = usize>>(
|
||||
header: read::CompilationUnitHeader<R>,
|
||||
dwarf: &read::Dwarf<R>,
|
||||
at: &AddressTransform,
|
||||
deps: &mut Dependencies,
|
||||
) -> read::Result<()> {
|
||||
let unit = dwarf.unit(header)?;
|
||||
let mut tree = unit.entries_tree(None)?;
|
||||
let root = tree.root()?;
|
||||
build_die_dependencies(root, dwarf, &unit, at, deps)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_die_back_edge<R: Reader<Offset = usize>>(die: &read::DebuggingInformationEntry<R>) -> bool {
|
||||
match die.tag() {
|
||||
constants::DW_TAG_variable
|
||||
| constants::DW_TAG_constant
|
||||
| constants::DW_TAG_inlined_subroutine
|
||||
| constants::DW_TAG_lexical_block
|
||||
| constants::DW_TAG_label
|
||||
| constants::DW_TAG_with_stmt
|
||||
| constants::DW_TAG_try_block
|
||||
| constants::DW_TAG_catch_block
|
||||
| constants::DW_TAG_template_type_parameter
|
||||
| constants::DW_TAG_member
|
||||
| constants::DW_TAG_formal_parameter => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_valid_code_range<R: Reader<Offset = usize>>(
|
||||
die: &read::DebuggingInformationEntry<R>,
|
||||
dwarf: &read::Dwarf<R>,
|
||||
unit: &read::Unit<R>,
|
||||
at: &AddressTransform,
|
||||
) -> read::Result<bool> {
|
||||
match die.tag() {
|
||||
constants::DW_TAG_subprogram => {
|
||||
if let Some(ranges_attr) = die.attr_value(constants::DW_AT_ranges)? {
|
||||
let offset = match ranges_attr {
|
||||
read::AttributeValue::RangeListsRef(val) => val,
|
||||
read::AttributeValue::DebugRngListsIndex(index) => {
|
||||
dwarf.ranges_offset(unit, index)?
|
||||
}
|
||||
_ => return Ok(false),
|
||||
};
|
||||
let mut has_valid_base = if let Some(read::AttributeValue::Addr(low_pc)) =
|
||||
die.attr_value(constants::DW_AT_low_pc)?
|
||||
{
|
||||
Some(at.can_translate_address(low_pc))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut it = dwarf.ranges.raw_ranges(offset, unit.encoding())?;
|
||||
while let Some(range) = it.next()? {
|
||||
// If at least one of the range addresses can be converted,
|
||||
// declaring code range as valid.
|
||||
match range {
|
||||
read::RawRngListEntry::AddressOrOffsetPair { .. }
|
||||
if has_valid_base.is_some() =>
|
||||
{
|
||||
if has_valid_base.unwrap() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
read::RawRngListEntry::StartEnd { begin, .. }
|
||||
| read::RawRngListEntry::StartLength { begin, .. }
|
||||
| read::RawRngListEntry::AddressOrOffsetPair { begin, .. } => {
|
||||
if at.can_translate_address(begin) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
read::RawRngListEntry::StartxEndx { begin, .. }
|
||||
| read::RawRngListEntry::StartxLength { begin, .. } => {
|
||||
let addr = dwarf.address(unit, begin)?;
|
||||
if at.can_translate_address(addr) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
read::RawRngListEntry::BaseAddress { addr } => {
|
||||
has_valid_base = Some(at.can_translate_address(addr));
|
||||
}
|
||||
read::RawRngListEntry::BaseAddressx { addr } => {
|
||||
let addr = dwarf.address(unit, addr)?;
|
||||
has_valid_base = Some(at.can_translate_address(addr));
|
||||
}
|
||||
read::RawRngListEntry::OffsetPair { .. } => (),
|
||||
}
|
||||
}
|
||||
return Ok(false);
|
||||
} else if let Some(low_pc) = die.attr_value(constants::DW_AT_low_pc)? {
|
||||
if let read::AttributeValue::Addr(a) = low_pc {
|
||||
return Ok(at.can_translate_address(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn build_die_dependencies<R: Reader<Offset = usize>>(
|
||||
die: read::EntriesTreeNode<R>,
|
||||
dwarf: &read::Dwarf<R>,
|
||||
unit: &read::Unit<R>,
|
||||
at: &AddressTransform,
|
||||
deps: &mut Dependencies,
|
||||
) -> read::Result<()> {
|
||||
let entry = die.entry();
|
||||
let offset = entry.offset().to_unit_section_offset(unit);
|
||||
let mut attrs = entry.attrs();
|
||||
while let Some(attr) = attrs.next()? {
|
||||
build_attr_dependencies(&attr, offset, dwarf, unit, at, deps)?;
|
||||
}
|
||||
|
||||
let mut children = die.children();
|
||||
while let Some(child) = children.next()? {
|
||||
let child_entry = child.entry();
|
||||
let child_offset = child_entry.offset().to_unit_section_offset(unit);
|
||||
deps.add_edge(child_offset, offset);
|
||||
if has_die_back_edge(child_entry) {
|
||||
deps.add_edge(offset, child_offset);
|
||||
}
|
||||
if has_valid_code_range(child_entry, dwarf, unit, at)? {
|
||||
deps.add_root(child_offset);
|
||||
}
|
||||
build_die_dependencies(child, dwarf, unit, at, deps)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_attr_dependencies<R: Reader<Offset = usize>>(
|
||||
attr: &read::Attribute<R>,
|
||||
offset: UnitSectionOffset,
|
||||
_dwarf: &read::Dwarf<R>,
|
||||
unit: &read::Unit<R>,
|
||||
_at: &AddressTransform,
|
||||
deps: &mut Dependencies,
|
||||
) -> read::Result<()> {
|
||||
match attr.value() {
|
||||
read::AttributeValue::UnitRef(val) => {
|
||||
let ref_offset = val.to_unit_section_offset(unit);
|
||||
deps.add_edge(offset, ref_offset);
|
||||
}
|
||||
read::AttributeValue::DebugInfoRef(val) => {
|
||||
let ref_offset = UnitSectionOffset::DebugInfoOffset(val);
|
||||
deps.add_edge(offset, ref_offset);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
338
lib/debug-writer/src/lib.rs
Normal file
338
lib/debug-writer/src/lib.rs
Normal file
@ -0,0 +1,338 @@
|
||||
// TODO: add attribution to LLVM for data definitions and WasmTime for code structure
|
||||
use std::str::FromStr;
|
||||
use std::ptr;
|
||||
use std::ffi::c_void;
|
||||
|
||||
mod read_debug_info;
|
||||
mod write_debug_info;
|
||||
mod gc;
|
||||
mod transform;
|
||||
|
||||
pub use crate::read_debug_info::{read_debug_info, DebugInfoData, WasmFileInfo};
|
||||
pub use crate::write_debug_info::{emit_dwarf, ResolvedSymbol, SymbolResolver};
|
||||
use crate::transform::WasmTypesDieRefs;
|
||||
|
||||
use target_lexicon::{Triple, Architecture, Vendor, OperatingSystem, Environment, BinaryFormat};
|
||||
use gimli::write::{self, DwarfUnit, Sections, Address, RangeList, EndianVec, AttributeValue, Range};
|
||||
|
||||
use wasmer_runtime_core::{module::ModuleInfo, state::CodeVersion};
|
||||
|
||||
/// Triple of x86_64 GNU/Linux
|
||||
const X86_64_GNU_LINUX: Triple = Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
vendor: Vendor::Unknown,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
environment: Environment::Gnu,
|
||||
binary_format: BinaryFormat::Elf,
|
||||
};
|
||||
|
||||
/// Triple of x86_64 OSX
|
||||
const X86_64_OSX: Triple = Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
vendor: Vendor::Apple,
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
environment: Environment::Unknown,
|
||||
binary_format: BinaryFormat::Macho,
|
||||
};
|
||||
|
||||
/// Triple of x86_64 Windows
|
||||
const X86_64_WINDOWS: Triple = Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
vendor: Vendor::Pc,
|
||||
operating_system: OperatingSystem::Windows,
|
||||
environment: Environment::Msvc,
|
||||
binary_format: BinaryFormat::Coff,
|
||||
};
|
||||
|
||||
// this code also from WasmTime
|
||||
// TODO: attribute
|
||||
struct ImageRelocResolver<'a> {
|
||||
func_offsets: &'a Vec<u64>,
|
||||
}
|
||||
|
||||
// this code also from WasmTime
|
||||
// TODO: attribute
|
||||
impl<'a> SymbolResolver for ImageRelocResolver<'a> {
|
||||
fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
|
||||
let func_start = self.func_offsets[symbol];
|
||||
ResolvedSymbol::PhysicalAddress(func_start + addend as u64)
|
||||
}
|
||||
}
|
||||
|
||||
// the structure of this function and some of its details come from WasmTime
|
||||
// TODO: attribute
|
||||
pub fn generate_dwarf(module_info: &ModuleInfo, debug_info_data: &DebugInfoData, code_version: &CodeVersion, platform: Triple) -> Result<Vec<u8>, String> {
|
||||
let func_offsets = unimplemented!();
|
||||
let resolver = ImageRelocResolver { func_offsets };
|
||||
// copied from https://docs.rs/gimli/0.20.0/gimli/write/index.html ; TODO: review these values
|
||||
let processed_dwarf = reprocess_dwarf(module_info, debug_info_data, code_version, platform).ok_or_else(|| "Failed to reprocess Wasm's dwarf".to_string())?;
|
||||
let encoding = gimli::Encoding {
|
||||
format: gimli::Format::Dwarf32,
|
||||
version: 3,
|
||||
address_size: 8,
|
||||
};
|
||||
let mut dwarf = DwarfUnit::new(encoding);
|
||||
// TODO: figure out what range is (from example)
|
||||
let range_list = RangeList(vec![Range::StartLength {
|
||||
begin: Address::Constant(0x100),
|
||||
length: 42,
|
||||
}]);
|
||||
let range_list_id = dwarf.unit.ranges.add(range_list);
|
||||
let root = dwarf.unit.root();
|
||||
dwarf.unit.get_mut(root).set(
|
||||
gimli::DW_AT_ranges,
|
||||
AttributeValue::RangeListRef(range_list_id),
|
||||
);
|
||||
let mut string_table = write::StringTable::default();
|
||||
let mut line_string_table = write::LineStringTable::default();
|
||||
|
||||
let mut obj = faerie::Artifact::new(platform, String::from("module"));
|
||||
|
||||
let mut sections = Sections::new(EndianVec::new(gimli::LittleEndian));
|
||||
// Finally, write the DWARF data to the sections.
|
||||
dwarf.write(&mut sections).map_err(|e| e.to_string())?;
|
||||
emit_dwarf(&mut obj, dwarf, &resolver);
|
||||
sections.for_each(|id, data| {
|
||||
// Here you can add the data to the output object file.
|
||||
Ok(())
|
||||
});
|
||||
|
||||
obj.emit_as(BinaryFormat::Elf).expect("TODO");
|
||||
// We want to populate DwarfUnit::line_str_table with WAT probably
|
||||
// and set up the string table with things like function signatures in WAT, function names, etc
|
||||
|
||||
// NOTES from DWARF spec:
|
||||
// http://dwarfstd.org/doc/DWARF5.pdf
|
||||
// - `DIE`s form the core of dwarf and live in .debug_info
|
||||
// - the tags can get fairly specific, it looks like we'll just need a mapping
|
||||
// from object code to a bunch of tags and ranges? created with the Wasm
|
||||
// data for extra info about types, etc.
|
||||
// - debug info can live in a separate object file (that's what we'll do here)
|
||||
// - attribute types are unique per DIE (lots of info here (like is tail call,
|
||||
// return addr, etc.)
|
||||
// - DW_AT_language: WebAssembly :bonjour:
|
||||
// - `DW_AT_linkage_name` function namespaces? (later described as the raw, mangled name)
|
||||
// `DW_AT_name` function name?
|
||||
// - `DW_AT_location` where in the code it is
|
||||
// - `DW_AT_main_subprogram` where to start from
|
||||
// - `DW_AT_producer`: wasmer
|
||||
// - `DW_AT_recursive` -- is this mandatory? what is it used for? TODO: find out
|
||||
// - `DW_AT_signature` -- can we use wasm type signature info here? TODO:
|
||||
// - `DIE`s form a graph/tree though a tree-like graph when it is a graph, docs say
|
||||
// this is how structs and relationship of code blocks is represented.
|
||||
// - when serialized the tree is in post-fix order (probably not important for our
|
||||
// purposes but mildly interesting)
|
||||
// - we'll need pointer sizer and platform information
|
||||
// - dwarf executes a typed stack-machine to compute the locations of things
|
||||
// - lots of neat info about the dwarf stack machine skipping for now because I
|
||||
// think gimli exposes a higher-level interface (if not, I'll add notes here
|
||||
// or further down about it)
|
||||
// - can use dwarf expressions/dynamically computing things to handle things like
|
||||
// a tiering JIT?
|
||||
// - location lists are needed for things that aren't lexically scoped, otherwise
|
||||
// single location descriptions (dwarf expressions) are sufficient
|
||||
// - I wonder what this means in the context of spilling registers... do we have
|
||||
// to create dwarf expressions that can handle that?
|
||||
// - `DW_AT_artificial` is used to tag `DIE` that didn't come directly from the code
|
||||
// - `DW_AT_declaration` for function/etc declarations at the top of the wasm module,
|
||||
// see section 2.13.2 for how to connect the definiton and the declaration
|
||||
// - `DW_AT_decl_line`, `DW_AT_decl_column` refer to the exact location in the source
|
||||
// file, so presumably we include the entire source file in one of the sections?
|
||||
// or perhaps that's purely for human consumption.
|
||||
// - `DW_AT_ranges` is for non-contiguous ranges of address and,
|
||||
// `DW_AT_low_pc` and `DW_AT_high_pc` are good for continuous
|
||||
// `DW_AT_low_pc` alone can work for a single address, but we can probably not
|
||||
// worry about that for now. These attribtues associate machine code with the DIE
|
||||
// -
|
||||
|
||||
match platform {
|
||||
X86_64_GNU_LINUX => unimplemented!("in progress"),
|
||||
X86_64_OSX => unimplemented!("in progress"),
|
||||
X86_64_WINDOWS => unimplemented!("in progress"),
|
||||
_ => return Err(format!("Debug output for the platform {} is not yet supported", platform)),
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
// converts existing dwarf into a usable form with metadata from the JIT
|
||||
fn reprocess_dwarf(module_info: &ModuleInfo, debug_info_data: &DebugInfoData, code_version: &CodeVersion, platform: Triple) -> Option<write::Dwarf> {
|
||||
None
|
||||
}
|
||||
|
||||
// black box, needs some kind of input, some kind of processing
|
||||
// and returns a bunch of bytes we can give to GDB
|
||||
//
|
||||
// where is this documented?
|
||||
// we need to pass in target triple, isa config, memories/pointers to memories, ranges of where things are,
|
||||
// and info like function names
|
||||
pub fn generate_debug_sections_image() -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
|
||||
// do it
|
||||
|
||||
// this code copied from WasmTime, TODO: give attribution
|
||||
|
||||
|
||||
// The `emit_wasm_types` function is a derative work of code in WasmTime:
|
||||
// TODO: update attributions file and/or do clean reimplementation of this logic
|
||||
//
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
fn emit_wasm_types(unit: &mut write::Unit, root_id: write::UnitEntryId, string_table: &mut write::StringTable) -> WasmTypesDieRefs {
|
||||
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(string_table.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 vmctx_id = {
|
||||
// TODO: get memory_offset
|
||||
let memory_offset = 0;
|
||||
let vmctx_die_id = unit.add(root_id, gimli::DW_TAG_structure_type);
|
||||
let vmctx_die = unit.get_mut(vmctx_die_id);
|
||||
vmctx_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(string_table.add("WasmerVMContext")),
|
||||
);
|
||||
vmctx_die.set(
|
||||
gimli::DW_AT_byte_size,
|
||||
write::AttributeValue::Data4(memory_offset as u32 + 8),
|
||||
);
|
||||
let vmctx_ptr_id = unit.add(root_id, gimli::DW_TAG_pointer_type);
|
||||
let vmctx_ptr_die = unit.get_mut(vmctx_ptr_id);
|
||||
vmctx_ptr_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(string_table.add("WasmerVMContext*")),
|
||||
);
|
||||
vmctx_ptr_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(vmctx_die_id),
|
||||
);
|
||||
|
||||
vmctx_ptr_id
|
||||
};
|
||||
|
||||
let i32_id = def_type!("i32", 4, gimli::DW_ATE_signed);
|
||||
let i64_id = def_type!("i64", 8, gimli::DW_ATE_signed);
|
||||
let i128_id = def_type!("i128", 16, gimli::DW_ATE_signed);
|
||||
let f32_id = def_type!("f32", 4, gimli::DW_ATE_float);
|
||||
let f64_id = def_type!("f64", 8, gimli::DW_ATE_float);
|
||||
|
||||
WasmTypesDieRefs {
|
||||
vmctx: vmctx_id,
|
||||
i32: i32_id,
|
||||
i64: i64_id,
|
||||
i128: i128_id,
|
||||
f32: f32_id,
|
||||
f64: f64_id,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// LLDB hook magic:
|
||||
// see lldb/packages/Python/lldbsuite/test/functionalities/jitloader_gdb in
|
||||
// llvm repo for example
|
||||
//
|
||||
// see also https://sourceware.org/gdb/current/onlinedocs/gdb.html#JIT-Interface
|
||||
|
||||
#[inline(never)]
|
||||
pub extern "C" fn __jit_debug_register_code() {
|
||||
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug)]
|
||||
#[repr(u32)]
|
||||
pub enum JITAction { JIT_NOACTION = 0, JIT_REGISTER_FN = 1, JIT_UNREGISTER_FN = 2 }
|
||||
|
||||
#[no_mangle]
|
||||
#[repr(C)]
|
||||
pub struct JITCodeEntry {
|
||||
next: *mut JITCodeEntry,
|
||||
prev: *mut JITCodeEntry,
|
||||
// TODO: use CStr here?
|
||||
symfile_addr: *const u8,
|
||||
symfile_size: u64,
|
||||
}
|
||||
|
||||
impl Default for JITCodeEntry {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
next: ptr::null_mut(),
|
||||
prev: ptr::null_mut(),
|
||||
symfile_addr: ptr::null(),
|
||||
symfile_size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[repr(C)]
|
||||
pub struct JitDebugDescriptor {
|
||||
version: u32,
|
||||
action_flag: u32,
|
||||
relevant_entry: *mut JITCodeEntry,
|
||||
first_entry: *mut JITCodeEntry,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static mut __jit_debug_descriptor: JitDebugDescriptor = JitDebugDescriptor {
|
||||
version: 1,
|
||||
action_flag: JITAction::JIT_NOACTION as _,
|
||||
relevant_entry: ptr::null_mut(),
|
||||
first_entry: ptr::null_mut(),
|
||||
};
|
||||
|
||||
/// Prepend an item to the front of the `__jit_debug_descriptor` entry list
|
||||
///
|
||||
/// # Safety
|
||||
/// - Pointer to [`JITCodeEntry`] should point to a valid entry and stay alive
|
||||
/// for the 'static lifetime
|
||||
unsafe fn push_front(jce: *mut JITCodeEntry) {
|
||||
if __jit_debug_descriptor.first_entry.is_null() {
|
||||
__jit_debug_descriptor.first_entry = jce;
|
||||
} else {
|
||||
let old_first = __jit_debug_descriptor.first_entry;
|
||||
debug_assert!((*old_first).prev.is_null());
|
||||
(*jce).next = old_first;
|
||||
(*old_first).prev = jce;
|
||||
__jit_debug_descriptor.first_entry = jce;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_new_jit_code_entry(bytes: &'static [u8], action: JITAction) -> *mut JITCodeEntry {
|
||||
let entry: *mut JITCodeEntry = Box::into_raw(Box::new(JITCodeEntry {
|
||||
symfile_addr: bytes.as_ptr(),
|
||||
symfile_size: bytes.len() as _,
|
||||
..JITCodeEntry::default()
|
||||
}));
|
||||
|
||||
unsafe {
|
||||
push_front(entry);
|
||||
__jit_debug_descriptor.relevant_entry = entry;
|
||||
__jit_debug_descriptor.action_flag = action as u32;
|
||||
}
|
||||
|
||||
entry
|
||||
}
|
265
lib/debug-writer/src/read_debug_info.rs
Normal file
265
lib/debug-writer/src/read_debug_info.rs
Normal file
@ -0,0 +1,265 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project. It reads DWARF info from a Wasm module.
|
||||
// It was copied at revision `39e57e3e9ac9c15bef45eb77a2544a7c0b76501a`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
|
||||
use gimli::{
|
||||
DebugAbbrev, DebugAddr, DebugInfo, DebugLine, DebugLineStr, DebugLoc, DebugLocLists,
|
||||
DebugRanges, DebugRngLists, DebugStr, DebugStrOffsets, DebugTypes, EndianSlice, LittleEndian,
|
||||
LocationLists, RangeLists,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use wasmparser::{self, ModuleReader, SectionCode};
|
||||
|
||||
trait Reader: gimli::Reader<Offset = usize, Endian = LittleEndian> {}
|
||||
|
||||
impl<'input> Reader for gimli::EndianSlice<'input, LittleEndian> {}
|
||||
|
||||
pub use wasmparser::Type as WasmType;
|
||||
|
||||
pub type Dwarf<'input> = gimli::Dwarf<gimli::EndianSlice<'input, LittleEndian>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionMetadata {
|
||||
pub params: Box<[WasmType]>,
|
||||
pub locals: Box<[(u32, WasmType)]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WasmFileInfo {
|
||||
pub path: Option<PathBuf>,
|
||||
pub code_section_offset: u64,
|
||||
pub funcs: Box<[FunctionMetadata]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NameSection {
|
||||
pub module_name: Option<String>,
|
||||
pub func_names: HashMap<u32, String>,
|
||||
pub locals_names: HashMap<u32, HashMap<u32, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugInfoData<'a> {
|
||||
pub dwarf: Dwarf<'a>,
|
||||
pub name_section: Option<NameSection>,
|
||||
pub wasm_file: WasmFileInfo,
|
||||
}
|
||||
|
||||
fn convert_sections<'a>(sections: HashMap<&str, &'a [u8]>) -> Dwarf<'a> {
|
||||
const EMPTY_SECTION: &[u8] = &[];
|
||||
|
||||
let endian = LittleEndian;
|
||||
let debug_str = DebugStr::new(sections.get(".debug_str").unwrap_or(&EMPTY_SECTION), endian);
|
||||
let debug_abbrev = DebugAbbrev::new(
|
||||
sections.get(".debug_abbrev").unwrap_or(&EMPTY_SECTION),
|
||||
endian,
|
||||
);
|
||||
let debug_info = DebugInfo::new(
|
||||
sections.get(".debug_info").unwrap_or(&EMPTY_SECTION),
|
||||
endian,
|
||||
);
|
||||
let debug_line = DebugLine::new(
|
||||
sections.get(".debug_line").unwrap_or(&EMPTY_SECTION),
|
||||
endian,
|
||||
);
|
||||
|
||||
if sections.contains_key(".debug_addr") {
|
||||
panic!("Unexpected .debug_addr");
|
||||
}
|
||||
|
||||
let debug_addr = DebugAddr::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
if sections.contains_key(".debug_line_str") {
|
||||
panic!("Unexpected .debug_line_str");
|
||||
}
|
||||
|
||||
let debug_line_str = DebugLineStr::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
let debug_str_sup = DebugStr::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
if sections.contains_key(".debug_rnglists") {
|
||||
panic!("Unexpected .debug_rnglists");
|
||||
}
|
||||
|
||||
let debug_ranges = match sections.get(".debug_ranges") {
|
||||
Some(section) => DebugRanges::new(section, endian),
|
||||
None => DebugRanges::new(EMPTY_SECTION, endian),
|
||||
};
|
||||
let debug_rnglists = DebugRngLists::new(EMPTY_SECTION, endian);
|
||||
let ranges = RangeLists::new(debug_ranges, debug_rnglists);
|
||||
|
||||
if sections.contains_key(".debug_loclists") {
|
||||
panic!("Unexpected .debug_loclists");
|
||||
}
|
||||
|
||||
let debug_loc = match sections.get(".debug_loc") {
|
||||
Some(section) => DebugLoc::new(section, endian),
|
||||
None => DebugLoc::new(EMPTY_SECTION, endian),
|
||||
};
|
||||
let debug_loclists = DebugLocLists::new(EMPTY_SECTION, endian);
|
||||
let locations = LocationLists::new(debug_loc, debug_loclists);
|
||||
|
||||
if sections.contains_key(".debug_str_offsets") {
|
||||
panic!("Unexpected .debug_str_offsets");
|
||||
}
|
||||
|
||||
let debug_str_offsets = DebugStrOffsets::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
if sections.contains_key(".debug_types") {
|
||||
panic!("Unexpected .debug_types");
|
||||
}
|
||||
|
||||
let debug_types = DebugTypes::from(EndianSlice::new(EMPTY_SECTION, endian));
|
||||
|
||||
Dwarf {
|
||||
debug_abbrev,
|
||||
debug_addr,
|
||||
debug_info,
|
||||
debug_line,
|
||||
debug_line_str,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_str_sup,
|
||||
debug_types,
|
||||
locations,
|
||||
ranges,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_name_section(reader: wasmparser::NameSectionReader) -> wasmparser::Result<NameSection> {
|
||||
let mut module_name = None;
|
||||
let mut func_names = HashMap::new();
|
||||
let mut locals_names = HashMap::new();
|
||||
for i in reader.into_iter() {
|
||||
match i? {
|
||||
wasmparser::Name::Module(m) => {
|
||||
module_name = Some(String::from(m.get_name()?));
|
||||
}
|
||||
wasmparser::Name::Function(f) => {
|
||||
let mut reader = f.get_map()?;
|
||||
while let Ok(naming) = reader.read() {
|
||||
func_names.insert(naming.index, String::from(naming.name));
|
||||
}
|
||||
}
|
||||
wasmparser::Name::Local(l) => {
|
||||
let mut reader = l.get_function_local_reader()?;
|
||||
while let Ok(f) = reader.read() {
|
||||
let mut names = HashMap::new();
|
||||
let mut reader = f.get_map()?;
|
||||
while let Ok(naming) = reader.read() {
|
||||
names.insert(naming.index, String::from(naming.name));
|
||||
}
|
||||
locals_names.insert(f.func_index, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = NameSection {
|
||||
module_name,
|
||||
func_names,
|
||||
locals_names,
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn read_debug_info(data: &[u8]) -> DebugInfoData {
|
||||
let mut reader = ModuleReader::new(data).expect("reader");
|
||||
let mut sections = HashMap::new();
|
||||
let mut name_section = None;
|
||||
let mut code_section_offset = 0;
|
||||
|
||||
let mut signatures_params: Vec<Box<[WasmType]>> = Vec::new();
|
||||
let mut func_params_refs: Vec<usize> = Vec::new();
|
||||
let mut func_locals: Vec<Box<[(u32, WasmType)]>> = Vec::new();
|
||||
|
||||
while !reader.eof() {
|
||||
let section = reader.read().expect("section");
|
||||
match section.code {
|
||||
SectionCode::Custom { name, .. } => {
|
||||
if name.starts_with(".debug_") {
|
||||
let mut reader = section.get_binary_reader();
|
||||
let len = reader.bytes_remaining();
|
||||
sections.insert(name, reader.read_bytes(len).expect("bytes"));
|
||||
}
|
||||
if name == "name" {
|
||||
if let Ok(reader) = section.get_name_section_reader() {
|
||||
if let Ok(section) = read_name_section(reader) {
|
||||
name_section = Some(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionCode::Type => {
|
||||
signatures_params = section
|
||||
.get_type_section_reader()
|
||||
.expect("type section")
|
||||
.into_iter()
|
||||
.map(|ft| ft.expect("type").params)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
SectionCode::Function => {
|
||||
func_params_refs = section
|
||||
.get_function_section_reader()
|
||||
.expect("function section")
|
||||
.into_iter()
|
||||
.map(|index| index.expect("func index") as usize)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
SectionCode::Code => {
|
||||
code_section_offset = section.range().start as u64;
|
||||
func_locals = section
|
||||
.get_code_section_reader()
|
||||
.expect("code section")
|
||||
.into_iter()
|
||||
.map(|body| {
|
||||
let locals = body
|
||||
.expect("body")
|
||||
.get_locals_reader()
|
||||
.expect("locals reader");
|
||||
locals
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.expect("locals data")
|
||||
.into_boxed_slice()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let func_meta = func_params_refs
|
||||
.into_iter()
|
||||
.zip(func_locals.into_iter())
|
||||
.map(|(params_index, locals)| FunctionMetadata {
|
||||
params: signatures_params[params_index].clone(),
|
||||
locals,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
DebugInfoData {
|
||||
dwarf: convert_sections(sections),
|
||||
name_section,
|
||||
wasm_file: WasmFileInfo {
|
||||
path: None,
|
||||
code_section_offset,
|
||||
funcs: func_meta.into_boxed_slice(),
|
||||
},
|
||||
}
|
||||
}
|
676
lib/debug-writer/src/transform/address_transform.rs
Normal file
676
lib/debug-writer/src/transform/address_transform.rs
Normal file
@ -0,0 +1,676 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `907e7aac01af333a0af310ce0472abbc8a9adb6c`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use crate::WasmFileInfo;
|
||||
use cranelift_codegen::ir::SourceLoc;
|
||||
use cranelift_entity::{EntityRef, PrimaryMap};
|
||||
use gimli::write;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FromIterator;
|
||||
use wasmer_runtime_core::types::FuncIndex;
|
||||
use wasmtime_environ::{FunctionAddressMap, ModuleAddressMap};
|
||||
|
||||
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 accending order for search.
|
||||
#[derive(Debug)]
|
||||
struct Range {
|
||||
wasm_start: WasmAddress,
|
||||
wasm_end: WasmAddress,
|
||||
gen_start: GeneratedAddress,
|
||||
gen_end: GeneratedAddress,
|
||||
positions: Box<[Position]>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug)]
|
||||
struct FuncLookup {
|
||||
index: Vec<(WasmAddress, Box<[usize]>)>,
|
||||
ranges: Box<[Range]>,
|
||||
}
|
||||
|
||||
/// Mapping of original functions to generated code locations/ranges.
|
||||
#[derive(Debug)]
|
||||
struct FuncTransform {
|
||||
start: WasmAddress,
|
||||
end: WasmAddress,
|
||||
index: FuncIndex,
|
||||
lookup: FuncLookup,
|
||||
}
|
||||
|
||||
/// Module functions mapping to generated code.
|
||||
#[derive(Debug)]
|
||||
pub struct AddressTransform {
|
||||
map: PrimaryMap<FuncIndex, FunctionMap>,
|
||||
func: Vec<(WasmAddress, FuncTransform)>,
|
||||
}
|
||||
|
||||
/// Returns a wasm bytecode offset in the code section from SourceLoc.
|
||||
pub fn get_wasm_code_offset(loc: SourceLoc, code_section_offset: u64) -> WasmAddress {
|
||||
// Code section size <= 4GB, allow wrapped SourceLoc to recover the overflow.
|
||||
loc.bits().wrapping_sub(code_section_offset as u32) as WasmAddress
|
||||
}
|
||||
|
||||
fn build_function_lookup(
|
||||
ft: &FunctionAddressMap,
|
||||
code_section_offset: u64,
|
||||
) -> (WasmAddress, WasmAddress, FuncLookup) {
|
||||
assert!(code_section_offset <= ft.start_srcloc.bits() as u64);
|
||||
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!(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();
|
||||
for t in &ft.instructions {
|
||||
if t.srcloc.is_default() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
|
||||
assert!(fn_start <= offset);
|
||||
assert!(offset <= fn_end);
|
||||
|
||||
let inst_gen_start = t.code_offset;
|
||||
let inst_gen_end = t.code_offset + t.code_len;
|
||||
|
||||
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();
|
||||
}
|
||||
// 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,
|
||||
});
|
||||
last_wasm_pos = offset;
|
||||
}
|
||||
let last_gen_addr = ft.body_offset + ft.body_len;
|
||||
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 last_wasm_pos.is_some() {
|
||||
index.insert(
|
||||
last_wasm_pos.unwrap(),
|
||||
active_ranges.clone().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);
|
||||
}
|
||||
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(
|
||||
at: &ModuleAddressMap,
|
||||
code_section_offset: u64,
|
||||
) -> PrimaryMap<FuncIndex, FunctionMap> {
|
||||
let mut map = PrimaryMap::new();
|
||||
for (_, ft) in at {
|
||||
let mut fn_map = Vec::new();
|
||||
for t in &ft.instructions {
|
||||
if t.srcloc.is_default() {
|
||||
continue;
|
||||
}
|
||||
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
|
||||
fn_map.push(AddressMap {
|
||||
generated: t.code_offset,
|
||||
wasm: offset,
|
||||
});
|
||||
}
|
||||
|
||||
if cfg!(debug) {
|
||||
// fn_map is sorted by the generated field -- see FunctionAddressMap::instructions.
|
||||
for i in 1..fn_map.len() {
|
||||
assert!(fn_map[i - 1].generated <= fn_map[i].generated);
|
||||
}
|
||||
}
|
||||
|
||||
map.push(FunctionMap {
|
||||
offset: ft.body_offset,
|
||||
len: ft.body_len,
|
||||
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
|
||||
}
|
||||
|
||||
struct TransformRangeIter<'a> {
|
||||
addr: u64,
|
||||
indicies: &'a [usize],
|
||||
ranges: &'a [Range],
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeIter<'a> {
|
||||
fn new(func: &'a FuncTransform, addr: u64) -> 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 {
|
||||
TransformRangeIter {
|
||||
addr,
|
||||
indicies: range_indices,
|
||||
ranges: &func.lookup.ranges,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for TransformRangeIter<'a> {
|
||||
type Item = (usize, usize);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((first, tail)) = self.indicies.split_first() {
|
||||
let range_index = *first;
|
||||
let range = &self.ranges[range_index];
|
||||
self.indicies = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TransformRangeEndIter<'a> {
|
||||
addr: u64,
|
||||
indicies: &'a [usize],
|
||||
ranges: &'a [Range],
|
||||
}
|
||||
|
||||
impl<'a> TransformRangeEndIter<'a> {
|
||||
fn new(func: &'a FuncTransform, addr: u64) -> 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,
|
||||
indicies: range_indices,
|
||||
ranges: &func.lookup.ranges,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TransformRangeEndIter<'a> {
|
||||
type Item = (usize, usize);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some((first, tail)) = self.indicies.split_first() {
|
||||
let range_index = *first;
|
||||
let range = &self.ranges[range_index];
|
||||
if range.wasm_start >= self.addr {
|
||||
continue;
|
||||
}
|
||||
self.indicies = tail;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressTransform {
|
||||
pub fn new(at: &ModuleAddressMap, wasm_file: &WasmFileInfo) -> Self {
|
||||
let code_section_offset = wasm_file.code_section_offset;
|
||||
|
||||
let mut func = BTreeMap::new();
|
||||
for (i, ft) in at {
|
||||
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(at, 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<FuncIndex> {
|
||||
self.find_func(addr).map(|f| f.index)
|
||||
}
|
||||
|
||||
pub fn translate_raw(&self, addr: u64) -> Option<(FuncIndex, 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 = TransformRangeIter::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(
|
||||
&self,
|
||||
start: u64,
|
||||
end: u64,
|
||||
) -> Option<(FuncIndex, Vec<(GeneratedAddress, GeneratedAddress)>)> {
|
||||
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 mut starts: HashMap<usize, usize> =
|
||||
HashMap::from_iter(TransformRangeIter::new(func, start).map(|(a, r)| (r, a)));
|
||||
let mut result = Vec::new();
|
||||
TransformRangeEndIter::new(func, end).for_each(|(a, r)| {
|
||||
let range_start = if let Some(range_start) = starts.get(&r) {
|
||||
let range_start = *range_start;
|
||||
starts.remove(&r);
|
||||
range_start
|
||||
} else {
|
||||
let range = &func.lookup.ranges[r];
|
||||
range.gen_start
|
||||
};
|
||||
result.push((range_start, a));
|
||||
});
|
||||
for (r, range_start) in starts {
|
||||
let range = &func.lookup.ranges[r];
|
||||
result.push((range_start, range.gen_end));
|
||||
}
|
||||
return Some((func.index, result));
|
||||
}
|
||||
// Address was not found: function was not compiled?
|
||||
None
|
||||
}
|
||||
|
||||
pub fn translate_ranges(&self, start: u64, end: u64) -> Vec<(write::Address, u64)> {
|
||||
self.translate_ranges_raw(start, end)
|
||||
.map_or(vec![], |(func_index, ranges)| {
|
||||
ranges
|
||||
.iter()
|
||||
.map(|(start, end)| {
|
||||
(
|
||||
write::Address::Symbol {
|
||||
symbol: func_index.index(),
|
||||
addend: *start as i64,
|
||||
},
|
||||
(*end - *start) as u64,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map(&self) -> &PrimaryMap<FuncIndex, FunctionMap> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
pub fn func_range(&self, index: FuncIndex) -> (GeneratedAddress, GeneratedAddress) {
|
||||
let map = &self.map[index];
|
||||
(map.offset, map.offset + map.len)
|
||||
}
|
||||
|
||||
pub fn func_source_range(&self, index: FuncIndex) -> (WasmAddress, WasmAddress) {
|
||||
let map = &self.map[index];
|
||||
(map.wasm_start, map.wasm_end)
|
||||
}
|
||||
|
||||
pub fn convert_to_code_range(
|
||||
&self,
|
||||
addr: write::Address,
|
||||
len: u64,
|
||||
) -> (GeneratedAddress, GeneratedAddress) {
|
||||
let start = if let write::Address::Symbol { addend, .. } = addr {
|
||||
// TODO subtract self.map[symbol].offset ?
|
||||
addend as GeneratedAddress
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
(start, start + len as GeneratedAddress)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
|
||||
use crate::read_debug_info::WasmFileInfo;
|
||||
use cranelift_codegen::ir::SourceLoc;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use gimli::write::Address;
|
||||
use std::iter::FromIterator;
|
||||
use wasmtime_environ::{FunctionAddressMap, InstructionAddressMap, ModuleAddressMap};
|
||||
|
||||
#[test]
|
||||
fn test_get_wasm_code_offset() {
|
||||
let offset = get_wasm_code_offset(SourceLoc::new(3), 1);
|
||||
assert_eq!(2, offset);
|
||||
let offset = get_wasm_code_offset(SourceLoc::new(16), 0xF000_0000);
|
||||
assert_eq!(0x1000_0010, offset);
|
||||
let offset = get_wasm_code_offset(SourceLoc::new(1), 0x20_8000_0000);
|
||||
assert_eq!(0x8000_0001, offset);
|
||||
}
|
||||
|
||||
fn create_simple_func(wasm_offset: u32) -> FunctionAddressMap {
|
||||
FunctionAddressMap {
|
||||
instructions: vec![
|
||||
InstructionAddressMap {
|
||||
srcloc: SourceLoc::new(wasm_offset + 2),
|
||||
code_offset: 5,
|
||||
code_len: 3,
|
||||
},
|
||||
InstructionAddressMap {
|
||||
srcloc: SourceLoc::new(wasm_offset + 7),
|
||||
code_offset: 15,
|
||||
code_len: 8,
|
||||
},
|
||||
],
|
||||
start_srcloc: SourceLoc::new(wasm_offset),
|
||||
end_srcloc: SourceLoc::new(wasm_offset + 10),
|
||||
body_offset: 0,
|
||||
body_len: 30,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_simple_module(func: FunctionAddressMap) -> ModuleAddressMap {
|
||||
PrimaryMap::from_iter(vec![func])
|
||||
}
|
||||
|
||||
#[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]
|
||||
input.instructions.push(InstructionAddressMap {
|
||||
srcloc: SourceLoc::new(11 + 2),
|
||||
code_offset: 23,
|
||||
code_len: 3,
|
||||
});
|
||||
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,
|
||||
funcs: Box::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
|
||||
);
|
||||
}
|
||||
}
|
308
lib/debug-writer/src/transform/attr.rs
Normal file
308
lib/debug-writer/src/transform/attr.rs
Normal file
@ -0,0 +1,308 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `907e7aac01af333a0af310ce0472abbc8a9adb6c`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::expression::{compile_expression, CompiledExpression, FunctionFrameInfo};
|
||||
use super::range_info_builder::RangeInfoBuilder;
|
||||
use super::unit::PendingDieRef;
|
||||
use super::{DebugInputContext, Reader, TransformError};
|
||||
use anyhow::Error;
|
||||
use gimli::{
|
||||
write, AttributeValue, DebugLineOffset, DebugStr, DebuggingInformationEntry, UnitOffset,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) enum FileAttributeContext<'a> {
|
||||
Root(Option<DebugLineOffset>),
|
||||
Children(&'a Vec<write::FileId>, 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>(
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&FunctionFrameInfo>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
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,
|
||||
die_ref_map: &HashMap<UnitOffset, write::UnitEntryId>,
|
||||
pending_die_refs: &mut Vec<PendingDieRef>,
|
||||
file_context: FileAttributeContext<'a>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let _tag = &entry.tag();
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
|
||||
let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {
|
||||
subprogram_range_builder
|
||||
} else if entry.tag() == gimli::DW_TAG_compile_unit {
|
||||
// FIXME currently address_transform operate on a single func range,
|
||||
// once it is fixed we can properly set DW_AT_ranges attribute.
|
||||
// Using for now DW_AT_low_pc = 0.
|
||||
RangeInfoBuilder::Position(0)
|
||||
} else {
|
||||
RangeInfoBuilder::from(entry, context, unit_encoding, 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(_) 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::Addr(u) => {
|
||||
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_context {
|
||||
write::AttributeValue::FileIndex(Some(file_map[(i - 1) 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::RangeListsRef(r) => {
|
||||
let range_info =
|
||||
RangeInfoBuilder::from_ranges_ref(r, context, unit_encoding, 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,
|
||||
context.debug_addr_base,
|
||||
)?;
|
||||
let frame_base = if let FileAttributeContext::Children(_, frame_base) = file_context
|
||||
{
|
||||
frame_base
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut result = None;
|
||||
while let Some(loc) = locs.next()? {
|
||||
if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {
|
||||
if result.is_none() {
|
||||
result = Some(Vec::new());
|
||||
}
|
||||
for (start, len, expr) in expr.build_with_locals(
|
||||
&[(loc.range.begin, loc.range.end)],
|
||||
addr_tr,
|
||||
frame_info,
|
||||
endian,
|
||||
) {
|
||||
if len == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
result.as_mut().unwrap().push(write::Location::StartLength {
|
||||
begin: start,
|
||||
length: len,
|
||||
data: expr,
|
||||
});
|
||||
}
|
||||
} 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, endian);
|
||||
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.0.eq(&prev_expr.0) {
|
||||
continue; // the same expression
|
||||
}
|
||||
found_expr = None;
|
||||
break;
|
||||
}
|
||||
found_expr = Some(expr.clone())
|
||||
}
|
||||
found_expr
|
||||
};
|
||||
if found_single_expr.is_some() {
|
||||
write::AttributeValue::Exprloc(found_single_expr.unwrap())
|
||||
} 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(ref offset) => {
|
||||
if let Some(unit_id) = die_ref_map.get(offset) {
|
||||
write::AttributeValue::ThisUnitEntryRef(*unit_id)
|
||||
} else {
|
||||
pending_die_refs.push((current_scope_id, attr.name(), *offset));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// AttributeValue::DebugInfoRef(_) => {
|
||||
// continue;
|
||||
// }
|
||||
_ => panic!(), //write::AttributeValue::StringRef(out_strings.add("_")),
|
||||
};
|
||||
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,
|
||||
debug_str: &DebugStr<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<write::LineString, gimli::Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let content = match attr_value {
|
||||
AttributeValue::DebugStrRef(str_offset) => {
|
||||
debug_str.get_str(*str_offset)?.to_slice()?.to_vec()
|
||||
}
|
||||
AttributeValue::String(b) => b.to_slice()?.to_vec(),
|
||||
_ => panic!("Unexpected attribute value"),
|
||||
};
|
||||
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),
|
||||
_ => panic!("DW_FORM_line_strp or other not supported"),
|
||||
})
|
||||
}
|
515
lib/debug-writer/src/transform/expression.rs
Normal file
515
lib/debug-writer/src/transform/expression.rs
Normal file
@ -0,0 +1,515 @@
|
||||
// Copyright 2029 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `3992b8669f9b9e185abe81e9998ce2ff4d40ff68`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use super::address_transform::AddressTransform;
|
||||
use anyhow::Error;
|
||||
use cranelift_codegen::ir::{StackSlots, ValueLabel, ValueLoc};
|
||||
use cranelift_codegen::isa::RegUnit;
|
||||
use cranelift_codegen::ValueLabelsRanges;
|
||||
use cranelift_entity::EntityRef;
|
||||
use cranelift_wasm::{get_vmctx_value_label, DefinedFuncIndex};
|
||||
use gimli::{self, write, Expression, Operation, Reader, ReaderOffset, Register, X86_64};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionFrameInfo<'a> {
|
||||
pub value_ranges: &'a ValueLabelsRanges,
|
||||
pub memory_offset: i64,
|
||||
pub stack_slots: &'a StackSlots,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CompiledExpressionPart {
|
||||
Code(Vec<u8>),
|
||||
Local(ValueLabel),
|
||||
Deref,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompiledExpression {
|
||||
parts: Vec<CompiledExpressionPart>,
|
||||
need_deref: bool,
|
||||
}
|
||||
|
||||
impl Clone for CompiledExpressionPart {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
CompiledExpressionPart::Code(c) => CompiledExpressionPart::Code(c.clone()),
|
||||
CompiledExpressionPart::Local(i) => CompiledExpressionPart::Local(*i),
|
||||
CompiledExpressionPart::Deref => CompiledExpressionPart::Deref,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompiledExpression {
|
||||
pub fn vmctx() -> CompiledExpression {
|
||||
CompiledExpression::from_label(get_vmctx_value_label())
|
||||
}
|
||||
|
||||
pub fn from_label(label: ValueLabel) -> CompiledExpression {
|
||||
CompiledExpression {
|
||||
parts: vec![
|
||||
CompiledExpressionPart::Local(label),
|
||||
CompiledExpressionPart::Code(vec![gimli::constants::DW_OP_stack_value.0 as u8]),
|
||||
],
|
||||
need_deref: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_reg(reg: RegUnit) -> Register {
|
||||
static mut REG_X86_MAP: Option<HashMap<RegUnit, Register>> = None;
|
||||
// FIXME lazy initialization?
|
||||
unsafe {
|
||||
if REG_X86_MAP.is_none() {
|
||||
REG_X86_MAP = Some(HashMap::new());
|
||||
}
|
||||
if let Some(val) = REG_X86_MAP.as_mut().unwrap().get(®) {
|
||||
return *val;
|
||||
}
|
||||
let result = match reg {
|
||||
0 => X86_64::RAX,
|
||||
1 => X86_64::RCX,
|
||||
2 => X86_64::RDX,
|
||||
3 => X86_64::RBX,
|
||||
4 => X86_64::RSP,
|
||||
5 => X86_64::RBP,
|
||||
6 => X86_64::RSI,
|
||||
7 => X86_64::RDI,
|
||||
8 => X86_64::R8,
|
||||
9 => X86_64::R9,
|
||||
10 => X86_64::R10,
|
||||
11 => X86_64::R11,
|
||||
12 => X86_64::R12,
|
||||
13 => X86_64::R13,
|
||||
14 => X86_64::R14,
|
||||
15 => X86_64::R15,
|
||||
16 => X86_64::XMM0,
|
||||
17 => X86_64::XMM1,
|
||||
18 => X86_64::XMM2,
|
||||
19 => X86_64::XMM3,
|
||||
20 => X86_64::XMM4,
|
||||
21 => X86_64::XMM5,
|
||||
22 => X86_64::XMM6,
|
||||
23 => X86_64::XMM7,
|
||||
24 => X86_64::XMM8,
|
||||
25 => X86_64::XMM9,
|
||||
26 => X86_64::XMM10,
|
||||
27 => X86_64::XMM11,
|
||||
28 => X86_64::XMM12,
|
||||
29 => X86_64::XMM13,
|
||||
30 => X86_64::XMM14,
|
||||
31 => X86_64::XMM15,
|
||||
_ => panic!("unknown x86_64 register {}", reg),
|
||||
};
|
||||
REG_X86_MAP.as_mut().unwrap().insert(reg, result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_loc(loc: ValueLoc, frame_info: Option<&FunctionFrameInfo>) -> Option<Vec<u8>> {
|
||||
match loc {
|
||||
ValueLoc::Reg(reg) => {
|
||||
let machine_reg = map_reg(reg).0 as u8;
|
||||
assert!(machine_reg < 32); // FIXME
|
||||
Some(vec![gimli::constants::DW_OP_reg0.0 + machine_reg])
|
||||
}
|
||||
ValueLoc::Stack(ss) => {
|
||||
if let Some(frame_info) = frame_info {
|
||||
if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
|
||||
use gimli::write::Writer;
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
let mut writer = write::EndianVec::new(endian);
|
||||
writer
|
||||
.write_u8(gimli::constants::DW_OP_breg0.0 + X86_64::RBP.0 as u8)
|
||||
.expect("bp wr");
|
||||
writer.write_sleb128(ss_offset as i64 + 16).expect("ss wr");
|
||||
writer
|
||||
.write_u8(gimli::constants::DW_OP_deref.0 as u8)
|
||||
.expect("bp wr");
|
||||
let buf = writer.into_vec();
|
||||
return Some(buf);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn append_memory_deref(
|
||||
buf: &mut Vec<u8>,
|
||||
frame_info: &FunctionFrameInfo,
|
||||
vmctx_loc: ValueLoc,
|
||||
endian: gimli::RunTimeEndian,
|
||||
) -> write::Result<bool> {
|
||||
use gimli::write::Writer;
|
||||
let mut writer = write::EndianVec::new(endian);
|
||||
match vmctx_loc {
|
||||
ValueLoc::Reg(vmctx_reg) => {
|
||||
let reg = map_reg(vmctx_reg);
|
||||
writer.write_u8(gimli::constants::DW_OP_breg0.0 + reg.0 as u8)?;
|
||||
writer.write_sleb128(frame_info.memory_offset)?;
|
||||
}
|
||||
ValueLoc::Stack(ss) => {
|
||||
if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
|
||||
writer.write_u8(gimli::constants::DW_OP_breg0.0 + X86_64::RBP.0 as u8)?;
|
||||
writer.write_sleb128(ss_offset as i64 + 16)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_deref.0 as u8)?;
|
||||
|
||||
writer.write_u8(gimli::constants::DW_OP_consts.0 as u8)?;
|
||||
writer.write_sleb128(frame_info.memory_offset)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_plus.0 as u8)?;
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
writer.write_u8(gimli::constants::DW_OP_deref.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_swap.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_stack_value.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_constu.0 as u8)?;
|
||||
writer.write_uleb128(0xffff_ffff)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_and.0 as u8)?;
|
||||
writer.write_u8(gimli::constants::DW_OP_plus.0 as u8)?;
|
||||
buf.extend_from_slice(writer.slice());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
impl CompiledExpression {
|
||||
pub fn is_simple(&self) -> bool {
|
||||
if let [CompiledExpressionPart::Code(_)] = self.parts.as_slice() {
|
||||
true
|
||||
} else {
|
||||
self.parts.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Option<write::Expression> {
|
||||
if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {
|
||||
return Some(write::Expression(code.to_vec()));
|
||||
}
|
||||
// locals found, not supported
|
||||
None
|
||||
}
|
||||
|
||||
pub fn build_with_locals(
|
||||
&self,
|
||||
scope: &[(u64, u64)], // wasm ranges
|
||||
addr_tr: &AddressTransform,
|
||||
frame_info: Option<&FunctionFrameInfo>,
|
||||
endian: gimli::RunTimeEndian,
|
||||
) -> Vec<(write::Address, u64, write::Expression)> {
|
||||
if scope.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {
|
||||
let mut result_scope = Vec::new();
|
||||
for s in scope {
|
||||
for (addr, len) in addr_tr.translate_ranges(s.0, s.1) {
|
||||
result_scope.push((addr, len, write::Expression(code.to_vec())));
|
||||
}
|
||||
}
|
||||
return result_scope;
|
||||
}
|
||||
|
||||
let vmctx_label = get_vmctx_value_label();
|
||||
|
||||
// Some locals are present, preparing and divided ranges based on the scope
|
||||
// and frame_info data.
|
||||
let mut ranges_builder = ValueLabelRangesBuilder::new(scope, addr_tr, frame_info);
|
||||
for p in &self.parts {
|
||||
match p {
|
||||
CompiledExpressionPart::Code(_) => (),
|
||||
CompiledExpressionPart::Local(label) => ranges_builder.process_label(*label),
|
||||
CompiledExpressionPart::Deref => ranges_builder.process_label(vmctx_label),
|
||||
}
|
||||
}
|
||||
if self.need_deref {
|
||||
ranges_builder.process_label(vmctx_label);
|
||||
}
|
||||
ranges_builder.remove_incomplete_ranges();
|
||||
let ranges = ranges_builder.ranges;
|
||||
|
||||
let mut result = Vec::new();
|
||||
'range: for CachedValueLabelRange {
|
||||
func_index,
|
||||
start,
|
||||
end,
|
||||
label_location,
|
||||
} in ranges
|
||||
{
|
||||
// build expression
|
||||
let mut code_buf = Vec::new();
|
||||
for part in &self.parts {
|
||||
match part {
|
||||
CompiledExpressionPart::Code(c) => code_buf.extend_from_slice(c.as_slice()),
|
||||
CompiledExpressionPart::Local(label) => {
|
||||
let loc = *label_location.get(&label).expect("loc");
|
||||
if let Some(expr) = translate_loc(loc, frame_info) {
|
||||
code_buf.extend_from_slice(&expr)
|
||||
} else {
|
||||
continue 'range;
|
||||
}
|
||||
}
|
||||
CompiledExpressionPart::Deref => {
|
||||
if let (Some(vmctx_loc), Some(frame_info)) =
|
||||
(label_location.get(&vmctx_label), frame_info)
|
||||
{
|
||||
if !append_memory_deref(&mut code_buf, frame_info, *vmctx_loc, endian)
|
||||
.expect("append_memory_deref")
|
||||
{
|
||||
continue 'range;
|
||||
}
|
||||
} else {
|
||||
continue 'range;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.need_deref {
|
||||
if let (Some(vmctx_loc), Some(frame_info)) =
|
||||
(label_location.get(&vmctx_label), frame_info)
|
||||
{
|
||||
if !append_memory_deref(&mut code_buf, frame_info, *vmctx_loc, endian)
|
||||
.expect("append_memory_deref")
|
||||
{
|
||||
continue 'range;
|
||||
}
|
||||
} else {
|
||||
continue 'range;
|
||||
};
|
||||
}
|
||||
result.push((
|
||||
write::Address::Symbol {
|
||||
symbol: func_index.index(),
|
||||
addend: start as i64,
|
||||
},
|
||||
(end - start) as u64,
|
||||
write::Expression(code_buf),
|
||||
));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_expression<R>(
|
||||
expr: &Expression<R>,
|
||||
encoding: gimli::Encoding,
|
||||
frame_base: Option<&CompiledExpression>,
|
||||
) -> Result<Option<CompiledExpression>, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut parts = Vec::new();
|
||||
let mut need_deref = false;
|
||||
if let Some(frame_base) = frame_base {
|
||||
parts.extend_from_slice(&frame_base.parts);
|
||||
need_deref = frame_base.need_deref;
|
||||
}
|
||||
let base_len = parts.len();
|
||||
let mut pc = expr.0.clone();
|
||||
let mut code_chunk = Vec::new();
|
||||
let buf = expr.0.to_slice()?;
|
||||
while !pc.is_empty() {
|
||||
let next = buf[pc.offset_from(&expr.0).into_u64() as usize];
|
||||
need_deref = true;
|
||||
if next == 0xED {
|
||||
// WebAssembly DWARF extension
|
||||
pc.read_u8()?;
|
||||
let ty = pc.read_uleb128()?;
|
||||
assert_eq!(ty, 0);
|
||||
let index = pc.read_sleb128()?;
|
||||
pc.read_u8()?; // consume 159
|
||||
if code_chunk.len() > 0 {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
code_chunk = Vec::new();
|
||||
}
|
||||
let label = ValueLabel::from_u32(index as u32);
|
||||
parts.push(CompiledExpressionPart::Local(label));
|
||||
} else {
|
||||
let pos = pc.offset_from(&expr.0).into_u64() as usize;
|
||||
let op = Operation::parse(&mut pc, &expr.0, encoding)?;
|
||||
match op {
|
||||
Operation::Literal { .. } | Operation::PlusConstant { .. } => (),
|
||||
Operation::StackValue => {
|
||||
need_deref = false;
|
||||
}
|
||||
Operation::Deref { .. } => {
|
||||
if code_chunk.len() > 0 {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
code_chunk = Vec::new();
|
||||
}
|
||||
parts.push(CompiledExpressionPart::Deref);
|
||||
}
|
||||
_ => {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
let chunk = &buf[pos..pc.offset_from(&expr.0).into_u64() as usize];
|
||||
code_chunk.extend_from_slice(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if code_chunk.len() > 0 {
|
||||
parts.push(CompiledExpressionPart::Code(code_chunk));
|
||||
}
|
||||
|
||||
if base_len > 0 && base_len + 1 < parts.len() {
|
||||
// see if we can glue two code chunks
|
||||
if let [CompiledExpressionPart::Code(cc1), CompiledExpressionPart::Code(cc2)] =
|
||||
&parts[base_len..base_len + 1]
|
||||
{
|
||||
let mut combined = cc1.clone();
|
||||
combined.extend_from_slice(cc2);
|
||||
parts[base_len] = CompiledExpressionPart::Code(combined);
|
||||
parts.remove(base_len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(CompiledExpression { parts, need_deref }))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CachedValueLabelRange {
|
||||
func_index: DefinedFuncIndex,
|
||||
start: usize,
|
||||
end: usize,
|
||||
label_location: HashMap<ValueLabel, ValueLoc>,
|
||||
}
|
||||
|
||||
struct ValueLabelRangesBuilder<'a, 'b> {
|
||||
ranges: Vec<CachedValueLabelRange>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&'a FunctionFrameInfo<'b>>,
|
||||
processed_labels: HashSet<ValueLabel>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ValueLabelRangesBuilder<'a, 'b> {
|
||||
fn new(
|
||||
scope: &[(u64, u64)], // wasm ranges
|
||||
addr_tr: &'a AddressTransform,
|
||||
frame_info: Option<&'a FunctionFrameInfo<'b>>,
|
||||
) -> Self {
|
||||
let mut ranges = Vec::new();
|
||||
for s in scope {
|
||||
if let Some((func_index, tr)) = addr_tr.translate_ranges_raw(s.0, s.1) {
|
||||
for (start, end) in tr {
|
||||
ranges.push(CachedValueLabelRange {
|
||||
func_index,
|
||||
start,
|
||||
end,
|
||||
label_location: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start));
|
||||
ValueLabelRangesBuilder {
|
||||
ranges,
|
||||
addr_tr,
|
||||
frame_info,
|
||||
processed_labels: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_label(&mut self, label: ValueLabel) {
|
||||
if self.processed_labels.contains(&label) {
|
||||
return;
|
||||
}
|
||||
self.processed_labels.insert(label);
|
||||
|
||||
let value_ranges = if let Some(frame_info) = self.frame_info {
|
||||
&frame_info.value_ranges
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let ranges = &mut self.ranges;
|
||||
if let Some(local_ranges) = value_ranges.get(&label) {
|
||||
for local_range in local_ranges {
|
||||
let wasm_start = local_range.start;
|
||||
let wasm_end = local_range.end;
|
||||
let loc = local_range.loc;
|
||||
// Find all native ranges for the value label ranges.
|
||||
for (addr, len) in self
|
||||
.addr_tr
|
||||
.translate_ranges(wasm_start as u64, wasm_end as u64)
|
||||
{
|
||||
let (range_start, range_end) = self.addr_tr.convert_to_code_range(addr, len);
|
||||
if range_start == range_end {
|
||||
continue;
|
||||
}
|
||||
assert!(range_start < range_end);
|
||||
// Find acceptable scope of ranges to intersect with.
|
||||
let i = match ranges.binary_search_by(|s| s.start.cmp(&range_start)) {
|
||||
Ok(i) => i,
|
||||
Err(i) => {
|
||||
if i > 0 && range_start < ranges[i - 1].end {
|
||||
i - 1
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
};
|
||||
let j = match ranges.binary_search_by(|s| s.start.cmp(&range_end)) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
// Starting for the end, intersect (range_start..range_end) with
|
||||
// self.ranges array.
|
||||
for i in (i..j).rev() {
|
||||
if range_end <= ranges[i].start || ranges[i].end <= range_start {
|
||||
continue;
|
||||
}
|
||||
if range_end < ranges[i].end {
|
||||
// Cutting some of the range from the end.
|
||||
let mut tail = ranges[i].clone();
|
||||
ranges[i].end = range_end;
|
||||
tail.start = range_end;
|
||||
ranges.insert(i + 1, tail);
|
||||
}
|
||||
assert!(ranges[i].end <= range_end);
|
||||
if range_start <= ranges[i].start {
|
||||
ranges[i].label_location.insert(label, loc);
|
||||
continue;
|
||||
}
|
||||
// Cutting some of the range from the start.
|
||||
let mut tail = ranges[i].clone();
|
||||
ranges[i].end = range_start;
|
||||
tail.start = range_start;
|
||||
tail.label_location.insert(label, loc);
|
||||
ranges.insert(i + 1, tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_incomplete_ranges(&mut self) {
|
||||
// Ranges with not-enough labels are discarded.
|
||||
let processed_labels_len = self.processed_labels.len();
|
||||
self.ranges
|
||||
.retain(|r| r.label_location.len() == processed_labels_len);
|
||||
}
|
||||
}
|
248
lib/debug-writer/src/transform/line_program.rs
Normal file
248
lib/debug-writer/src/transform/line_program.rs
Normal file
@ -0,0 +1,248 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `cc6e8e1af25e5f9b64e183970d50f62c8338f259`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::attr::clone_attr_string;
|
||||
use super::{Reader, TransformError};
|
||||
use anyhow::Error;
|
||||
use cranelift_entity::EntityRef;
|
||||
use gimli::{
|
||||
write, DebugLine, DebugLineOffset, DebugStr, DebuggingInformationEntry, LineEncoding, Unit,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[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, Eq, PartialEq)]
|
||||
enum ReadLineProgramState {
|
||||
SequenceEnded,
|
||||
ReadSequence,
|
||||
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_line: &DebugLine<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>), 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 = clone_attr_string(
|
||||
comp_dir.as_ref().expect("comp_dir"),
|
||||
gimli::DW_FORM_strp,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?;
|
||||
let out_comp_name = clone_attr_string(
|
||||
comp_name.as_ref().expect("comp_name"),
|
||||
gimli::DW_FORM_strp,
|
||||
debug_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();
|
||||
assert!(header.version() <= 4, "not supported 5");
|
||||
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,
|
||||
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,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?);
|
||||
dirs.push(dir_id);
|
||||
}
|
||||
let mut files = Vec::new();
|
||||
for file_entry in header.file_names() {
|
||||
let dir_id = dirs[file_entry.directory_index() as usize];
|
||||
let file_id = out_program.add_file(
|
||||
clone_attr_string(
|
||||
&file_entry.path_name(),
|
||||
gimli::DW_FORM_string,
|
||||
debug_str,
|
||||
out_strings,
|
||||
)?,
|
||||
dir_id,
|
||||
None,
|
||||
);
|
||||
files.push(file_id);
|
||||
}
|
||||
|
||||
let mut rows = program.rows();
|
||||
let mut saved_rows = BTreeMap::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() {
|
||||
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;
|
||||
}
|
||||
state = ReadLineProgramState::ReadSequence;
|
||||
}
|
||||
SavedLineProgramRow::Normal {
|
||||
address: row.address(),
|
||||
op_index: row.op_index(),
|
||||
file_index: row.file_index(),
|
||||
line: row.line().unwrap_or(0),
|
||||
column: match row.column() {
|
||||
gimli::ColumnType::LeftEdge => 0,
|
||||
gimli::ColumnType::Column(val) => val,
|
||||
},
|
||||
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.insert(row.address(), saved_row);
|
||||
}
|
||||
|
||||
let saved_rows = Vec::from_iter(saved_rows.into_iter());
|
||||
for (i, map) in addr_tr.map() {
|
||||
if map.len == 0 {
|
||||
continue; // no code generated
|
||||
}
|
||||
let symbol = i.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 - 1) 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 - 1) as u64;
|
||||
out_program.end_sequence(end_addr);
|
||||
}
|
||||
Ok((out_program, offset, files))
|
||||
} else {
|
||||
Err(TransformError("Valid line program not found").into())
|
||||
}
|
||||
}
|
133
lib/debug-writer/src/transform/mod.rs
Normal file
133
lib/debug-writer/src/transform/mod.rs
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `907e7aac01af333a0af310ce0472abbc8a9adb6c`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use crate::gc::build_dependencies;
|
||||
use crate::DebugInfoData;
|
||||
use anyhow::Error;
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use gimli::{
|
||||
write, DebugAddr, DebugAddrBase, DebugLine, DebugStr, LocationLists, RangeLists,
|
||||
UnitSectionOffset,
|
||||
};
|
||||
use simulate::generate_simulated_dwarf;
|
||||
use std::collections::HashSet;
|
||||
use thiserror::Error;
|
||||
use unit::clone_unit;
|
||||
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
|
||||
|
||||
pub use address_transform::AddressTransform;
|
||||
|
||||
mod address_transform;
|
||||
mod attr;
|
||||
mod expression;
|
||||
mod line_program;
|
||||
mod range_info_builder;
|
||||
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_line: &'a DebugLine<R>,
|
||||
debug_addr: &'a DebugAddr<R>,
|
||||
debug_addr_base: DebugAddrBase<R::Offset>,
|
||||
rnglists: &'a RangeLists<R>,
|
||||
loclists: &'a LocationLists<R>,
|
||||
reachable: &'a HashSet<UnitSectionOffset>,
|
||||
}
|
||||
|
||||
pub fn transform_dwarf(
|
||||
target_config: &TargetFrontendConfig,
|
||||
di: &DebugInfoData,
|
||||
at: &ModuleAddressMap,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
ranges: &ValueLabelsRanges,
|
||||
) -> Result<write::Dwarf, Error> {
|
||||
let addr_tr = AddressTransform::new(at, &di.wasm_file);
|
||||
let reachable = build_dependencies(&di.dwarf, &addr_tr)?.get_reachable();
|
||||
|
||||
let context = DebugInputContext {
|
||||
debug_str: &di.dwarf.debug_str,
|
||||
debug_line: &di.dwarf.debug_line,
|
||||
debug_addr: &di.dwarf.debug_addr,
|
||||
debug_addr_base: DebugAddrBase(0),
|
||||
rnglists: &di.dwarf.ranges,
|
||||
loclists: &di.dwarf.locations,
|
||||
reachable: &reachable,
|
||||
};
|
||||
|
||||
let out_encoding = gimli::Encoding {
|
||||
format: gimli::Format::Dwarf32,
|
||||
// TODO: this should be configurable
|
||||
// macOS doesn't seem to support DWARF > 3
|
||||
version: 3,
|
||||
address_size: target_config.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 translated = HashSet::new();
|
||||
let mut iter = di.dwarf.debug_info.units();
|
||||
while let Some(unit) = iter.next().unwrap_or(None) {
|
||||
let unit = di.dwarf.unit(unit)?;
|
||||
clone_unit(
|
||||
unit,
|
||||
&context,
|
||||
&addr_tr,
|
||||
&ranges,
|
||||
out_encoding,
|
||||
&vmctx_info,
|
||||
&mut out_units,
|
||||
&mut out_strings,
|
||||
&mut translated,
|
||||
)?;
|
||||
}
|
||||
|
||||
generate_simulated_dwarf(
|
||||
&addr_tr,
|
||||
di,
|
||||
&vmctx_info,
|
||||
&ranges,
|
||||
&translated,
|
||||
out_encoding,
|
||||
&mut out_units,
|
||||
&mut out_strings,
|
||||
)?;
|
||||
|
||||
Ok(write::Dwarf {
|
||||
units: out_units,
|
||||
line_programs: vec![],
|
||||
line_strings: out_line_strings,
|
||||
strings: out_strings,
|
||||
})
|
||||
}
|
237
lib/debug-writer/src/transform/range_info_builder.rs
Normal file
237
lib/debug-writer/src/transform/range_info_builder.rs
Normal file
@ -0,0 +1,237 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `907e7aac01af333a0af310ce0472abbc8a9adb6c`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::{DebugInputContext, Reader};
|
||||
use anyhow::Error;
|
||||
use cranelift_entity::EntityRef;
|
||||
use wasmer_runtime_core::types::FuncIndex
|
||||
use gimli::{write, AttributeValue, DebuggingInformationEntry, RangeListsOffset};
|
||||
|
||||
pub(crate) enum RangeInfoBuilder {
|
||||
Undefined,
|
||||
Position(u64),
|
||||
Ranges(Vec<(u64, u64)>),
|
||||
Function(FuncIndex),
|
||||
}
|
||||
|
||||
impl RangeInfoBuilder {
|
||||
pub(crate) fn from<R>(
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
if let Some(AttributeValue::RangeListsRef(r)) = entry.attr_value(gimli::DW_AT_ranges)? {
|
||||
return RangeInfoBuilder::from_ranges_ref(r, context, unit_encoding, cu_low_pc);
|
||||
};
|
||||
|
||||
let low_pc =
|
||||
if let Some(AttributeValue::Addr(addr)) = entry.attr_value(gimli::DW_AT_low_pc)? {
|
||||
addr
|
||||
} 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>(
|
||||
ranges: RangeListsOffset,
|
||||
context: &DebugInputContext<R>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut ranges = context.rnglists.ranges(
|
||||
ranges,
|
||||
unit_encoding,
|
||||
cu_low_pc,
|
||||
&context.debug_addr,
|
||||
context.debug_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.len() > 0 {
|
||||
RangeInfoBuilder::Ranges(result)
|
||||
} else {
|
||||
RangeInfoBuilder::Undefined
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_subprogram_die<R>(
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
context: &DebugInputContext<R>,
|
||||
unit_encoding: gimli::Encoding,
|
||||
addr_tr: &AddressTransform,
|
||||
cu_low_pc: u64,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let addr =
|
||||
if let Some(AttributeValue::Addr(addr)) = entry.attr_value(gimli::DW_AT_low_pc)? {
|
||||
addr
|
||||
} else if let Some(AttributeValue::RangeListsRef(r)) =
|
||||
entry.attr_value(gimli::DW_AT_ranges)?
|
||||
{
|
||||
let mut ranges = context.rnglists.ranges(
|
||||
r,
|
||||
unit_encoding,
|
||||
cu_low_pc,
|
||||
&context.debug_addr,
|
||||
context.debug_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 {
|
||||
for tr in addr_tr.translate_ranges(*begin, *end) {
|
||||
if tr.1 == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
result.push(tr);
|
||||
}
|
||||
}
|
||||
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!(begin < end);
|
||||
for tr in addr_tr.translate_ranges(*begin, *end) {
|
||||
if tr.1 == 0 {
|
||||
// Ignore empty range
|
||||
continue;
|
||||
}
|
||||
range_list.push(write::Range::StartLength {
|
||||
begin: tr.0,
|
||||
length: tr.1,
|
||||
});
|
||||
}
|
||||
}
|
||||
out_range_lists.add(write::RangeList(range_list))
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
392
lib/debug-writer/src/transform/simulate.rs
Normal file
392
lib/debug-writer/src/transform/simulate.rs
Normal file
@ -0,0 +1,392 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `cc6e8e1af25e5f9b64e183970d50f62c8338f259`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use super::expression::{CompiledExpression, FunctionFrameInfo};
|
||||
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
|
||||
use super::AddressTransform;
|
||||
use crate::read_debug_info::WasmFileInfo;
|
||||
use anyhow::Error;
|
||||
use cranelift_entity::EntityRef;
|
||||
use cranelift_wasm::get_vmctx_value_label;
|
||||
use gimli::write;
|
||||
use gimli::{self, LineEncoding};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
// TODO: ValueLabelsRanges
|
||||
use wasmer_runtime_core::{module::ModuleInfo, state::CodeVersion};
|
||||
|
||||
pub use crate::read_debug_info::{DebugInfoData, FunctionMetadata, WasmType};
|
||||
|
||||
const PRODUCER_NAME: &str = "wasmtime";
|
||||
|
||||
fn generate_line_info(
|
||||
addr_tr: &AddressTransform,
|
||||
translated: &HashSet<u32>,
|
||||
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(&(symbol as u32)) {
|
||||
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 autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
|
||||
let module_name = di
|
||||
.name_section
|
||||
.as_ref()
|
||||
.and_then(|ns| ns.module_name.to_owned())
|
||||
.unwrap_or_else(|| unsafe {
|
||||
static mut GEN_ID: u32 = 0;
|
||||
GEN_ID += 1;
|
||||
format!("<gen-{}>", GEN_ID)
|
||||
});
|
||||
let path = format!("/<wasm-module>/{}.wasm", module_name);
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
struct WasmTypesDieRefs {
|
||||
vmctx: write::UnitEntryId,
|
||||
i32: write::UnitEntryId,
|
||||
i64: write::UnitEntryId,
|
||||
i128: 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,
|
||||
vmctx_info: &ModuleInfo,
|
||||
) -> WasmTypesDieRefs {
|
||||
let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, vmctx_info);
|
||||
|
||||
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 i128_die_id = def_type!("i128", 16, 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,
|
||||
i128: i128_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, String>>,
|
||||
out_strings: &mut write::StringTable,
|
||||
) {
|
||||
let vmctx_label = get_vmctx_value_label();
|
||||
|
||||
for label in frame_info.value_ranges.keys() {
|
||||
if label.index() == vmctx_label.index() {
|
||||
append_vmctx_info(
|
||||
unit,
|
||||
die_id,
|
||||
wasm_types.vmctx,
|
||||
addr_tr,
|
||||
Some(frame_info),
|
||||
scope_ranges,
|
||||
out_strings,
|
||||
)
|
||||
.expect("append_vmctx_info success");
|
||||
} 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 endian = gimli::RunTimeEndian::Little;
|
||||
|
||||
let expr = CompiledExpression::from_label(*label);
|
||||
let mut locs = Vec::new();
|
||||
for (begin, length, data) in
|
||||
expr.build_with_locals(scope_ranges, addr_tr, Some(frame_info), endian)
|
||||
{
|
||||
locs.push(write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
});
|
||||
}
|
||||
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))) {
|
||||
Some(n) => out_strings.add(n.to_owned()),
|
||||
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::ThisUnitEntryRef(type_die_id),
|
||||
);
|
||||
var.set(
|
||||
gimli::DW_AT_location,
|
||||
write::AttributeValue::LocationListRef(loc_list_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_simulated_dwarf(
|
||||
addr_tr: &AddressTransform,
|
||||
di: &DebugInfoData,
|
||||
vmctx_info: &ModuleVmctxInfo,
|
||||
ranges: &ValueLabelsRanges,
|
||||
translated: &HashSet<u32>,
|
||||
out_encoding: gimli::Encoding,
|
||||
out_units: &mut write::UnitTable,
|
||||
out_strings: &mut write::StringTable,
|
||||
) -> Result<(), Error> {
|
||||
let path = di
|
||||
.wasm_file
|
||||
.path
|
||||
.to_owned()
|
||||
.unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
|
||||
|
||||
let (func_names, locals_names) = if let Some(ref name_section) = di.name_section {
|
||||
(
|
||||
Some(&name_section.func_names),
|
||||
Some(&name_section.locals_names),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let (unit, root_id, name_id) = {
|
||||
let comp_dir_id = out_strings.add(path.parent().expect("path dir").to_str().unwrap());
|
||||
let name = path.file_name().expect("path name").to_str().unwrap();
|
||||
let name_id = out_strings.add(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, vmctx_info);
|
||||
|
||||
for (i, map) in addr_tr.map().iter() {
|
||||
let index = i.index();
|
||||
if translated.contains(&(index as u32)) {
|
||||
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 id = match func_names.and_then(|m| m.get(&(index as u32))) {
|
||||
Some(n) => out_strings.add(n.to_owned()),
|
||||
None => out_strings.add(format!("wasm-function[{}]", 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 = addr_tr.map().get(i).unwrap();
|
||||
let f_start = f.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(vmctx_info, i, ranges) {
|
||||
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.and_then(|m| m.get(&(index as u32))),
|
||||
out_strings,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
387
lib/debug-writer/src/transform/unit.rs
Normal file
387
lib/debug-writer/src/transform/unit.rs
Normal file
@ -0,0 +1,387 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `907e7aac01af333a0af310ce0472abbc8a9adb6c`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
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::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
|
||||
use super::{DebugInputContext, Reader, TransformError};
|
||||
use anyhow::Error;
|
||||
use cranelift_entity::EntityRef;
|
||||
use gimli::write;
|
||||
use gimli::{AttributeValue, DebuggingInformationEntry, Unit, UnitOffset};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
|
||||
|
||||
pub(crate) type PendingDieRef = (write::UnitEntryId, gimli::DwAt, UnitOffset);
|
||||
|
||||
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.
|
||||
match type_entry.attr_value(gimli::DW_AT_type)? {
|
||||
Some(AttributeValue::UnitRef(ref offset)) => {
|
||||
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("??"))
|
||||
}
|
||||
|
||||
fn replace_pointer_type<R>(
|
||||
parent_id: write::UnitEntryId,
|
||||
comp_unit: &mut write::Unit,
|
||||
wp_die_id: write::UnitEntryId,
|
||||
entry: &DebuggingInformationEntry<R>,
|
||||
unit: &Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
out_strings: &mut write::StringTable,
|
||||
pending_die_refs: &mut Vec<(write::UnitEntryId, gimli::DwAt, UnitOffset)>,
|
||||
) -> Result<write::UnitEntryId, Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let die_id = comp_unit.add(parent_id, gimli::DW_TAG_structure_type);
|
||||
let die = comp_unit.get_mut(die_id);
|
||||
|
||||
let name = format!(
|
||||
"WebAssemblyPtrWrapper<{}>",
|
||||
get_base_type_name(entry, unit, context)?
|
||||
);
|
||||
die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add(name.as_str())),
|
||||
);
|
||||
die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(4));
|
||||
|
||||
let p_die_id = comp_unit.add(die_id, gimli::DW_TAG_template_type_parameter);
|
||||
let p_die = comp_unit.get_mut(p_die_id);
|
||||
p_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("T")),
|
||||
);
|
||||
p_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(wp_die_id),
|
||||
);
|
||||
match entry.attr_value(gimli::DW_AT_type)? {
|
||||
Some(AttributeValue::UnitRef(ref offset)) => {
|
||||
pending_die_refs.push((p_die_id, gimli::DW_AT_type, *offset))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let m_die_id = comp_unit.add(die_id, gimli::DW_TAG_member);
|
||||
let m_die = comp_unit.get_mut(m_die_id);
|
||||
m_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("__ptr")),
|
||||
);
|
||||
m_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(wp_die_id),
|
||||
);
|
||||
m_die.set(
|
||||
gimli::DW_AT_data_member_location,
|
||||
write::AttributeValue::Data1(0),
|
||||
);
|
||||
Ok(die_id)
|
||||
}
|
||||
|
||||
pub(crate) fn clone_unit<'a, R>(
|
||||
unit: Unit<R, R::Offset>,
|
||||
context: &DebugInputContext<R>,
|
||||
addr_tr: &'a AddressTransform,
|
||||
value_ranges: &'a ValueLabelsRanges,
|
||||
out_encoding: gimli::Encoding,
|
||||
module_info: &ModuleVmctxInfo,
|
||||
out_units: &mut write::UnitTable,
|
||||
out_strings: &mut write::StringTable,
|
||||
translated: &mut HashSet<u32>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
R: Reader,
|
||||
{
|
||||
let mut die_ref_map = HashMap::new();
|
||||
let mut pending_die_refs = Vec::new();
|
||||
let mut stack = Vec::new();
|
||||
|
||||
// Iterate over all of this compilation unit's entries.
|
||||
let mut entries = unit.entries();
|
||||
let (mut comp_unit, file_map, 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) = clone_line_program(
|
||||
&unit,
|
||||
entry,
|
||||
addr_tr,
|
||||
out_encoding,
|
||||
context.debug_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 {
|
||||
// FIXME? return Err(TransformError("No low_pc for unit header").into());
|
||||
0
|
||||
};
|
||||
|
||||
clone_die_attributes(
|
||||
entry,
|
||||
context,
|
||||
addr_tr,
|
||||
None,
|
||||
unit.encoding(),
|
||||
comp_unit,
|
||||
root_id,
|
||||
None,
|
||||
None,
|
||||
cu_low_pc,
|
||||
out_strings,
|
||||
&die_ref_map,
|
||||
&mut pending_die_refs,
|
||||
FileAttributeContext::Root(Some(debug_line_offset)),
|
||||
)?;
|
||||
|
||||
let (wp_die_id, vmctx_die_id) =
|
||||
add_internal_types(comp_unit, root_id, out_strings, module_info);
|
||||
|
||||
stack.push(root_id);
|
||||
(comp_unit, file_map, cu_low_pc, wp_die_id, vmctx_die_id)
|
||||
} else {
|
||||
return Err(TransformError("Unexpected unit header").into());
|
||||
}
|
||||
} else {
|
||||
return Ok(()); // 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(
|
||||
entry,
|
||||
context,
|
||||
unit.encoding(),
|
||||
addr_tr,
|
||||
cu_low_pc,
|
||||
)?;
|
||||
if let RangeInfoBuilder::Function(func_index) = range_builder {
|
||||
if let Some(frame_info) =
|
||||
get_function_frame_info(module_info, func_index, value_ranges)
|
||||
{
|
||||
current_value_range.push(new_stack_len, frame_info);
|
||||
}
|
||||
translated.insert(func_index.index() as u32);
|
||||
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(entry, context, unit.encoding(), 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 {
|
||||
// Wrap pointer types.
|
||||
// TODO reference types?
|
||||
let die_id = replace_pointer_type(
|
||||
*parent,
|
||||
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(
|
||||
entry,
|
||||
context,
|
||||
addr_tr,
|
||||
current_value_range.top(),
|
||||
unit.encoding(),
|
||||
&mut comp_unit,
|
||||
die_id,
|
||||
range_builder,
|
||||
current_scope_ranges.top(),
|
||||
cu_low_pc,
|
||||
out_strings,
|
||||
&die_ref_map,
|
||||
&mut pending_die_refs,
|
||||
FileAttributeContext::Children(&file_map, current_frame_base.top()),
|
||||
)?;
|
||||
|
||||
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().expect("range"),
|
||||
out_strings,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
for (die_id, attr_name, offset) in pending_die_refs {
|
||||
let die = comp_unit.get_mut(die_id);
|
||||
if let Some(unit_id) = die_ref_map.get(&offset) {
|
||||
die.set(attr_name, write::AttributeValue::ThisUnitEntryRef(*unit_id));
|
||||
} else {
|
||||
// TODO check why loosing DIEs
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
172
lib/debug-writer/src/transform/utils.rs
Normal file
172
lib/debug-writer/src/transform/utils.rs
Normal file
@ -0,0 +1,172 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project.
|
||||
// It was copied at revision `cc6e8e1af25e5f9b64e183970d50f62c8338f259`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
use super::address_transform::AddressTransform;
|
||||
use super::expression::{CompiledExpression, FunctionFrameInfo};
|
||||
use anyhow::Error;
|
||||
// TODO: review
|
||||
use wasmer_runtime_core::types::FuncIndex;
|
||||
use gimli::write;
|
||||
use wasmer_runtime_core::{module::ModuleInfo, state::CodeVersion};
|
||||
// TODO: ValueLabelsRanges
|
||||
|
||||
pub(crate) fn add_internal_types(
|
||||
comp_unit: &mut write::Unit,
|
||||
root_id: write::UnitEntryId,
|
||||
out_strings: &mut write::StringTable,
|
||||
module_info: &ModuleInfo,
|
||||
) -> (write::UnitEntryId, write::UnitEntryId) {
|
||||
let wp_die_id = comp_unit.add(root_id, gimli::DW_TAG_base_type);
|
||||
let wp_die = comp_unit.get_mut(wp_die_id);
|
||||
wp_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("WebAssemblyPtr")),
|
||||
);
|
||||
wp_die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(4));
|
||||
wp_die.set(
|
||||
gimli::DW_AT_encoding,
|
||||
write::AttributeValue::Encoding(gimli::DW_ATE_unsigned),
|
||||
);
|
||||
|
||||
let memory_byte_die_id = comp_unit.add(root_id, gimli::DW_TAG_base_type);
|
||||
let memory_byte_die = comp_unit.get_mut(memory_byte_die_id);
|
||||
memory_byte_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("u8")),
|
||||
);
|
||||
memory_byte_die.set(
|
||||
gimli::DW_AT_encoding,
|
||||
write::AttributeValue::Encoding(gimli::DW_ATE_unsigned),
|
||||
);
|
||||
memory_byte_die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(1));
|
||||
|
||||
let memory_bytes_die_id = comp_unit.add(root_id, gimli::DW_TAG_pointer_type);
|
||||
let memory_bytes_die = comp_unit.get_mut(memory_bytes_die_id);
|
||||
memory_bytes_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("u8*")),
|
||||
);
|
||||
memory_bytes_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(memory_byte_die_id),
|
||||
);
|
||||
|
||||
let memory_offset = unimplemented!("TODO");
|
||||
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("WasmerVMContext")),
|
||||
);
|
||||
vmctx_die.set(
|
||||
gimli::DW_AT_byte_size,
|
||||
write::AttributeValue::Data4(memory_offset as u32 + 8),
|
||||
);
|
||||
|
||||
let m_die_id = comp_unit.add(vmctx_die_id, gimli::DW_TAG_member);
|
||||
let m_die = comp_unit.get_mut(m_die_id);
|
||||
m_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("memory")),
|
||||
);
|
||||
m_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(memory_bytes_die_id),
|
||||
);
|
||||
m_die.set(
|
||||
gimli::DW_AT_data_member_location,
|
||||
write::AttributeValue::Udata(memory_offset as u64),
|
||||
);
|
||||
|
||||
let vmctx_ptr_die_id = comp_unit.add(root_id, gimli::DW_TAG_pointer_type);
|
||||
let vmctx_ptr_die = comp_unit.get_mut(vmctx_ptr_die_id);
|
||||
vmctx_ptr_die.set(
|
||||
gimli::DW_AT_name,
|
||||
write::AttributeValue::StringRef(out_strings.add("WasmerVMContext*")),
|
||||
);
|
||||
vmctx_ptr_die.set(
|
||||
gimli::DW_AT_type,
|
||||
write::AttributeValue::ThisUnitEntryRef(vmctx_die_id),
|
||||
);
|
||||
|
||||
(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,
|
||||
) -> Result<(), Error> {
|
||||
let loc = {
|
||||
let endian = gimli::RunTimeEndian::Little;
|
||||
|
||||
let expr = CompiledExpression::vmctx();
|
||||
let mut locs = Vec::new();
|
||||
for (begin, length, data) in
|
||||
expr.build_with_locals(scope_ranges, addr_tr, frame_info, endian)
|
||||
{
|
||||
locs.push(write::Location::StartLength {
|
||||
begin,
|
||||
length,
|
||||
data,
|
||||
});
|
||||
}
|
||||
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::ThisUnitEntryRef(vmctx_die_id),
|
||||
);
|
||||
var_die.set(gimli::DW_AT_location, loc);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_function_frame_info<'a, 'b, 'c>(
|
||||
module_info: &'b ModuleInfo,
|
||||
func_index: FuncIndex,
|
||||
value_ranges: &'c ValueLabelsRanges,
|
||||
) -> Option<FunctionFrameInfo<'a>>
|
||||
where
|
||||
'b: 'a,
|
||||
'c: 'a,
|
||||
{
|
||||
if let Some(value_ranges) = value_ranges.get(func_index) {
|
||||
let frame_info = FunctionFrameInfo {
|
||||
value_ranges,
|
||||
memory_offset: module_info.memory_offset,
|
||||
stack_slots: &module_info.stack_slots[func_index],
|
||||
};
|
||||
Some(frame_info)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
163
lib/debug-writer/src/write_debug_info.rs
Normal file
163
lib/debug-writer/src/write_debug_info.rs
Normal file
@ -0,0 +1,163 @@
|
||||
// Copyright 2019 WasmTime Project Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This file is from the WasmTime project. It reads DWARF info from a Wasm module.
|
||||
// It was copied at revision `ea73d4fa91399349fd8d42e833363a0b1cad9f1c`.
|
||||
//
|
||||
// Changes to this file are copyright of Wasmer inc. unless otherwise indicated
|
||||
// and are licensed under the Wasmer project's license.
|
||||
|
||||
use faerie::artifact::{Decl, SectionKind};
|
||||
use faerie::*;
|
||||
use gimli::write::{Address, Dwarf, EndianVec, Result, Sections, Writer};
|
||||
use gimli::{RunTimeEndian, SectionId};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DebugReloc {
|
||||
offset: u32,
|
||||
size: u8,
|
||||
name: String,
|
||||
addend: i64,
|
||||
}
|
||||
|
||||
pub enum ResolvedSymbol {
|
||||
PhysicalAddress(u64),
|
||||
Reloc { name: String, addend: i64 },
|
||||
}
|
||||
|
||||
pub trait SymbolResolver {
|
||||
fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol;
|
||||
}
|
||||
|
||||
pub fn emit_dwarf(
|
||||
artifact: &mut Artifact,
|
||||
mut dwarf: Dwarf,
|
||||
symbol_resolver: &dyn SymbolResolver,
|
||||
) -> anyhow::Result<()> {
|
||||
let endian = RunTimeEndian::Little;
|
||||
|
||||
let mut sections = Sections::new(WriterRelocate::new(endian, symbol_resolver));
|
||||
dwarf.write(&mut sections)?;
|
||||
sections.for_each_mut(|id, s| -> anyhow::Result<()> {
|
||||
artifact.declare_with(
|
||||
id.name(),
|
||||
Decl::section(SectionKind::Debug),
|
||||
s.writer.take(),
|
||||
)
|
||||
})?;
|
||||
sections.for_each_mut(|id, s| -> anyhow::Result<()> {
|
||||
for reloc in &s.relocs {
|
||||
artifact.link_with(
|
||||
faerie::Link {
|
||||
from: id.name(),
|
||||
to: &reloc.name,
|
||||
at: u64::from(reloc.offset),
|
||||
},
|
||||
faerie::Reloc::Debug {
|
||||
size: reloc.size,
|
||||
addend: reloc.addend as i32,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WriterRelocate<'a> {
|
||||
relocs: Vec<DebugReloc>,
|
||||
writer: EndianVec<RunTimeEndian>,
|
||||
symbol_resolver: &'a dyn SymbolResolver,
|
||||
}
|
||||
|
||||
impl<'a> WriterRelocate<'a> {
|
||||
pub fn new(endian: RunTimeEndian, symbol_resolver: &'a dyn SymbolResolver) -> Self {
|
||||
WriterRelocate {
|
||||
relocs: Vec::new(),
|
||||
writer: EndianVec::new(endian),
|
||||
symbol_resolver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Writer for WriterRelocate<'a> {
|
||||
type Endian = RunTimeEndian;
|
||||
|
||||
fn endian(&self) -> Self::Endian {
|
||||
self.writer.endian()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.writer.len()
|
||||
}
|
||||
|
||||
fn write(&mut self, bytes: &[u8]) -> Result<()> {
|
||||
self.writer.write(bytes)
|
||||
}
|
||||
|
||||
fn write_at(&mut self, offset: usize, bytes: &[u8]) -> Result<()> {
|
||||
self.writer.write_at(offset, bytes)
|
||||
}
|
||||
|
||||
fn write_address(&mut self, address: Address, size: u8) -> Result<()> {
|
||||
match address {
|
||||
Address::Constant(val) => self.write_udata(val, size),
|
||||
Address::Symbol { symbol, addend } => {
|
||||
match self.symbol_resolver.resolve_symbol(symbol, addend as i64) {
|
||||
ResolvedSymbol::PhysicalAddress(addr) => self.write_udata(addr, size),
|
||||
ResolvedSymbol::Reloc { name, addend } => {
|
||||
let offset = self.len() as u64;
|
||||
self.relocs.push(DebugReloc {
|
||||
offset: offset as u32,
|
||||
size,
|
||||
name,
|
||||
addend,
|
||||
});
|
||||
self.write_udata(addend as u64, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_offset(&mut self, val: usize, section: SectionId, size: u8) -> Result<()> {
|
||||
let offset = self.len() as u32;
|
||||
let name = section.name().to_string();
|
||||
self.relocs.push(DebugReloc {
|
||||
offset,
|
||||
size,
|
||||
name,
|
||||
addend: val as i64,
|
||||
});
|
||||
self.write_udata(val as u64, size)
|
||||
}
|
||||
|
||||
fn write_offset_at(
|
||||
&mut self,
|
||||
offset: usize,
|
||||
val: usize,
|
||||
section: SectionId,
|
||||
size: u8,
|
||||
) -> Result<()> {
|
||||
let name = section.name().to_string();
|
||||
self.relocs.push(DebugReloc {
|
||||
offset: offset as u32,
|
||||
size,
|
||||
name,
|
||||
addend: val as i64,
|
||||
});
|
||||
self.write_udata_at(offset, val as u64, size)
|
||||
}
|
||||
}
|
@ -250,6 +250,7 @@ impl<
|
||||
cache_gen,
|
||||
runnable_module: Arc::new(Box::new(exec_context)),
|
||||
info: Arc::try_unwrap(info).unwrap().into_inner().unwrap(),
|
||||
debug_info: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -20,12 +20,18 @@ use indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct DebugInfo {
|
||||
|
||||
}
|
||||
|
||||
/// This is used to instantiate a new WebAssembly module.
|
||||
#[doc(hidden)]
|
||||
pub struct ModuleInner {
|
||||
pub runnable_module: Arc<Box<dyn RunnableModule>>,
|
||||
pub cache_gen: Box<dyn CacheGen>,
|
||||
pub info: ModuleInfo,
|
||||
pub debug_info: Option<DebugInfo>,
|
||||
}
|
||||
|
||||
/// Container for module data including memories, globals, tables, imports, and exports.
|
||||
|
Loading…
x
Reference in New Issue
Block a user