Browse Source

Add initial support for DWARF Fission (#8055)

* add cloning for String attributes

* use into_owned instead of to_vec to avoid a clone if possible.

* runs, but does not substitute

* show vars from c program, start cleanup

* tiday

* resolve conflicts

* remove WASI folder

* Add module_builder

add dwarf_package to state for cache

* move dwarf loading to module_environ.rs

pass the dwarf as binary as low as module_environ.rs

* pass dwarf package rather than add to debug_info

* tidy option/result nested if

* revert some toml and whitespace.

* add features cranelift,winch to module_builder and compute_artifacts

remove some `use`s

* address some feedback

remove unused 'use's

* address some feedback

remove unused 'use's

* move wat feature condition to cover whole method.

* More feedback

Another try at wat feature move

* Another try at wat feature move

* change gimli exemption version

add typed-arena exemption

* add None for c-api

* move `use` to #cfg

* fix another config build

* revert unwanted code deletion

* move inner function closer to use

* revert extra param to Module::new

* workaround object crate bug.

* add missing parameter

* add missing parameter

* Merge remote-tracking branch 'origin/main' into dwarf-att-string

# Conflicts:
#	crates/wasmtime/src/engine.rs
#	crates/wasmtime/src/runtime/module.rs
#	src/common.rs

* remove moduke

* use common gimli version of 28.1

* remove wasm feature, revert gimli version

* remove use of object for wasm dwarf

* remove NativeFile workaround, add feature for dwp loading

* sync winch signature

* revert bench api change

* add dwarf for no cache feature

* put back merge loss of module kind

* remove param from docs

* add dwarf fission lldb test

* simplify and include test source

* clang-format

* address feedback, remove packages

add docs
simplify return type

* remove Default use on ModuleTypesBuilder

* Remove an `unwrap()` and use `if let` instead

* Use `&[u8]` instead of `&Vec<u8>`

* Remove an `unwrap()` and return `None` instead

* Clean up some code in `transform_dwarf`

* Clean up some code in `replace_unit_from_split_dwarf`

* Clean up some code in `split_unit`

* Minor refactorings and documentation in `CodeBuilder`

* Restrict visibility of `dwarf_package_binary`

* Revert supply-chain folder changes

* Fix compile error on nightly

* prtest:full

* prtest:full

* prtest:full

* prtest:full

* prtest:full

* prtest:full

* prtest:full

* prtest:full

* use lldb 15

* prtest:full

* prtest:full

* load dwp when loading wasm bytes with path

* correct source file name

* remove debug

---------

Co-authored-by: Alex Crichton <alex@alexcrichton.com>
pull/8462/head
yowl 7 months ago
committed by GitHub
parent
commit
fb4f4cd300
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      .github/workflows/main.yml
  2. 4
      crates/cranelift/src/compiler.rs
  3. 15
      crates/cranelift/src/debug/gc.rs
  4. 49
      crates/cranelift/src/debug/transform/attr.rs
  5. 53
      crates/cranelift/src/debug/transform/line_program.rs
  6. 156
      crates/cranelift/src/debug/transform/mod.rs
  7. 53
      crates/cranelift/src/debug/transform/unit.rs
  8. 11
      crates/cranelift/src/debug/write_debuginfo.rs
  9. 2
      crates/environ/src/compile/mod.rs
  10. 42
      crates/environ/src/compile/module_artifacts.rs
  11. 17
      crates/environ/src/module_environ.rs
  12. 16
      crates/wasmtime/src/compile.rs
  13. 78
      crates/wasmtime/src/compile/code_builder.rs
  14. 18
      crates/wasmtime/src/compile/runtime.rs
  15. 1
      crates/wasmtime/src/runtime/module.rs
  16. 2
      crates/winch/src/compiler.rs
  17. 48
      tests/all/debug/lldb.rs
  18. 7
      tests/all/debug/testsuite/dwarf_fission.c
  19. BIN
      tests/all/debug/testsuite/dwarf_fission.dwp
  20. BIN
      tests/all/debug/testsuite/dwarf_fission.wasm

6
.github/workflows/main.yml

@ -635,11 +635,15 @@ jobs:
# Test debug (DWARF) related functionality.
- run: |
sudo apt-get update && sudo apt-get install -y gdb lldb llvm
sudo apt-get update && sudo apt-get install -y gdb lldb-15 llvm
# woraround for https://bugs.launchpad.net/ubuntu/+source/llvm-defaults/+bug/1972855
sudo mkdir -p /usr/lib/local/lib/python3.10/dist-packages/lldb
sudo ln -s /usr/lib/llvm-15/lib/python3.10/dist-packages/lldb/* /usr/lib/python3/dist-packages/lldb/
cargo test test_debug_dwarf -- --ignored --test-threads 1
if: matrix.os == 'ubuntu-latest' && matrix.target == ''&& needs.determine.outputs.run-full
env:
RUST_BACKTRACE: 1
LLDB: lldb-15 # override default version, 14
# NB: the test job here is explicitly lacking in cancellation of this run if
# something goes wrong. These take the longest anyway and otherwise if

4
crates/cranelift/src/compiler.rs

@ -536,6 +536,8 @@ impl wasmtime_environ::Compiler for Compiler {
obj: &mut Object<'_>,
translation: &ModuleTranslation<'_>,
funcs: &PrimaryMap<DefinedFuncIndex, (SymbolId, &(dyn Any + Send))>,
dwarf_package_bytes: Option<&[u8]>,
tunables: &Tunables,
) -> Result<()> {
let ofs = VMOffsets::new(
self.isa
@ -578,6 +580,8 @@ impl wasmtime_environ::Compiler for Compiler {
&translation.debuginfo,
&functions_info,
&memory_offset,
dwarf_package_bytes,
tunables,
)
.with_context(|| "failed to emit DWARF debug information")?;

15
crates/cranelift/src/debug/gc.rs

@ -67,12 +67,13 @@ impl Dependencies {
pub fn build_dependencies<R: Reader<Offset = usize>>(
dwarf: &read::Dwarf<R>,
dwp: &Option<read::DwarfPackage<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)?;
build_unit_dependencies(unit, dwarf, dwp, at, &mut deps)?;
}
Ok(deps)
}
@ -80,6 +81,7 @@ pub fn build_dependencies<R: Reader<Offset = usize>>(
fn build_unit_dependencies<R: Reader<Offset = usize>>(
header: read::UnitHeader<R>,
dwarf: &read::Dwarf<R>,
dwp: &Option<read::DwarfPackage<R>>,
at: &AddressTransform,
deps: &mut Dependencies,
) -> read::Result<()> {
@ -87,6 +89,17 @@ fn build_unit_dependencies<R: Reader<Offset = usize>>(
let mut tree = unit.entries_tree(None)?;
let root = tree.root()?;
build_die_dependencies(root, dwarf, &unit, at, deps)?;
if let Some(dwarf_package) = dwp {
if let Some(dwo_id) = unit.dwo_id {
if let Some(cu) = dwarf_package.find_cu(dwo_id, dwarf)? {
if let Some(unit_header) = cu.debug_info.units().next()? {
build_unit_dependencies(unit_header, &cu, &None, at, deps)?;
}
}
}
}
Ok(())
}

49
crates/cranelift/src/debug/transform/attr.rs

@ -5,10 +5,7 @@ use super::refs::{PendingDebugInfoRefs, PendingUnitRefs};
use super::{DebugInputContext, Reader, TransformError};
use anyhow::{bail, Error};
use cranelift_codegen::isa::TargetIsa;
use gimli::{
write, AttributeValue, DebugLineOffset, DebugLineStr, DebugStr, DebugStrOffsets,
DebuggingInformationEntry, Unit,
};
use gimli::{write, AttributeValue, DebugLineOffset, DebuggingInformationEntry, Unit};
#[derive(Debug)]
pub(crate) enum FileAttributeContext<'a> {
@ -124,17 +121,11 @@ where
return Err(TransformError("unexpected file index attribute").into());
}
}
AttributeValue::DebugStrRef(str_offset) => {
let s = context.debug_str.get_str(str_offset)?.to_slice()?.to_vec();
write::AttributeValue::StringRef(out_strings.add(s))
}
AttributeValue::DebugStrOffsetsIndex(i) => {
let str_offset = context.debug_str_offsets.get_str_offset(
gimli::Format::Dwarf32,
unit.str_offsets_base,
i,
)?;
let s = context.debug_str.get_str(str_offset)?.to_slice()?.to_vec();
AttributeValue::DebugStrRef(_) | AttributeValue::DebugStrOffsetsIndex(_) => {
let s = dwarf
.attr_string(unit, attr.value().clone())?
.to_string_lossy()?
.into_owned();
write::AttributeValue::StringRef(out_strings.add(s))
}
AttributeValue::RangeListsRef(r) => {
@ -308,38 +299,22 @@ pub(crate) fn clone_attr_string<R>(
attr_value: &AttributeValue<R>,
form: gimli::DwForm,
unit: &Unit<R, R::Offset>,
debug_str: &DebugStr<R>,
debug_str_offsets: &DebugStrOffsets<R>,
debug_line_str: &DebugLineStr<R>,
dwarf: &gimli::Dwarf<R>,
out_strings: &mut write::StringTable,
) -> Result<write::LineString, Error>
where
R: Reader,
{
let content = match attr_value {
AttributeValue::DebugStrRef(str_offset) => {
debug_str.get_str(*str_offset)?.to_slice()?.to_vec()
}
AttributeValue::DebugStrOffsetsIndex(i) => {
let str_offset = debug_str_offsets.get_str_offset(
gimli::Format::Dwarf32,
unit.str_offsets_base,
*i,
)?;
debug_str.get_str(str_offset)?.to_slice()?.to_vec()
}
AttributeValue::DebugLineStrRef(str_offset) => {
debug_line_str.get_str(*str_offset)?.to_slice()?.to_vec()
}
AttributeValue::String(b) => b.to_slice()?.to_vec(),
v => bail!("Unexpected attribute value: {:?}", v),
};
let content = dwarf
.attr_string(unit, attr_value.clone())?
.to_string_lossy()?
.into_owned();
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),
gimli::DW_FORM_string => write::LineString::String(content.into()),
_ => bail!("DW_FORM_line_strp or other not supported"),
})
}

53
crates/cranelift/src/debug/transform/line_program.rs

@ -3,7 +3,7 @@ use super::attr::clone_attr_string;
use super::{Reader, TransformError};
use anyhow::{Context, Error};
use gimli::{
write, DebugLine, DebugLineOffset, DebugLineStr, DebugStr, DebugStrOffsets,
write, AttributeValue::DebugLineRef, DebugLine, DebugLineOffset, DebugStr,
DebuggingInformationEntry, LineEncoding, Unit,
};
use wasmtime_environ::{DefinedFuncIndex, EntityRef};
@ -40,48 +40,57 @@ enum ReadLineProgramState {
}
pub(crate) fn clone_line_program<R>(
dwarf: &gimli::Dwarf<R>,
skeleton_dwarf: &gimli::Dwarf<R>,
unit: &Unit<R, R::Offset>,
root: &DebuggingInformationEntry<R>,
skeleton_die: Option<&DebuggingInformationEntry<R>>,
addr_tr: &AddressTransform,
out_encoding: gimli::Encoding,
debug_str: &DebugStr<R>,
debug_str_offsets: &DebugStrOffsets<R>,
debug_line_str: &DebugLineStr<R>,
debug_line: &DebugLine<R>,
out_strings: &mut write::StringTable,
) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>, u64), Error>
where
R: Reader,
{
let offset = match root.attr_value(gimli::DW_AT_stmt_list)? {
Some(gimli::AttributeValue::DebugLineRef(offset)) => offset,
// Where are the "location" attributes
let (location_die, location_dwarf) = match skeleton_die {
Some(die) => (die, skeleton_dwarf),
_ => (root, dwarf),
};
let offset = match location_die.attr_value(gimli::DW_AT_stmt_list)? {
Some(DebugLineRef(offset)) => offset,
Some(gimli::AttributeValue::SecOffset(offset)) => DebugLineOffset(offset),
_ => {
return Err(TransformError("Debug line offset is not found").into());
}
};
let comp_dir = root.attr_value(gimli::DW_AT_comp_dir)?;
let comp_dir = location_die.attr_value(gimli::DW_AT_comp_dir)?;
let comp_name = root.attr_value(gimli::DW_AT_name)?;
let out_comp_dir = match &comp_dir {
Some(comp_dir) => Some(clone_attr_string(
comp_dir,
gimli::DW_FORM_strp,
unit,
debug_str,
debug_str_offsets,
debug_line_str,
location_dwarf,
out_strings,
)?),
None => None,
};
let out_comp_name = clone_attr_string(
comp_name.as_ref().context("missing DW_AT_name attribute")?,
gimli::DW_FORM_strp,
unit,
debug_str,
debug_str_offsets,
debug_line_str,
out_strings,
)?;
let out_comp_name = match comp_name {
Some(_) => clone_attr_string(
comp_name
.as_ref()
.context("failed to read DW_AT_name attribute")?,
gimli::DW_FORM_strp,
unit,
dwarf,
out_strings,
)?,
_ => gimli::write::LineString::String("missing DW_AT_name attribute".into()),
};
let program = debug_line.program(
offset,
@ -114,9 +123,7 @@ where
dir_attr,
gimli::DW_FORM_string,
unit,
debug_str,
debug_str_offsets,
debug_line_str,
location_dwarf,
out_strings,
)?);
dirs.push(dir_id);
@ -132,9 +139,7 @@ where
&file_entry.path_name(),
gimli::DW_FORM_string,
unit,
debug_str,
debug_str_offsets,
debug_line_str,
location_dwarf,
out_strings,
)?,
dir_id,

156
crates/cranelift/src/debug/transform/mod.rs

@ -7,12 +7,12 @@ use crate::CompiledFunctionsMetadata;
use anyhow::Error;
use cranelift_codegen::isa::TargetIsa;
use gimli::{
write, DebugAddr, DebugLine, DebugLineStr, DebugStr, DebugStrOffsets, LocationLists,
RangeLists, UnitSectionOffset,
write, DebugAddr, DebugLine, DebugStr, Dwarf, DwarfPackage, LittleEndian, LocationLists,
RangeLists, Section, Unit, UnitSectionOffset,
};
use std::collections::HashSet;
use std::{collections::HashSet, fmt::Debug};
use thiserror::Error;
use wasmtime_environ::DebugInfoData;
use wasmtime_environ::{DebugInfoData, ModuleTranslation, Tunables};
pub use address_transform::AddressTransform;
@ -26,9 +26,12 @@ mod simulate;
mod unit;
mod utils;
pub(crate) trait Reader: gimli::Reader<Offset = usize> {}
pub(crate) trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where Endian: gimli::Endianity {}
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
Endian: gimli::Endianity + Send + Sync
{
}
#[derive(Error, Debug)]
#[error("Debug info transform error: {0}")]
@ -39,8 +42,6 @@ where
R: Reader,
{
debug_str: &'a DebugStr<R>,
debug_str_offsets: &'a DebugStrOffsets<R>,
debug_line_str: &'a DebugLineStr<R>,
debug_line: &'a DebugLine<R>,
debug_addr: &'a DebugAddr<R>,
rnglists: &'a RangeLists<R>,
@ -48,19 +49,113 @@ where
reachable: &'a HashSet<UnitSectionOffset>,
}
pub fn transform_dwarf(
fn load_dwp<'data>(
translation: ModuleTranslation<'data>,
buffer: &'data [u8],
) -> anyhow::Result<DwarfPackage<gimli::EndianSlice<'data, gimli::LittleEndian>>> {
let endian_slice = gimli::EndianSlice::new(buffer, LittleEndian);
let dwarf_package = DwarfPackage::load(
|id| -> anyhow::Result<_> {
let slice = match id {
gimli::SectionId::DebugAbbrev => {
translation.debuginfo.dwarf.debug_abbrev.reader().slice()
}
gimli::SectionId::DebugInfo => {
translation.debuginfo.dwarf.debug_info.reader().slice()
}
gimli::SectionId::DebugLine => {
translation.debuginfo.dwarf.debug_line.reader().slice()
}
gimli::SectionId::DebugStr => {
translation.debuginfo.dwarf.debug_str.reader().slice()
}
gimli::SectionId::DebugStrOffsets => translation
.debuginfo
.dwarf
.debug_str_offsets
.reader()
.slice(),
gimli::SectionId::DebugLoc => translation.debuginfo.debug_loc.reader().slice(),
gimli::SectionId::DebugLocLists => {
translation.debuginfo.debug_loclists.reader().slice()
}
gimli::SectionId::DebugRngLists => {
translation.debuginfo.debug_rnglists.reader().slice()
}
gimli::SectionId::DebugTypes => {
translation.debuginfo.dwarf.debug_types.reader().slice()
}
gimli::SectionId::DebugCuIndex => {
translation.debuginfo.debug_cu_index.reader().slice()
}
gimli::SectionId::DebugTuIndex => {
translation.debuginfo.debug_tu_index.reader().slice()
}
_ => &buffer,
};
Ok(gimli::EndianSlice::new(slice, gimli::LittleEndian))
},
endian_slice,
)?;
Ok(dwarf_package)
}
/// Attempts to load a DWARF package using the passed bytes.
fn read_dwarf_package_from_bytes<'data>(
dwp_bytes: &'data [u8],
buffer: &'data [u8],
tunables: &Tunables,
) -> Option<DwarfPackage<gimli::EndianSlice<'data, gimli::LittleEndian>>> {
let mut validator = wasmparser::Validator::new();
let parser = wasmparser::Parser::new(0);
let mut types = wasmtime_environ::ModuleTypesBuilder::new(&validator);
let translation =
match wasmtime_environ::ModuleEnvironment::new(tunables, &mut validator, &mut types)
.translate(parser, dwp_bytes)
{
Ok(translation) => translation,
Err(e) => {
log::warn!("failed to parse wasm dwarf package: {e:?}");
return None;
}
};
match load_dwp(translation, buffer) {
Ok(package) => Some(package),
Err(err) => {
log::warn!("Failed to load Dwarf package {}", err);
None
}
}
}
pub fn transform_dwarf<'data>(
isa: &dyn TargetIsa,
di: &DebugInfoData,
funcs: &CompiledFunctionsMetadata,
memory_offset: &ModuleMemoryOffset,
dwarf_package_bytes: Option<&[u8]>,
tunables: &Tunables,
) -> Result<write::Dwarf, Error> {
let addr_tr = AddressTransform::new(funcs, &di.wasm_file);
let reachable = build_dependencies(&di.dwarf, &addr_tr)?.get_reachable();
let buffer = Vec::new();
let dwarf_package = dwarf_package_bytes
.map(
|bytes| -> Option<DwarfPackage<gimli::EndianSlice<'_, gimli::LittleEndian>>> {
read_dwarf_package_from_bytes(bytes, &buffer, tunables)
},
)
.flatten();
let reachable = build_dependencies(&di.dwarf, &dwarf_package, &addr_tr)?.get_reachable();
let context = DebugInputContext {
debug_str: &di.dwarf.debug_str,
debug_str_offsets: &di.dwarf.debug_str_offsets,
debug_line_str: &di.dwarf.debug_line_str,
debug_line: &di.dwarf.debug_line,
debug_addr: &di.dwarf.debug_addr,
rnglists: &di.dwarf.ranges,
@ -84,11 +179,29 @@ pub fn transform_dwarf(
let mut translated = HashSet::new();
let mut iter = di.dwarf.debug_info.units();
while let Some(header) = iter.next().unwrap_or(None) {
let unit = di.dwarf.unit(header)?;
let mut resolved_unit = None;
let mut split_dwarf = None;
if let gimli::UnitType::Skeleton(_dwo_id) = unit.header.type_() {
if let Some(dwarf_package) = &dwarf_package {
if let Some((fused, fused_dwarf)) =
replace_unit_from_split_dwarf(&unit, dwarf_package, &di.dwarf)
{
resolved_unit = Some(fused);
split_dwarf = Some(fused_dwarf);
}
}
}
if let Some((id, ref_map, pending_refs)) = clone_unit(
&di.dwarf,
unit,
&unit,
resolved_unit.as_ref(),
split_dwarf.as_ref(),
&context,
&addr_tr,
funcs,
@ -124,3 +237,20 @@ pub fn transform_dwarf(
strings: out_strings,
})
}
fn replace_unit_from_split_dwarf<'a>(
unit: &'a Unit<gimli::EndianSlice<'a, gimli::LittleEndian>, usize>,
dwp: &DwarfPackage<gimli::EndianSlice<'a, gimli::LittleEndian>>,
parent: &Dwarf<gimli::EndianSlice<'a, gimli::LittleEndian>>,
) -> Option<(
Unit<gimli::EndianSlice<'a, gimli::LittleEndian>, usize>,
Dwarf<gimli::EndianSlice<'a, gimli::LittleEndian>>,
)> {
let dwo_id = unit.dwo_id?;
let split_unit_dwarf = dwp.find_cu(dwo_id, parent).ok()??;
let unit_header = split_unit_dwarf.debug_info.units().next().ok()??;
Some((
split_unit_dwarf.unit(unit_header).unwrap(),
split_unit_dwarf,
))
}

53
crates/cranelift/src/debug/transform/unit.rs

@ -5,7 +5,7 @@ use super::line_program::clone_line_program;
use super::range_info_builder::RangeInfoBuilder;
use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
use super::{DebugInputContext, Reader, TransformError};
use super::{DebugInputContext, Reader};
use crate::debug::ModuleMemoryOffset;
use crate::CompiledFunctionsMetadata;
use anyhow::{Context, Error};
@ -256,7 +256,9 @@ fn is_dead_code<R: Reader>(entry: &DebuggingInformationEntry<R>) -> bool {
pub(crate) fn clone_unit<'a, R>(
dwarf: &gimli::Dwarf<R>,
unit: Unit<R, R::Offset>,
unit: &Unit<R, R::Offset>,
split_unit: Option<&Unit<R, R::Offset>>,
split_dwarf: Option<&gimli::Dwarf<R>>,
context: &DebugInputContext<R>,
addr_tr: &'a AddressTransform,
funcs: &'a CompiledFunctionsMetadata,
@ -275,20 +277,35 @@ where
let mut pending_di_refs = PendingDebugInfoRefs::new();
let mut stack = Vec::new();
let mut program_unit = unit;
let mut skeleton_die = None;
// Get entries in outer scope to avoid borrowing on short lived temporary.
let mut skeleton_entries = unit.entries();
if let Some(unit) = split_unit {
program_unit = unit;
// From the spec, a skeleton unit has no children so we can assume the first, and only, entry is the DW_TAG_skeleton_unit (https://dwarfstd.org/doc/DWARF5.pdf).
if let Some(die_tuple) = skeleton_entries.next_dfs()? {
skeleton_die = Some(die_tuple.1);
}
}
// Iterate over all of this compilation unit's entries.
let mut entries = unit.entries();
let mut entries = program_unit.entries();
let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
if let Some((depth_delta, entry)) = entries.next_dfs()? {
assert_eq!(depth_delta, 0);
let (out_line_program, debug_line_offset, file_map, file_index_base) =
clone_line_program(
&unit,
split_dwarf.unwrap_or(dwarf),
dwarf,
program_unit,
entry,
skeleton_die,
addr_tr,
out_encoding,
context.debug_str,
context.debug_str_offsets,
context.debug_line_str,
context.debug_line,
out_strings,
)?;
@ -299,23 +316,11 @@ where
let root_id = comp_unit.root();
die_ref_map.insert(entry.offset(), root_id);
let cu_low_pc = if let Some(AttributeValue::Addr(addr)) =
entry.attr_value(gimli::DW_AT_low_pc)?
{
addr
} else if let Some(AttributeValue::DebugAddrIndex(i)) =
entry.attr_value(gimli::DW_AT_low_pc)?
{
context.debug_addr.get_address(4, unit.addr_base, i)?
} else {
// FIXME? return Err(TransformError("No low_pc for unit header").into());
0
};
let cu_low_pc = unit.low_pc;
clone_die_attributes(
dwarf,
&unit,
split_dwarf.unwrap_or(dwarf),
&program_unit,
entry,
context,
addr_tr,
@ -346,7 +351,9 @@ where
vmctx_die_id,
)
} else {
return Err(TransformError("Unexpected unit header").into());
// Can happen when the DWARF is split and we dont have the package/dwo files.
// This is a better user experience than errorring.
return Ok(None); // empty:
}
} else {
return Ok(None); // empty
@ -469,7 +476,7 @@ where
die_ref_map.insert(entry.offset(), die_id);
clone_die_attributes(
dwarf,
split_dwarf.unwrap_or(dwarf),
&unit,
entry,
context,

11
crates/cranelift/src/debug/write_debuginfo.rs

@ -179,8 +179,17 @@ pub fn emit_dwarf<'a>(
debuginfo_data: &DebugInfoData,
funcs: &CompiledFunctionsMetadata,
memory_offset: &ModuleMemoryOffset,
dwarf_package_bytes: Option<&[u8]>,
tunables: &wasmtime_environ::Tunables,
) -> anyhow::Result<Vec<DwarfSection>> {
let dwarf = transform_dwarf(isa, debuginfo_data, funcs, memory_offset)?;
let dwarf = transform_dwarf(
isa,
debuginfo_data,
funcs,
memory_offset,
dwarf_package_bytes,
tunables,
)?;
let frame_table = create_frame_table(isa, funcs);
let sections = emit_dwarf_sections(isa, dwarf, frame_table)?;
Ok(sections)

2
crates/environ/src/compile/mod.rs

@ -377,6 +377,8 @@ pub trait Compiler: Send + Sync {
obj: &mut Object<'_>,
translation: &ModuleTranslation<'_>,
funcs: &PrimaryMap<DefinedFuncIndex, (SymbolId, &(dyn Any + Send))>,
dwarf_package_bytes: Option<&[u8]>,
tunables: &Tunables,
) -> Result<()>;
/// Creates a new System V Common Information Entry for the ISA.

42
crates/environ/src/compile/module_artifacts.rs

@ -2,8 +2,8 @@
//! with `bincode` as part of a module's compilation process.
use crate::{
obj, CompiledFunctionInfo, CompiledModuleInfo, DefinedFuncIndex, FunctionLoc, FunctionName,
MemoryInitialization, Metadata, ModuleTranslation, PrimaryMap, Tunables,
obj, CompiledFunctionInfo, CompiledModuleInfo, DebugInfoData, DefinedFuncIndex, FunctionLoc,
FunctionName, MemoryInitialization, Metadata, ModuleTranslation, PrimaryMap, Tunables,
};
use anyhow::{bail, Result};
use object::write::{Object, SectionId, StandardSegment, WritableBuffer};
@ -57,6 +57,31 @@ impl<'a> ObjectBuilder<'a> {
}
}
/// Insert the wasm raw wasm-based debuginfo into the output.
/// Note that this is distinct from the native debuginfo
/// possibly generated by the native compiler, hence these sections
/// getting wasm-specific names.
pub fn push_debuginfo(
&mut self,
dwarf: &mut Vec<(u8, Range<u64>)>,
debuginfo: &DebugInfoData<'_>,
) {
self.push_debug(dwarf, &debuginfo.dwarf.debug_abbrev);
self.push_debug(dwarf, &debuginfo.dwarf.debug_addr);
self.push_debug(dwarf, &debuginfo.dwarf.debug_aranges);
self.push_debug(dwarf, &debuginfo.dwarf.debug_info);
self.push_debug(dwarf, &debuginfo.dwarf.debug_line);
self.push_debug(dwarf, &debuginfo.dwarf.debug_line_str);
self.push_debug(dwarf, &debuginfo.dwarf.debug_str);
self.push_debug(dwarf, &debuginfo.dwarf.debug_str_offsets);
self.push_debug(dwarf, &debuginfo.debug_ranges);
self.push_debug(dwarf, &debuginfo.debug_rnglists);
self.push_debug(dwarf, &debuginfo.debug_cu_index);
// Sort this for binary-search-lookup later in `symbolize_context`.
dwarf.sort_by_key(|(id, _)| *id);
}
/// Completes compilation of the `translation` specified, inserting
/// everything necessary into the `Object` being built.
///
@ -188,19 +213,8 @@ impl<'a> ObjectBuilder<'a> {
// getting wasm-specific names.
let mut dwarf = Vec::new();
if self.tunables.parse_wasm_debuginfo {
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_abbrev);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_addr);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_aranges);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_info);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_line);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_line_str);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_str);
self.push_debug(&mut dwarf, &debuginfo.dwarf.debug_str_offsets);
self.push_debug(&mut dwarf, &debuginfo.debug_ranges);
self.push_debug(&mut dwarf, &debuginfo.debug_rnglists);
self.push_debuginfo(&mut dwarf, &debuginfo);
}
// Sort this for binary-search-lookup later in `symbolize_context`.
dwarf.sort_by_key(|(id, _)| *id);
Ok(CompiledModuleInfo {
module,

17
crates/environ/src/module_environ.rs

@ -122,10 +122,12 @@ pub struct DebugInfoData<'a> {
pub dwarf: Dwarf<'a>,
pub name_section: NameSection<'a>,
pub wasm_file: WasmFileInfo,
debug_loc: gimli::DebugLoc<Reader<'a>>,
debug_loclists: gimli::DebugLocLists<Reader<'a>>,
pub debug_loc: gimli::DebugLoc<Reader<'a>>,
pub debug_loclists: gimli::DebugLocLists<Reader<'a>>,
pub debug_ranges: gimli::DebugRanges<Reader<'a>>,
pub debug_rnglists: gimli::DebugRngLists<Reader<'a>>,
pub debug_cu_index: gimli::DebugCuIndex<Reader<'a>>,
pub debug_tu_index: gimli::DebugTuIndex<Reader<'a>>,
}
#[allow(missing_docs)]
@ -782,7 +784,7 @@ and for re-adding support for interface types you can see this issue:
}
fn register_dwarf_section(&mut self, section: &CustomSectionReader<'data>) {
let name = section.name();
let name = section.name().trim_end_matches(".dwo");
if !name.starts_with(".debug_") {
return;
}
@ -800,7 +802,9 @@ and for re-adding support for interface types you can see this issue:
// `gimli::Dwarf` fields.
".debug_abbrev" => dwarf.debug_abbrev = gimli::DebugAbbrev::new(data, endian),
".debug_addr" => dwarf.debug_addr = gimli::DebugAddr::from(slice),
".debug_info" => dwarf.debug_info = gimli::DebugInfo::new(data, endian),
".debug_info" => {
dwarf.debug_info = gimli::DebugInfo::new(data, endian);
}
".debug_line" => dwarf.debug_line = gimli::DebugLine::new(data, endian),
".debug_line_str" => dwarf.debug_line_str = gimli::DebugLineStr::from(slice),
".debug_str" => dwarf.debug_str = gimli::DebugStr::new(data, endian),
@ -818,9 +822,12 @@ and for re-adding support for interface types you can see this issue:
".debug_ranges" => info.debug_ranges = gimli::DebugRanges::new(data, endian),
".debug_rnglists" => info.debug_rnglists = gimli::DebugRngLists::new(data, endian),
// DWARF package fields
".debug_cu_index" => info.debug_cu_index = gimli::DebugCuIndex::new(data, endian),
".debug_tu_index" => info.debug_tu_index = gimli::DebugTuIndex::new(data, endian),
// We don't use these at the moment.
".debug_aranges" | ".debug_pubnames" | ".debug_pubtypes" => return,
other => {
log::warn!("unknown debug section `{}`", other);
return;

16
crates/wasmtime/src/compile.rs

@ -29,6 +29,7 @@ use std::{
collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet},
mem,
};
#[cfg(feature = "component-model")]
use wasmtime_environ::component::Translator;
use wasmtime_environ::{
@ -61,6 +62,7 @@ pub use self::runtime::finish_object;
pub(crate) fn build_artifacts<T: FinishedObject>(
engine: &Engine,
wasm: &[u8],
dwarf_package: Option<&[u8]>,
) -> Result<(T, Option<(CompiledModuleInfo, ModuleTypes)>)> {
let tunables = engine.tunables();
@ -101,6 +103,7 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
engine,
compiled_funcs,
std::iter::once(translation).collect(),
dwarf_package,
)?;
let info = compilation_artifacts.unwrap_as_module_info();
@ -118,9 +121,10 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
/// The output artifact here is the serialized object file contained within
/// an owned mmap along with metadata about the compilation itself.
#[cfg(feature = "component-model")]
pub(crate) fn build_component_artifacts<T: FinishedObject>(
pub(crate) fn build_component_artifacts<'a, T: FinishedObject>(
engine: &Engine,
binary: &[u8],
_dwarf_package: Option<&[u8]>,
) -> Result<(T, Option<wasmtime_environ::component::ComponentArtifacts>)> {
use wasmtime_environ::component::{
CompiledComponentInfo, ComponentArtifacts, ComponentTypesBuilder,
@ -159,6 +163,7 @@ pub(crate) fn build_component_artifacts<T: FinishedObject>(
engine,
compiled_funcs,
module_translations,
None, // TODO: Support dwarf packages for components.
)?;
let (types, ty) = types.finish(
&compilation_artifacts.modules,
@ -649,6 +654,7 @@ impl FunctionIndices {
engine: &'a Engine,
compiled_funcs: Vec<(String, Box<dyn Any + Send>)>,
translations: PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
dwarf_package_bytes: Option<&[u8]>,
) -> Result<(wasmtime_environ::ObjectBuilder<'a>, Artifacts)> {
// Append all the functions to the ELF file.
//
@ -713,7 +719,13 @@ impl FunctionIndices {
})
.collect();
if !funcs.is_empty() {
compiler.append_dwarf(&mut obj, translation, &funcs)?;
compiler.append_dwarf(
&mut obj,
translation,
&funcs,
dwarf_package_bytes,
tunables,
)?;
}
}
}

78
crates/wasmtime/src/compile/code_builder.rs

@ -40,6 +40,8 @@ pub struct CodeBuilder<'a> {
pub(super) engine: &'a Engine,
wasm: Option<Cow<'a, [u8]>>,
wasm_path: Option<Cow<'a, Path>>,
dwarf_package: Option<Cow<'a, [u8]>>,
dwarf_package_path: Option<Cow<'a, Path>>,
wat: bool,
}
@ -51,6 +53,8 @@ impl<'a> CodeBuilder<'a> {
engine,
wasm: None,
wasm_path: None,
dwarf_package: None,
dwarf_package_path: None,
wat: cfg!(feature = "wat"),
}
}
@ -76,6 +80,11 @@ impl<'a> CodeBuilder<'a> {
}
self.wasm = Some(wasm_bytes.into());
self.wasm_path = wasm_path.map(|p| p.into());
if self.wasm_path.is_some() {
self.dwarf_package_from_wasm_path()?;
}
Ok(self)
}
@ -104,6 +113,10 @@ impl<'a> CodeBuilder<'a> {
/// either as a WebAssembly binary or as a WebAssembly text file. The
/// contents are inspected to do this, the file extension is not consulted.
///
/// A DWARF package file will be probed using the root of `file` and with a
/// `.dwp` extension. If found, it will be loaded and DWARF fusion
/// performed.
///
/// # Errors
///
/// If wasm bytes have already been configured via a call to this method or
@ -111,6 +124,9 @@ impl<'a> CodeBuilder<'a> {
///
/// If `file` can't be read or an error happens reading it then that will
/// also be returned.
///
/// If DWARF fusion is performed and the DWARF packaged file cannot be read
/// then an error will be returned.
pub fn wasm_file(&mut self, file: &'a Path) -> Result<&mut Self> {
if self.wasm.is_some() {
bail!("cannot call `wasm` or `wasm_file` twice");
@ -119,6 +135,8 @@ impl<'a> CodeBuilder<'a> {
.with_context(|| format!("failed to read input file: {}", file.display()))?;
self.wasm = Some(wasm.into());
self.wasm_path = Some(file.into());
self.dwarf_package_from_wasm_path()?;
Ok(self)
}
@ -139,6 +157,61 @@ impl<'a> CodeBuilder<'a> {
Ok((&wasm[..]).into())
}
/// Explicitly specify DWARF `.dwp` path.
///
/// # Errors
///
/// This method will return an error if the `.dwp` file has already been set
/// through [`CodeBuilder::dwarf_package`] or auto-detection in
/// [`CodeBuilder::wasm_file`].
///
/// This method will also return an error if `file` cannot be read.
pub fn dwarf_package_file(&mut self, file: &Path) -> Result<&mut Self> {
if self.dwarf_package.is_some() {
bail!("cannot call `dwarf_package` or `dwarf_package_file` twice");
}
let dwarf_package = std::fs::read(file)
.with_context(|| format!("failed to read dwarf input file: {}", file.display()))?;
self.dwarf_package_path = Some(Cow::Owned(file.to_owned()));
self.dwarf_package = Some(dwarf_package.into());
Ok(self)
}
fn dwarf_package_from_wasm_path(&mut self) -> Result<&mut Self> {
let dwarf_package_path_buf = self.wasm_path.as_ref().unwrap().with_extension("dwp");
if dwarf_package_path_buf.exists() {
return self.dwarf_package_file(dwarf_package_path_buf.as_path());
}
Ok(self)
}
/// Gets the DWARF package.
pub(super) fn dwarf_package_binary(&self) -> Option<&[u8]> {
return self.dwarf_package.as_deref();
}
/// Set the DWARF package binary.
///
/// Initializes `dwarf_package` from `dwp_bytes` in preparation for
/// DWARF fusion. Allows the DWARF package to be supplied as a byte array
/// when the file probing performed in `wasm_file` is not appropriate.
///
/// # Errors
///
/// Returns an error if the `*.dwp` file is already set via auto-probing in
/// [`CodeBuilder::wasm_file`] or explicitly via
/// [`CodeBuilder::dwarf_package_file`].
pub fn dwarf_package(&mut self, dwp_bytes: &'a [u8]) -> Result<&mut Self> {
if self.dwarf_package.is_some() {
bail!("cannot call `dwarf_package` or `dwarf_package_file` twice");
}
self.dwarf_package = Some(dwp_bytes.into());
Ok(self)
}
/// Finishes this compilation and produces a serialized list of bytes.
///
/// This method requires that either [`CodeBuilder::wasm`] or
@ -157,7 +230,8 @@ impl<'a> CodeBuilder<'a> {
/// compilation-related error is encountered.
pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
let wasm = self.wasm_binary()?;
let (v, _) = super::build_artifacts(self.engine, &wasm)?;
let dwarf_package = self.dwarf_package_binary();
let (v, _) = super::build_artifacts(self.engine, &wasm, dwarf_package.as_deref())?;
Ok(v)
}
@ -168,7 +242,7 @@ impl<'a> CodeBuilder<'a> {
#[cfg_attr(docsrs, doc(cfg(feature = "component-model")))]
pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
let bytes = self.wasm_binary()?;
let (v, _) = super::build_component_artifacts(self.engine, &bytes)?;
let (v, _) = super::build_component_artifacts(self.engine, &bytes, None)?;
Ok(v)
}
}

18
crates/wasmtime/src/compile/runtime.rs

@ -11,9 +11,10 @@ use wasmtime_runtime::MmapVec;
impl<'a> CodeBuilder<'a> {
fn compile_cached<T>(
&self,
build_artifacts: fn(&Engine, &[u8]) -> Result<(MmapVecWrapper, Option<T>)>,
build_artifacts: fn(&Engine, &[u8], Option<&[u8]>) -> Result<(MmapVecWrapper, Option<T>)>,
) -> Result<(Arc<CodeMemory>, Option<T>)> {
let wasm = self.wasm_binary()?;
let dwarf_package = self.dwarf_package_binary();
self.engine
.check_compatible_with_native_host()
@ -24,6 +25,7 @@ impl<'a> CodeBuilder<'a> {
let state = (
HashedEngineCompileEnv(self.engine),
&wasm,
&dwarf_package,
// Don't hash this as it's just its own "pure" function pointer.
NotHashed(build_artifacts),
);
@ -32,15 +34,18 @@ impl<'a> CodeBuilder<'a> {
.get_data_raw(
&state,
// Cache miss, compute the actual artifacts
|(engine, wasm, build_artifacts)| -> Result<_> {
let (mmap, info) = (build_artifacts.0)(engine.0, wasm)?;
|(engine, wasm, dwarf_package, build_artifacts)| -> Result<_> {
let (mmap, info) =
(build_artifacts.0)(engine.0, wasm, dwarf_package.as_deref())?;
let code = publish_mmap(mmap.0)?;
Ok((code, info))
},
// Implementation of how to serialize artifacts
|(_engine, _wasm, _), (code, _info_and_types)| Some(code.mmap().to_vec()),
|(_engine, _wasm, _, _), (code, _info_and_types)| {
Some(code.mmap().to_vec())
},
// Cache hit, deserialize the provided artifacts
|(engine, wasm, _), serialized_bytes| {
|(engine, wasm, _, _), serialized_bytes| {
let kind = if wasmparser::Parser::is_component(&wasm) {
ObjectKind::Component
} else {
@ -55,7 +60,8 @@ impl<'a> CodeBuilder<'a> {
#[cfg(not(feature = "cache"))]
{
let (mmap, info_and_types) = build_artifacts(self.engine, &wasm)?;
let (mmap, info_and_types) =
build_artifacts(self.engine, &wasm, dwarf_package.as_deref())?;
let code = publish_mmap(mmap.0)?;
return Ok((code, info_and_types));
}

1
crates/wasmtime/src/runtime/module.rs

@ -23,7 +23,6 @@ use wasmtime_runtime::{
CompiledModuleId, MemoryImage, MmapVec, ModuleMemoryImages, VMArrayCallFunction,
VMNativeCallFunction, VMWasmCallFunction,
};
mod registry;
pub use registry::{

2
crates/winch/src/compiler.rs

@ -233,6 +233,8 @@ impl wasmtime_environ::Compiler for Compiler {
_obj: &mut Object<'_>,
_translation: &ModuleTranslation<'_>,
_funcs: &PrimaryMap<DefinedFuncIndex, (SymbolId, &(dyn Any + Send))>,
_dwarf_package: Option<&[u8]>,
_tunables: &wasmtime_environ::Tunables,
) -> Result<()> {
todo!()
}

48
tests/all/debug/lldb.rs

@ -247,3 +247,51 @@ check: exited with status
)?;
Ok(())
}
/* C program used for this test, dwarf_fission.c, compiled with `emcc dwarf_fission.c -o dwarf_fission.wasm -gsplit-dwarf -gdwarf-5 -gpubnames -sWASM_BIGINT`:
#include <stdio.h>
int main()
{
int i = 1;
i++;
return i - 2;
}
*/
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
pub fn test_debug_dwarf5_fission_lldb() -> Result<()> {
let output = lldb_with_script(
&[
"-Ccache=n",
"-Ddebug-info",
"tests/all/debug/testsuite/dwarf_fission.wasm",
],
r#"breakpoint set --file dwarf_fission.c --line 6
r
fr v
s
fr v
c"#,
)?;
check_lldb_output(
&output,
r#"
check: Breakpoint 1: no locations (pending)
check: Unable to resolve breakpoint to any actual locations.
check: 1 location added to breakpoint 1
check: stop reason = breakpoint 1.1
check: i = 1
check: stop reason = step in
check: i = 2
check: resuming
check: exited with status = 0
"#,
)?;
Ok(())
}

7
tests/all/debug/testsuite/dwarf_fission.c

@ -0,0 +1,7 @@
#include <stdio.h>
int main() {
int i = 1;
i++;
return i - 2;
}

BIN
tests/all/debug/testsuite/dwarf_fission.dwp

Binary file not shown.

BIN
tests/all/debug/testsuite/dwarf_fission.wasm

Binary file not shown.
Loading…
Cancel
Save