Add debug prototype 2 (fork of wasmtime-debug)

This commit is contained in:
Mark McCaskey 2020-01-31 12:42:09 -08:00
parent 589a99452c
commit 800b2a42cc
20 changed files with 4296 additions and 14 deletions

135
Cargo.lock generated
View File

@ -18,6 +18,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "anyhow"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.5" version = "0.3.5"
@ -228,8 +234,8 @@ dependencies = [
"cranelift-codegen-shared", "cranelift-codegen-shared",
"cranelift-entity", "cranelift-entity",
"log", "log",
"smallvec", "smallvec 1.1.0",
"target-lexicon", "target-lexicon 0.9.0",
"thiserror", "thiserror",
] ]
@ -263,7 +269,7 @@ checksum = "21398a0bc6ba389ea86964ac4a495426dd61080f2ddd306184777a8560fe9976"
dependencies = [ dependencies = [
"cranelift-codegen", "cranelift-codegen",
"raw-cpuid", "raw-cpuid",
"target-lexicon", "target-lexicon 0.9.0",
] ]
[[package]] [[package]]
@ -450,6 +456,28 @@ dependencies = [
"libc", "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]] [[package]]
name = "fern" name = "fern"
version = "0.5.9" version = "0.5.9"
@ -514,6 +542,20 @@ dependencies = [
"syn 1.0.11", "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]] [[package]]
name = "glob" name = "glob"
version = "0.2.11" version = "0.2.11"
@ -534,7 +576,18 @@ checksum = "e3fa261d919c1ae9d1e4533c4a2f99e10938603c4208d56c05bec7a872b661b0"
dependencies = [ dependencies = [
"log", "log",
"plain", "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]] [[package]]
@ -870,7 +923,7 @@ dependencies = [
"cloudabi", "cloudabi",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec 1.1.0",
"winapi", "winapi",
] ]
@ -1255,7 +1308,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383" checksum = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383"
dependencies = [ dependencies = [
"rustc_version", "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]] [[package]]
@ -1269,6 +1331,17 @@ dependencies = [
"syn 0.15.44", "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]] [[package]]
name = "sdl2" name = "sdl2"
version = "0.32.2" version = "0.32.2"
@ -1358,6 +1431,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "smallvec"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
dependencies = [
"maybe-uninit",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.1.0" version = "1.1.0"
@ -1370,6 +1452,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 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]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
@ -1439,6 +1530,12 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4" checksum = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4"
[[package]]
name = "target-lexicon"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.1.0" version = "3.1.0"
@ -1643,6 +1740,7 @@ dependencies = [
"typetag", "typetag",
"wabt", "wabt",
"wasmer-clif-backend", "wasmer-clif-backend",
"wasmer-debug-writer",
"wasmer-dev-utils", "wasmer-dev-utils",
"wasmer-emscripten", "wasmer-emscripten",
"wasmer-emscripten-tests", "wasmer-emscripten-tests",
@ -1673,7 +1771,7 @@ dependencies = [
"serde-bench", "serde-bench",
"serde_bytes", "serde_bytes",
"serde_derive", "serde_derive",
"target-lexicon", "target-lexicon 0.9.0",
"wasmer-clif-fork-frontend", "wasmer-clif-fork-frontend",
"wasmer-clif-fork-wasm", "wasmer-clif-fork-wasm",
"wasmer-runtime-core", "wasmer-runtime-core",
@ -1690,8 +1788,8 @@ checksum = "6d2e13201ef9ef527ad30a6bf1b08e3e024a40cf2731f393d80375dc88506207"
dependencies = [ dependencies = [
"cranelift-codegen", "cranelift-codegen",
"log", "log",
"smallvec", "smallvec 1.1.0",
"target-lexicon", "target-lexicon 0.9.0",
] ]
[[package]] [[package]]
@ -1708,6 +1806,17 @@ dependencies = [
"wasmparser", "wasmparser",
] ]
[[package]]
name = "wasmer-debug-writer"
version = "0.13.1"
dependencies = [
"faerie",
"gimli",
"target-lexicon 0.10.0",
"wasmer-runtime-core",
"wasmparser",
]
[[package]] [[package]]
name = "wasmer-dev-utils" name = "wasmer-dev-utils"
version = "0.13.1" version = "0.13.1"
@ -1756,7 +1865,7 @@ version = "0.13.1"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"cc", "cc",
"goblin", "goblin 0.0.24",
"inkwell", "inkwell",
"lazy_static", "lazy_static",
"libc", "libc",
@ -1764,7 +1873,7 @@ dependencies = [
"regex", "regex",
"rustc_version", "rustc_version",
"semver", "semver",
"smallvec", "smallvec 0.6.13",
"wabt", "wabt",
"wasmer-runtime-core", "wasmer-runtime-core",
"wasmparser", "wasmparser",
@ -1851,7 +1960,7 @@ dependencies = [
"serde-bench", "serde-bench",
"serde_bytes", "serde_bytes",
"serde_derive", "serde_derive",
"smallvec", "smallvec 0.6.13",
"wasmparser", "wasmparser",
"winapi", "winapi",
] ]
@ -1880,7 +1989,7 @@ dependencies = [
"nix", "nix",
"serde", "serde",
"serde_derive", "serde_derive",
"smallvec", "smallvec 0.6.13",
"wasmer-runtime-core", "wasmer-runtime-core",
] ]

View File

@ -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-middleware-common-tests = { path = "lib/middleware-common-tests", optional = true }
wasmer-emscripten-tests = { path = "lib/emscripten-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-wasi-experimental-io-devices = { path = "lib/wasi-experimental-io-devices", optional = true }
wasmer-debug-writer = { path = "lib/debug-writer", optional = true }
[workspace] [workspace]
members = [ members = [
@ -64,6 +65,7 @@ members = [
"lib/wasi-tests", "lib/wasi-tests",
"lib/emscripten-tests", "lib/emscripten-tests",
"lib/middleware-common-tests", "lib/middleware-common-tests",
"lib/debug-writer",
"examples/parallel", "examples/parallel",
"examples/plugin-for-example", "examples/plugin-for-example",
"examples/parallel-guest", "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 typetag = "0.1" # used by the plugin example
[features] [features]
default = ["fast-tests", "wasi", "backend-cranelift", "wabt"] default = ["fast-tests", "wasi", "backend-cranelift", "wabt", "wasmer-debug-writer"]
"loader-kernel" = ["wasmer-kernel-loader"] "loader-kernel" = ["wasmer-kernel-loader"]
debug = ["fern", "log/max_level_debug", "log/release_max_level_debug"] debug = ["fern", "log/max_level_debug", "log/release_max_level_debug"]
trace = ["fern", "log/max_level_trace", "log/release_max_level_trace"] trace = ["fern", "log/max_level_trace", "log/release_max_level_trace"]

View File

@ -45,6 +45,7 @@ impl Module {
runnable_module: Arc::new(Box::new(runnable_module)), runnable_module: Arc::new(Box::new(runnable_module)),
cache_gen, cache_gen,
info, info,
debug_info: None,
}) })
} }
} }

View 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"

View 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
View 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
View 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
}

View 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(),
},
}
}

View 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
);
}
}

View 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"),
})
}

View 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(&reg) {
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);
}
}

View 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())
}
}

View 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,
})
}

View 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!();
}
}
}

View 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(())
}

View 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(())
}

View 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
}
}

View 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)
}
}

View File

@ -250,6 +250,7 @@ impl<
cache_gen, cache_gen,
runnable_module: Arc::new(Box::new(exec_context)), runnable_module: Arc::new(Box::new(exec_context)),
info: Arc::try_unwrap(info).unwrap().into_inner().unwrap(), info: Arc::try_unwrap(info).unwrap().into_inner().unwrap(),
debug_info: None,
}) })
} }

View File

@ -20,12 +20,18 @@ use indexmap::IndexMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
#[doc(hidden)]
pub struct DebugInfo {
}
/// This is used to instantiate a new WebAssembly module. /// This is used to instantiate a new WebAssembly module.
#[doc(hidden)] #[doc(hidden)]
pub struct ModuleInner { pub struct ModuleInner {
pub runnable_module: Arc<Box<dyn RunnableModule>>, pub runnable_module: Arc<Box<dyn RunnableModule>>,
pub cache_gen: Box<dyn CacheGen>, pub cache_gen: Box<dyn CacheGen>,
pub info: ModuleInfo, pub info: ModuleInfo,
pub debug_info: Option<DebugInfo>,
} }
/// Container for module data including memories, globals, tables, imports, and exports. /// Container for module data including memories, globals, tables, imports, and exports.