Initial reorg.

This is largely the same as #305, but updated for the current tree.
This commit is contained in:
Dan Gohman
2019-11-07 17:11:06 -08:00
parent 2c69546a24
commit 22641de629
351 changed files with 52 additions and 52 deletions

3
crates/debug/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
target/
**/*.rs.bk
Cargo.lock

34
crates/debug/Cargo.toml Normal file
View File

@@ -0,0 +1,34 @@
[package]
name = "wasmtime-debug"
version = "0.2.0"
authors = ["The Wasmtime Project Developers"]
description = "Debug utils for WebAsssembly code in Cranelift"
repository = "https://github.com/CraneStation/wasmtime"
documentation = "https://docs.rs/wasmtime-debug/"
categories = ["wasm"]
keywords = ["webassembly", "wasm", "debuginfo"]
license = "Apache-2.0 WITH LLVM-exception"
readme = "README.md"
edition = "2018"
[dependencies]
gimli = "0.19.0"
wasmparser = "0.39.2"
cranelift-codegen = { version = "0.49", features = ["enable-serde"] }
cranelift-entity = { version = "0.49", features = ["enable-serde"] }
cranelift-wasm = { version = "0.49", features = ["enable-serde"] }
faerie = "0.12.0"
wasmtime-environ = { path = "../environ", default-features = false }
target-lexicon = { version = "0.9.0", default-features = false }
failure = { version = "0.1.3", default-features = false }
hashbrown = { version = "0.6.0", optional = true }
thiserror = "1.0.4"
[features]
default = ["std"]
std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std"]
core = ["hashbrown/nightly", "cranelift-codegen/core", "cranelift-wasm/core"]
[badges]
maintenance = { status = "experimental" }
travis-ci = { repository = "CraneStation/wasmtime" }

220
crates/debug/LICENSE Normal file
View File

@@ -0,0 +1,220 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

4
crates/debug/README.md Normal file
View File

@@ -0,0 +1,4 @@
This is the `wasmtime-debug` crate, which provides functionality to
read, transform, and write DWARF section.
[`wasmtime-debug`]: https://crates.io/crates/wasmtime-debug

232
crates/debug/src/gc.rs Normal file
View File

@@ -0,0 +1,232 @@
use crate::transform::AddressTransform;
use crate::{HashMap, HashSet};
use alloc::vec::Vec;
use gimli::constants;
use gimli::read;
use gimli::{Reader, UnitSectionOffset};
#[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 crate::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(())
}

192
crates/debug/src/lib.rs Normal file
View File

@@ -0,0 +1,192 @@
//! Debug utils for WebAssembly using Cranelift.
#![allow(clippy::cast_ptr_alignment)]
use alloc::string::String;
use alloc::vec::Vec;
use cranelift_codegen::isa::TargetFrontendConfig;
use faerie::{Artifact, Decl};
use failure::Error;
use target_lexicon::{BinaryFormat, Triple};
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
#[cfg(not(feature = "std"))]
use hashbrown::{hash_map, HashMap, HashSet};
#[cfg(feature = "std")]
use std::collections::{hash_map, HashMap, HashSet};
pub use crate::read_debuginfo::{read_debuginfo, DebugInfoData, WasmFileInfo};
pub use crate::transform::transform_dwarf;
pub use crate::write_debuginfo::{emit_dwarf, ResolvedSymbol, SymbolResolver};
mod gc;
mod read_debuginfo;
mod transform;
mod write_debuginfo;
extern crate alloc;
struct FunctionRelocResolver {}
impl SymbolResolver for FunctionRelocResolver {
fn resolve_symbol(&self, symbol: usize, addend: i64) -> ResolvedSymbol {
let name = format!("_wasm_function_{}", symbol);
ResolvedSymbol::Reloc { name, addend }
}
}
pub fn emit_debugsections(
obj: &mut Artifact,
vmctx_info: &ModuleVmctxInfo,
target_config: &TargetFrontendConfig,
debuginfo_data: &DebugInfoData,
at: &ModuleAddressMap,
ranges: &ValueLabelsRanges,
) -> Result<(), Error> {
let resolver = FunctionRelocResolver {};
let dwarf = transform_dwarf(target_config, debuginfo_data, at, vmctx_info, ranges)?;
emit_dwarf(obj, dwarf, &resolver)?;
Ok(())
}
struct ImageRelocResolver<'a> {
func_offsets: &'a Vec<u64>,
}
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)
}
}
pub fn emit_debugsections_image(
triple: Triple,
target_config: &TargetFrontendConfig,
debuginfo_data: &DebugInfoData,
vmctx_info: &ModuleVmctxInfo,
at: &ModuleAddressMap,
ranges: &ValueLabelsRanges,
funcs: &[(*const u8, usize)],
) -> Result<Vec<u8>, Error> {
let func_offsets = &funcs
.iter()
.map(|(ptr, _)| *ptr as u64)
.collect::<Vec<u64>>();
let mut obj = Artifact::new(triple, String::from("module"));
let resolver = ImageRelocResolver { func_offsets };
let dwarf = transform_dwarf(target_config, debuginfo_data, at, vmctx_info, ranges)?;
// Assuming all functions in the same code block, looking min/max of its range.
assert!(funcs.len() > 0);
let mut segment_body: (usize, usize) = (!0, 0);
for (body_ptr, body_len) in funcs {
segment_body.0 = ::core::cmp::min(segment_body.0, *body_ptr as usize);
segment_body.1 = ::core::cmp::max(segment_body.1, *body_ptr as usize + body_len);
}
let segment_body = (segment_body.0 as *const u8, segment_body.1 - segment_body.0);
let body = unsafe { ::core::slice::from_raw_parts(segment_body.0, segment_body.1) };
obj.declare_with("all", Decl::function(), body.to_vec())?;
emit_dwarf(&mut obj, dwarf, &resolver)?;
// LLDB is too "magical" about mach-o, generating elf
let mut bytes = obj.emit_as(BinaryFormat::Elf)?;
// elf is still missing details...
convert_faerie_elf_to_loadable_file(&mut bytes, segment_body.0);
// let mut file = ::std::fs::File::create(::std::path::Path::new("test.o")).expect("file");
// ::std::io::Write::write(&mut file, &bytes).expect("write");
Ok(bytes)
}
fn convert_faerie_elf_to_loadable_file(bytes: &mut Vec<u8>, code_ptr: *const u8) {
use std::ffi::CStr;
use std::os::raw::c_char;
assert!(
bytes[0x4] == 2 && bytes[0x5] == 1,
"bits and endianess in .ELF"
);
let e_phoff = unsafe { *(bytes.as_ptr().offset(0x20) as *const u64) };
let e_phnum = unsafe { *(bytes.as_ptr().offset(0x38) as *const u16) };
assert!(
e_phoff == 0 && e_phnum == 0,
"program header table is empty"
);
let e_phentsize = unsafe { *(bytes.as_ptr().offset(0x36) as *const u16) };
assert!(e_phentsize == 0x38, "size of ph");
let e_shentsize = unsafe { *(bytes.as_ptr().offset(0x3A) as *const u16) };
assert!(e_shentsize == 0x40, "size of sh");
let e_shoff = unsafe { *(bytes.as_ptr().offset(0x28) as *const u64) };
let e_shnum = unsafe { *(bytes.as_ptr().offset(0x3C) as *const u16) };
let mut shstrtab_off = 0;
let mut segment = None;
for i in 0..e_shnum {
let off = e_shoff as isize + i as isize * e_shentsize as isize;
let sh_type = unsafe { *(bytes.as_ptr().offset(off + 0x4) as *const u32) };
if sh_type == /* SHT_SYMTAB */ 3 {
shstrtab_off = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
}
if sh_type != /* SHT_PROGBITS */ 1 {
continue;
}
// It is a SHT_PROGBITS, but we need to check sh_name to ensure it is our function
let sh_name = unsafe {
let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
CStr::from_ptr(
bytes
.as_ptr()
.offset((shstrtab_off + sh_name_off as u64) as isize)
as *const c_char,
)
.to_str()
.expect("name")
};
if sh_name != ".text.all" {
continue;
}
assert!(segment.is_none());
// Functions was added at emit_debugsections_image as .text.all.
// Patch vaddr, and save file location and its size.
unsafe {
*(bytes.as_ptr().offset(off + 0x10) as *mut u64) = code_ptr as u64;
};
let sh_offset = unsafe { *(bytes.as_ptr().offset(off + 0x18) as *const u64) };
let sh_size = unsafe { *(bytes.as_ptr().offset(off + 0x20) as *const u64) };
segment = Some((sh_offset, code_ptr, sh_size));
// Fix name too: cut it to just ".text"
unsafe {
let sh_name_off = *(bytes.as_ptr().offset(off) as *const u32);
bytes[(shstrtab_off + sh_name_off as u64) as usize + ".text".len()] = 0;
}
}
// LLDB wants segment with virtual address set, placing them at the end of ELF.
let ph_off = bytes.len();
if let Some((sh_offset, v_offset, sh_size)) = segment {
let segment = vec![0; 0x38];
unsafe {
*(segment.as_ptr() as *mut u32) = /* PT_LOAD */ 0x1;
*(segment.as_ptr().offset(0x8) as *mut u64) = sh_offset;
*(segment.as_ptr().offset(0x10) as *mut u64) = v_offset as u64;
*(segment.as_ptr().offset(0x18) as *mut u64) = v_offset as u64;
*(segment.as_ptr().offset(0x20) as *mut u64) = sh_size;
*(segment.as_ptr().offset(0x28) as *mut u64) = sh_size;
}
bytes.extend_from_slice(&segment);
} else {
unreachable!();
}
// It is somewhat loadable ELF file at this moment.
// Update e_flags, e_phoff and e_phnum.
unsafe {
*(bytes.as_ptr().offset(0x10) as *mut u16) = /* ET_DYN */ 3;
*(bytes.as_ptr().offset(0x20) as *mut u64) = ph_off as u64;
*(bytes.as_ptr().offset(0x38) as *mut u16) = 1 as u16;
}
}

View File

@@ -0,0 +1,251 @@
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use std::collections::HashMap;
use std::path::PathBuf;
use wasmparser::{self, ModuleReader, SectionCode};
use gimli;
use gimli::{
DebugAbbrev, DebugAddr, DebugInfo, DebugLine, DebugLineStr, DebugLoc, DebugLocLists,
DebugRanges, DebugRngLists, DebugStr, DebugStrOffsets, DebugTypes, EndianSlice, LittleEndian,
LocationLists, RangeLists,
};
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_debuginfo(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,658 @@
use crate::HashMap;
use crate::WasmFileInfo;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::iter::FromIterator;
use cranelift_codegen::ir::SourceLoc;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_wasm::DefinedFuncIndex;
use gimli::write;
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: DefinedFuncIndex,
lookup: FuncLookup,
}
/// Module functions mapping to generated code.
#[derive(Debug)]
pub struct AddressTransform {
map: PrimaryMap<DefinedFuncIndex, FunctionMap>,
func: Vec<(WasmAddress, FuncTransform)>,
}
/// Returns a wasm bytecode offset in the code section from SourceLoc.
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 && 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) != core::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<DefinedFuncIndex, 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<DefinedFuncIndex> {
self.find_func(addr).map(|f| f.index)
}
pub fn translate_raw(&self, addr: u64) -> Option<(DefinedFuncIndex, GeneratedAddress)> {
if addr == 0 {
// It's normally 0 for debug info without the linked code.
return None;
}
if let Some(func) = self.find_func(addr) {
if addr == func.end {
// Clamp last address to the end to extend translation to the end
// of the function.
let map = &self.map[func.index];
return Some((func.index, map.len));
}
let first_result = 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<(DefinedFuncIndex, 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<DefinedFuncIndex, FunctionMap> {
&self.map
}
pub fn func_range(&self, index: DefinedFuncIndex) -> (GeneratedAddress, GeneratedAddress) {
let map = &self.map[index];
(map.offset, map.offset + map.len)
}
pub fn func_source_range(&self, index: DefinedFuncIndex) -> (WasmAddress, WasmAddress) {
let map = &self.map[index];
(map.wasm_start, map.wasm_end)
}
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_debuginfo::WasmFileInfo;
use core::iter::FromIterator;
use cranelift_codegen::ir::SourceLoc;
use cranelift_entity::PrimaryMap;
use gimli::write::Address;
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,294 @@
use crate::HashMap;
use alloc::vec::Vec;
use failure::Error;
use gimli;
use gimli::{AttributeValue, DebugLineOffset, DebugStr, DebuggingInformationEntry, UnitOffset};
use gimli::write;
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};
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,491 @@
use crate::{HashMap, HashSet};
use alloc::vec::Vec;
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 failure::Error;
use gimli::write;
use gimli::{self, Expression, Operation, Reader, ReaderOffset, Register, X86_64};
use super::address_transform::AddressTransform;
#[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,
_ => panic!("{}", 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,
) -> alloc::vec::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,234 @@
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::iter::FromIterator;
use cranelift_entity::EntityRef;
use failure::Error;
use gimli;
use gimli::{DebugLine, DebugLineOffset, DebugStr, DebuggingInformationEntry, LineEncoding, Unit};
use gimli::write;
use super::address_transform::AddressTransform;
use super::attr::clone_attr_string;
use super::{Reader, TransformError};
#[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,119 @@
use crate::gc::build_dependencies;
use crate::DebugInfoData;
use crate::HashSet;
use cranelift_codegen::isa::TargetFrontendConfig;
use failure::Error;
use simulate::generate_simulated_dwarf;
use thiserror::Error;
use wasmtime_environ::{ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
use gimli;
use gimli::{
DebugAddr, DebugAddrBase, DebugLine, DebugStr, LocationLists, RangeLists, UnitSectionOffset,
};
use gimli::write;
pub use address_transform::AddressTransform;
use unit::clone_unit;
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,226 @@
use alloc::vec::Vec;
use cranelift_entity::EntityRef;
use cranelift_wasm::DefinedFuncIndex;
use failure::Error;
use gimli;
use gimli::{AttributeValue, DebuggingInformationEntry, RangeListsOffset};
use gimli::write;
use super::address_transform::AddressTransform;
use super::DebugInputContext;
use super::Reader;
pub(crate) enum RangeInfoBuilder {
Undefined,
Position(u64),
Ranges(Vec<(u64, u64)>),
Function(DefinedFuncIndex),
}
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,372 @@
use crate::read_debuginfo::WasmFileInfo;
pub use crate::read_debuginfo::{DebugInfoData, FunctionMetadata, WasmType};
use crate::{HashMap, HashSet};
use alloc::string::String;
use alloc::vec::Vec;
use cranelift_entity::EntityRef;
use cranelift_wasm::get_vmctx_value_label;
use failure::Error;
use std::path::PathBuf;
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
use gimli::write;
use gimli::{self, LineEncoding};
use super::expression::{CompiledExpression, FunctionFrameInfo};
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
use super::AddressTransform;
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,
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: &ModuleVmctxInfo,
) -> 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 f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
WasmTypesDieRefs {
vmctx: vmctx_die_id,
i32: i32_die_id,
i64: i64_die_id,
f32: f32_die_id,
f64: f64_die_id,
}
}
fn resolve_var_type(
index: usize,
wasm_types: &WasmTypesDieRefs,
func_meta: &FunctionMetadata,
) -> Option<(write::UnitEntryId, bool)> {
let (ty, is_param) = if index < func_meta.params.len() {
(func_meta.params[index], true)
} else {
let mut i = (index - func_meta.params.len()) as u32;
let mut j = 0;
while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
i -= func_meta.locals[j].0;
j += 1;
}
if j >= func_meta.locals.len() {
// Ignore the var index out of bound.
return None;
}
(func_meta.locals[j].1, false)
};
let type_die_id = match ty {
WasmType::I32 => wasm_types.i32,
WasmType::I64 => wasm_types.i64,
WasmType::F32 => wasm_types.f32,
WasmType::F64 => wasm_types.f64,
_ => {
// Ignore unsupported types.
return None;
}
};
Some((type_die_id, is_param))
}
fn generate_vars(
unit: &mut write::Unit,
die_id: write::UnitEntryId,
addr_tr: &AddressTransform,
frame_info: &FunctionFrameInfo,
scope_ranges: &[(u64, u64)],
wasm_types: &WasmTypesDieRefs,
func_meta: &FunctionMetadata,
locals_names: Option<&HashMap<u32, 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,375 @@
use crate::{HashMap, HashSet};
use alloc::string::String;
use alloc::vec::Vec;
use cranelift_entity::EntityRef;
use failure::Error;
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
use gimli;
use gimli::{AttributeValue, DebuggingInformationEntry, Unit, UnitOffset};
use gimli::write;
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};
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!(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!(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!(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!(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,155 @@
use alloc::vec::Vec;
use cranelift_wasm::DefinedFuncIndex;
use failure::Error;
use wasmtime_environ::{ModuleVmctxInfo, ValueLabelsRanges};
use gimli;
use gimli::write;
use super::address_transform::AddressTransform;
use super::expression::{CompiledExpression, FunctionFrameInfo};
pub(crate) fn add_internal_types(
comp_unit: &mut write::Unit,
root_id: write::UnitEntryId,
out_strings: &mut write::StringTable,
module_info: &ModuleVmctxInfo,
) -> (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 = module_info.memory_offset;
let vmctx_die_id = comp_unit.add(root_id, gimli::DW_TAG_structure_type);
let vmctx_die = comp_unit.get_mut(vmctx_die_id);
vmctx_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("WasmtimeVMContext")),
);
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("WasmtimeVMContext*")),
);
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 ModuleVmctxInfo,
func_index: DefinedFuncIndex,
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,147 @@
use alloc::string::String;
use alloc::vec::Vec;
use gimli::write::{Address, Dwarf, EndianVec, Result, Sections, Writer};
use gimli::{RunTimeEndian, SectionId};
use core::result;
use faerie::artifact::{Decl, SectionKind};
use faerie::*;
#[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,
) -> result::Result<(), failure::Error> {
let endian = RunTimeEndian::Little;
let mut sections = Sections::new(WriterRelocate::new(endian, symbol_resolver));
dwarf.write(&mut sections)?;
sections.for_each_mut(|id, s| -> result::Result<(), failure::Error> {
artifact.declare_with(
id.name(),
Decl::section(SectionKind::Debug),
s.writer.take(),
)
})?;
sections.for_each_mut(|id, s| -> result::Result<(), failure::Error> {
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)
}
}