Path: blob/main/crates/cranelift/src/debug/transform/simulate.rs
1693 views
use super::AddressTransform;1use super::expression::{CompiledExpression, FunctionFrameInfo};2use super::utils::append_vmctx_info;3use crate::debug::Compilation;4use crate::translate::get_vmctx_value_label;5use anyhow::{Context, Error};6use cranelift_codegen::isa::TargetIsa;7use gimli::LineEncoding;8use gimli::write;9use std::collections::{HashMap, HashSet};10use std::path::PathBuf;11use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};12use wasmtime_environ::{13DebugInfoData, EntityRef, FunctionMetadata, PrimaryMap, StaticModuleIndex, WasmFileInfo,14WasmValType,15};1617const PRODUCER_NAME: &str = "wasmtime";1819macro_rules! assert_dwarf_str {20($s:expr) => {{21let s = $s;22if cfg!(debug_assertions) {23// Perform check the same way as gimli does it.24let bytes: Vec<u8> = s.clone().into();25debug_assert!(!bytes.contains(&0), "DWARF string shall not have NULL byte");26}27s28}};29}3031fn generate_line_info(32addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,33translated: &HashSet<usize>,34out_encoding: gimli::Encoding,35w: &WasmFileInfo,36comp_dir_id: write::StringId,37name_id: write::StringId,38name: &str,39) -> Result<(write::LineProgram, write::FileId), Error> {40let out_comp_dir = write::LineString::StringRef(comp_dir_id);41let out_comp_name = write::LineString::StringRef(name_id);4243let line_encoding = LineEncoding::default();4445let mut out_program = write::LineProgram::new(46out_encoding,47line_encoding,48out_comp_dir,49None,50out_comp_name,51None,52);5354let file_index = out_program.add_file(55write::LineString::String(name.as_bytes().to_vec()),56out_program.default_directory(),57None,58);5960let maps = addr_tr.iter().flat_map(|(_, transform)| {61transform.map().iter().filter_map(|(_, map)| {62if translated.contains(&map.symbol) {63None64} else {65Some((map.symbol, map))66}67})68});6970for (symbol, map) in maps {71let base_addr = map.offset;72out_program.begin_sequence(Some(write::Address::Symbol {73symbol,74addend: base_addr as i64,75}));7677// Always emit a row for offset zero - debuggers expect this.78out_program.row().address_offset = 0;79out_program.row().file = file_index;80out_program.row().line = 0; // Special line number for non-user code.81out_program.row().discriminator = 1;82out_program.row().is_statement = true;83out_program.generate_row();8485let mut is_prologue_end = true;86for addr_map in map.addresses.iter() {87let address_offset = (addr_map.generated - base_addr) as u64;88out_program.row().address_offset = address_offset;89let wasm_offset = w.code_section_offset + addr_map.wasm;90out_program.row().line = wasm_offset;91out_program.row().discriminator = 1;92out_program.row().prologue_end = is_prologue_end;93out_program.generate_row();9495is_prologue_end = false;96}97let end_addr = (base_addr + map.len - 1) as u64;98out_program.end_sequence(end_addr);99}100101Ok((out_program, file_index))102}103104fn check_invalid_chars_in_name(s: &str) -> Option<&str> {105if s.contains('\x00') { None } else { Some(s) }106}107108fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {109static NEXT_ID: AtomicUsize = AtomicUsize::new(0);110let module_name = di111.name_section112.module_name113.and_then(check_invalid_chars_in_name)114.map(|s| s.to_string())115.unwrap_or_else(|| format!("<gen-{}>.wasm", NEXT_ID.fetch_add(1, SeqCst)));116let path = format!("/<wasm-module>/{module_name}");117PathBuf::from(path)118}119120struct WasmTypesDieRefs {121i32: write::UnitEntryId,122i64: write::UnitEntryId,123f32: write::UnitEntryId,124f64: write::UnitEntryId,125}126127fn add_wasm_types(128unit: &mut write::Unit,129root_id: write::UnitEntryId,130out_strings: &mut write::StringTable,131) -> WasmTypesDieRefs {132macro_rules! def_type {133($id:literal, $size:literal, $enc:path) => {{134let die_id = unit.add(root_id, gimli::DW_TAG_base_type);135let die = unit.get_mut(die_id);136die.set(137gimli::DW_AT_name,138write::AttributeValue::StringRef(out_strings.add($id)),139);140die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));141die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));142die_id143}};144}145146let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);147let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);148let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);149let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);150151WasmTypesDieRefs {152i32: i32_die_id,153i64: i64_die_id,154f32: f32_die_id,155f64: f64_die_id,156}157}158159fn resolve_var_type(160index: usize,161wasm_types: &WasmTypesDieRefs,162func_meta: &FunctionMetadata,163) -> Option<(write::UnitEntryId, bool)> {164let (ty, is_param) = if index < func_meta.params.len() {165(func_meta.params[index], true)166} else {167let mut i = (index - func_meta.params.len()) as u32;168let mut j = 0;169while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {170i -= func_meta.locals[j].0;171j += 1;172}173if j >= func_meta.locals.len() {174// Ignore the var index out of bound.175return None;176}177(func_meta.locals[j].1, false)178};179let type_die_id = match ty {180WasmValType::I32 => wasm_types.i32,181WasmValType::I64 => wasm_types.i64,182WasmValType::F32 => wasm_types.f32,183WasmValType::F64 => wasm_types.f64,184_ => {185// Ignore unsupported types.186return None;187}188};189Some((type_die_id, is_param))190}191192fn generate_vars(193unit: &mut write::Unit,194die_id: write::UnitEntryId,195addr_tr: &AddressTransform,196frame_info: &FunctionFrameInfo,197scope_ranges: &[(u64, u64)],198vmctx_ptr_die_ref: write::Reference,199wasm_types: &WasmTypesDieRefs,200func_meta: &FunctionMetadata,201locals_names: Option<&HashMap<u32, &str>>,202out_strings: &mut write::StringTable,203isa: &dyn TargetIsa,204) -> Result<(), Error> {205let vmctx_label = get_vmctx_value_label();206207// Normalize order of ValueLabelsRanges keys to have reproducible results.208let mut vars = frame_info.value_ranges.keys().collect::<Vec<_>>();209vars.sort_by(|a, b| a.index().cmp(&b.index()));210211for label in vars {212if label.index() == vmctx_label.index() {213append_vmctx_info(214unit,215die_id,216vmctx_ptr_die_ref,217addr_tr,218Some(frame_info),219scope_ranges,220out_strings,221isa,222)?;223} else {224let var_index = label.index();225let (type_die_id, is_param) =226if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {227result228} else {229// Skipping if type of local cannot be detected.230continue;231};232233let loc_list_id = {234let locs = CompiledExpression::from_label(*label)235.build_with_locals(scope_ranges, addr_tr, Some(frame_info), isa)236.expressions237.map(|i| {238i.map(|(begin, length, data)| write::Location::StartLength {239begin,240length,241data,242})243})244.collect::<Result<Vec<_>, _>>()?;245unit.locations.add(write::LocationList(locs))246};247248let var_id = unit.add(249die_id,250if is_param {251gimli::DW_TAG_formal_parameter252} else {253gimli::DW_TAG_variable254},255);256let var = unit.get_mut(var_id);257258let name_id = match locals_names259.and_then(|m| m.get(&(var_index as u32)))260.and_then(|s| check_invalid_chars_in_name(s))261{262Some(n) => out_strings.add(assert_dwarf_str!(n)),263None => out_strings.add(format!("var{var_index}")),264};265266var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));267var.set(268gimli::DW_AT_type,269write::AttributeValue::UnitRef(type_die_id),270);271var.set(272gimli::DW_AT_location,273write::AttributeValue::LocationListRef(loc_list_id),274);275}276}277Ok(())278}279280fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {281path.clone()282.to_str()283.and_then(move |s| if s.contains('\x00') { None } else { Some(path) })284}285286/// Generate "simulated" native DWARF for functions lacking WASM-level DWARF.287pub fn generate_simulated_dwarf(288compilation: &mut Compilation<'_>,289addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,290translated: &HashSet<usize>,291out_encoding: gimli::Encoding,292vmctx_ptr_die_refs: &PrimaryMap<StaticModuleIndex, write::Reference>,293out_units: &mut write::UnitTable,294out_strings: &mut write::StringTable,295isa: &dyn TargetIsa,296) -> Result<(), Error> {297let (wasm_file, path) = {298let di = &compilation.translations.iter().next().unwrap().1.debuginfo;299let path = di300.wasm_file301.path302.to_owned()303.and_then(check_invalid_chars_in_path)304.unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));305(&di.wasm_file, path)306};307308let (unit, root_id, file_id) = {309let comp_dir_id = out_strings.add(assert_dwarf_str!(310path.parent()311.context("path dir")?312.to_str()313.context("path dir encoding")?314));315let name = path316.file_name()317.context("path name")?318.to_str()319.context("path name encoding")?;320let name_id = out_strings.add(assert_dwarf_str!(name));321322let (out_program, file_id) = generate_line_info(323addr_tr,324translated,325out_encoding,326wasm_file,327comp_dir_id,328name_id,329name,330)?;331332let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));333let unit = out_units.get_mut(unit_id);334335let root_id = unit.root();336let root = unit.get_mut(root_id);337338let id = out_strings.add(PRODUCER_NAME);339root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));340root.set(341gimli::DW_AT_language,342write::AttributeValue::Language(gimli::DW_LANG_C11),343);344root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));345root.set(346gimli::DW_AT_stmt_list,347write::AttributeValue::LineProgramRef,348);349root.set(350gimli::DW_AT_comp_dir,351write::AttributeValue::StringRef(comp_dir_id),352);353(unit, root_id, file_id)354};355356let wasm_types = add_wasm_types(unit, root_id, out_strings);357let mut unit_ranges = vec![];358for (module, index) in compilation.indexes().collect::<Vec<_>>() {359let (symbol, _) = compilation.function(module, index);360if translated.contains(&symbol) {361continue;362}363364let addr_tr = &addr_tr[module];365let map = &addr_tr.map()[index];366let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);367let die = unit.get_mut(die_id);368let low_pc = write::Address::Symbol {369symbol,370addend: map.offset as i64,371};372let code_length = map.len as u64;373die.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(low_pc));374die.set(375gimli::DW_AT_high_pc,376write::AttributeValue::Udata(code_length),377);378unit_ranges.push(write::Range::StartLength {379begin: low_pc,380length: code_length,381});382383let translation = &compilation.translations[module];384let func_index = translation.module.func_index(index);385let di = &translation.debuginfo;386let id = match di387.name_section388.func_names389.get(&func_index)390.and_then(|s| check_invalid_chars_in_name(s))391{392Some(n) => out_strings.add(assert_dwarf_str!(n)),393None => out_strings.add(format!("wasm-function[{}]", func_index.as_u32())),394};395396die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));397398die.set(399gimli::DW_AT_decl_file,400write::AttributeValue::FileIndex(Some(file_id)),401);402403let f_start = map.addresses[0].wasm;404let wasm_offset = di.wasm_file.code_section_offset + f_start;405die.set(406gimli::DW_AT_decl_line,407write::AttributeValue::Udata(wasm_offset),408);409410let frame_info = compilation.function_frame_info(module, index);411let source_range = addr_tr.func_source_range(index);412generate_vars(413unit,414die_id,415addr_tr,416&frame_info,417&[(source_range.0, source_range.1)],418vmctx_ptr_die_refs[module],419&wasm_types,420&di.wasm_file.funcs[index.as_u32() as usize],421di.name_section.locals_names.get(&func_index),422out_strings,423isa,424)?;425}426let unit_ranges_id = unit.ranges.add(write::RangeList(unit_ranges));427unit.get_mut(root_id).set(428gimli::DW_AT_ranges,429write::AttributeValue::RangeListRef(unit_ranges_id),430);431432Ok(())433}434435436