Path: blob/main/crates/cranelift/src/debug/transform/address_transform.rs
1693 views
use crate::FunctionAddressMap;1use crate::debug::Compilation;2use gimli::write;3use std::collections::BTreeMap;4use wasmtime_environ::{DefinedFuncIndex, FilePos, PrimaryMap, StaticModuleIndex};56pub type GeneratedAddress = usize;7pub type WasmAddress = u64;89/// Contains mapping of the generated address to its original10/// source location.11#[derive(Debug)]12pub struct AddressMap {13pub generated: GeneratedAddress,14pub wasm: WasmAddress,15}1617/// Information about generated function code: its body start,18/// length, and instructions addresses.19#[derive(Debug)]20pub struct FunctionMap {21pub symbol: usize,22pub offset: GeneratedAddress,23pub len: GeneratedAddress,24pub wasm_start: WasmAddress,25pub wasm_end: WasmAddress,26pub addresses: Box<[AddressMap]>,27}2829/// Mapping of the source location to its generated code range.30#[derive(Debug)]31struct Position {32wasm_pos: WasmAddress,33gen_start: GeneratedAddress,34gen_end: GeneratedAddress,35}3637/// Mapping of continuous range of source location to its generated38/// code. The positions are always in ascending order for search.39#[derive(Debug)]40struct Range {41wasm_start: WasmAddress,42wasm_end: WasmAddress,43gen_start: GeneratedAddress,44gen_end: GeneratedAddress,45positions: Box<[Position]>,46}4748type RangeIndex = usize;4950/// Helper function address lookup data. Contains ranges start positions51/// index and ranges data. The multiple ranges can include the same52/// original source position. The index (B-Tree) uses range start53/// position as a key. The index values reference the ranges array.54/// The item are ordered RangeIndex.55#[derive(Debug)]56struct FuncLookup {57index: Vec<(WasmAddress, Box<[RangeIndex]>)>,58ranges: Box<[Range]>,59}6061/// Mapping of original functions to generated code locations/ranges.62#[derive(Debug)]63struct FuncTransform {64start: WasmAddress,65end: WasmAddress,66index: DefinedFuncIndex,67lookup: FuncLookup,68}6970/// Module functions mapping to generated code.71#[derive(Debug)]72pub struct AddressTransform {73map: PrimaryMap<DefinedFuncIndex, FunctionMap>,74func: Vec<(WasmAddress, FuncTransform)>,75}7677/// Returns a wasm bytecode offset in the code section from SourceLoc.78fn get_wasm_code_offset(loc: FilePos, code_section_offset: u64) -> WasmAddress {79// Code section size <= 4GB, allow wrapped SourceLoc to recover the overflow.80loc.file_offset()81.unwrap()82.wrapping_sub(code_section_offset as u32) as WasmAddress83}8485fn build_function_lookup(86ft: &FunctionAddressMap,87code_section_offset: u64,88) -> (WasmAddress, WasmAddress, FuncLookup) {89assert!(code_section_offset <= ft.start_srcloc.file_offset().unwrap().into());90let fn_start = get_wasm_code_offset(ft.start_srcloc, code_section_offset);91let fn_end = get_wasm_code_offset(ft.end_srcloc, code_section_offset);92assert!(fn_start <= fn_end);9394// Build ranges of continuous source locations. The new ranges starts when95// non-descending order is interrupted. Assuming the same origin location can96// be present in multiple ranges.97let mut range_wasm_start = fn_start;98let mut range_gen_start = ft.body_offset;99let mut last_wasm_pos = range_wasm_start;100let mut ranges = Vec::new();101let mut ranges_index = BTreeMap::new();102let mut current_range = Vec::new();103let mut last_gen_inst_empty = false;104for (i, t) in ft.instructions.iter().enumerate() {105if t.srcloc.file_offset().is_none() {106continue;107}108109let offset = get_wasm_code_offset(t.srcloc, code_section_offset);110assert!(fn_start <= offset);111assert!(offset <= fn_end);112113let inst_gen_start = t.code_offset as usize;114let inst_gen_end = match ft.instructions.get(i + 1) {115Some(i) => i.code_offset as usize,116None => ft.body_len as usize,117};118119if last_wasm_pos > offset {120// Start new range.121ranges_index.insert(range_wasm_start, ranges.len());122ranges.push(Range {123wasm_start: range_wasm_start,124wasm_end: last_wasm_pos,125gen_start: range_gen_start,126gen_end: inst_gen_start,127positions: current_range.into_boxed_slice(),128});129range_wasm_start = offset;130range_gen_start = inst_gen_start;131current_range = Vec::new();132last_gen_inst_empty = false;133}134if last_gen_inst_empty && current_range.last().unwrap().gen_start == inst_gen_start {135// It is possible that previous inst_gen_start == inst_gen_end, so136// make an attempt to merge all such positions with current one.137if inst_gen_start < inst_gen_end {138let last = current_range.last_mut().unwrap();139last.gen_end = inst_gen_end;140last_gen_inst_empty = false;141}142} else {143// Continue existing range: add new wasm->generated code position.144current_range.push(Position {145wasm_pos: offset,146gen_start: inst_gen_start,147gen_end: inst_gen_end,148});149// Track if last position was empty (see if-branch above).150last_gen_inst_empty = inst_gen_start == inst_gen_end;151}152last_wasm_pos = offset;153}154let last_gen_addr = ft.body_offset + ft.body_len as usize;155ranges_index.insert(range_wasm_start, ranges.len());156ranges.push(Range {157wasm_start: range_wasm_start,158wasm_end: fn_end,159gen_start: range_gen_start,160gen_end: last_gen_addr,161positions: current_range.into_boxed_slice(),162});163164// Making ranges lookup faster by building index: B-tree with every range165// start position that maps into list of active ranges at this position.166let ranges = ranges.into_boxed_slice();167let mut active_ranges = Vec::new();168let mut index = BTreeMap::new();169let mut last_wasm_pos = None;170for (wasm_start, range_index) in ranges_index {171if Some(wasm_start) == last_wasm_pos {172active_ranges.push(range_index);173continue;174}175if let Some(position) = last_wasm_pos {176let mut sorted_ranges = active_ranges.clone();177sorted_ranges.sort();178index.insert(position, sorted_ranges.into_boxed_slice());179}180active_ranges.retain(|r| ranges[*r].wasm_end.cmp(&wasm_start) != std::cmp::Ordering::Less);181active_ranges.push(range_index);182last_wasm_pos = Some(wasm_start);183}184active_ranges.sort();185index.insert(last_wasm_pos.unwrap(), active_ranges.into_boxed_slice());186let index = Vec::from_iter(index);187(fn_start, fn_end, FuncLookup { index, ranges })188}189190fn build_function_addr_map(191compilation: &Compilation<'_>,192module: StaticModuleIndex,193) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {194let mut map = PrimaryMap::new();195for idx in compilation.translations[module]196.module197.defined_func_indices()198{199let (symbol, metadata) = compilation.function(module, idx);200let code_section_offset = compilation.translations[module]201.debuginfo202.wasm_file203.code_section_offset;204let ft = &metadata.address_map;205let mut fn_map = Vec::new();206for t in ft.instructions.iter() {207if t.srcloc.file_offset().is_none() {208continue;209}210let offset = get_wasm_code_offset(t.srcloc, code_section_offset);211fn_map.push(AddressMap {212generated: t.code_offset as usize,213wasm: offset,214});215}216217if cfg!(debug_assertions) {218// fn_map is sorted by the generated field -- see FunctionAddressMap::instructions.219for i in 1..fn_map.len() {220assert!(fn_map[i - 1].generated <= fn_map[i].generated);221}222}223224map.push(FunctionMap {225symbol,226offset: ft.body_offset,227len: ft.body_len as usize,228wasm_start: get_wasm_code_offset(ft.start_srcloc, code_section_offset),229wasm_end: get_wasm_code_offset(ft.end_srcloc, code_section_offset),230addresses: fn_map.into_boxed_slice(),231});232}233map234}235236// Utility iterator to find all ranges starts for specific Wasm address.237// The iterator returns generated addresses sorted by RangeIndex.238struct TransformRangeStartIter<'a> {239addr: WasmAddress,240indices: &'a [RangeIndex],241ranges: &'a [Range],242}243244impl<'a> TransformRangeStartIter<'a> {245fn new(func: &'a FuncTransform, addr: WasmAddress) -> Self {246let found = match func247.lookup248.index249.binary_search_by(|entry| entry.0.cmp(&addr))250{251Ok(i) => Some(&func.lookup.index[i].1),252Err(i) => {253if i > 0 {254Some(&func.lookup.index[i - 1].1)255} else {256None257}258}259};260if let Some(range_indices) = found {261TransformRangeStartIter {262addr,263indices: range_indices,264ranges: &func.lookup.ranges,265}266} else {267unreachable!();268}269}270}271272impl<'a> Iterator for TransformRangeStartIter<'a> {273type Item = (GeneratedAddress, RangeIndex);274fn next(&mut self) -> Option<Self::Item> {275if let Some((first, tail)) = self.indices.split_first() {276let range_index = *first;277let range = &self.ranges[range_index];278self.indices = tail;279let address = match range280.positions281.binary_search_by(|a| a.wasm_pos.cmp(&self.addr))282{283Ok(i) => range.positions[i].gen_start,284Err(i) => {285if i == 0 {286range.gen_start287} else {288range.positions[i - 1].gen_end289}290}291};292Some((address, range_index))293} else {294None295}296}297}298299// Utility iterator to find all ranges ends for specific Wasm address.300// The iterator returns generated addresses sorted by RangeIndex.301struct TransformRangeEndIter<'a> {302addr: WasmAddress,303indices: &'a [RangeIndex],304ranges: &'a [Range],305}306307impl<'a> TransformRangeEndIter<'a> {308fn new(func: &'a FuncTransform, addr: WasmAddress) -> Self {309let found = match func310.lookup311.index312.binary_search_by(|entry| entry.0.cmp(&addr))313{314Ok(i) => Some(&func.lookup.index[i].1),315Err(i) => {316if i > 0 {317Some(&func.lookup.index[i - 1].1)318} else {319None320}321}322};323if let Some(range_indices) = found {324TransformRangeEndIter {325addr,326indices: range_indices,327ranges: &func.lookup.ranges,328}329} else {330unreachable!();331}332}333}334335impl<'a> Iterator for TransformRangeEndIter<'a> {336type Item = (GeneratedAddress, RangeIndex);337fn next(&mut self) -> Option<Self::Item> {338while let Some((first, tail)) = self.indices.split_first() {339let range_index = *first;340let range = &self.ranges[range_index];341self.indices = tail;342if range.wasm_start >= self.addr {343continue;344}345let address = match range346.positions347.binary_search_by(|a| a.wasm_pos.cmp(&self.addr))348{349Ok(i) => range.positions[i].gen_end,350Err(i) => {351if i == range.positions.len() {352range.gen_end353} else {354range.positions[i].gen_start355}356}357};358return Some((address, range_index));359}360None361}362}363364// Utility iterator to iterate by translated function ranges.365struct TransformRangeIter<'a> {366func: &'a FuncTransform,367start_it: TransformRangeStartIter<'a>,368end_it: TransformRangeEndIter<'a>,369last_start: Option<(GeneratedAddress, RangeIndex)>,370last_end: Option<(GeneratedAddress, RangeIndex)>,371last_item: Option<(GeneratedAddress, GeneratedAddress)>,372}373374impl<'a> TransformRangeIter<'a> {375fn new(func: &'a FuncTransform, start: WasmAddress, end: WasmAddress) -> Self {376let mut start_it = TransformRangeStartIter::new(func, start);377let last_start = start_it.next();378let mut end_it = TransformRangeEndIter::new(func, end);379let last_end = end_it.next();380TransformRangeIter {381func,382start_it,383end_it,384last_start,385last_end,386last_item: None,387}388}389}390391impl<'a> Iterator for TransformRangeIter<'a> {392type Item = (GeneratedAddress, GeneratedAddress);393fn next(&mut self) -> Option<Self::Item> {394loop {395// Merge TransformRangeStartIter and TransformRangeEndIter data using396// FuncLookup index's field property to be sorted by RangeIndex.397let (start, end, range_index): (398Option<GeneratedAddress>,399Option<GeneratedAddress>,400RangeIndex,401) = {402match (self.last_start.as_ref(), self.last_end.as_ref()) {403(Some((s, sri)), Some((e, eri))) => {404if sri == eri {405// Start and end RangeIndex matched.406(Some(*s), Some(*e), *sri)407} else if sri < eri {408(Some(*s), None, *sri)409} else {410(None, Some(*e), *eri)411}412}413(Some((s, sri)), None) => (Some(*s), None, *sri),414(None, Some((e, eri))) => (None, Some(*e), *eri),415(None, None) => {416// Reached ends for start and end iterators.417return None;418}419}420};421let range_start = match start {422Some(range_start) => {423// Consume start iterator.424self.last_start = self.start_it.next();425range_start426}427None => {428let range = &self.func.lookup.ranges[range_index];429range.gen_start430}431};432let range_end = match end {433Some(range_end) => {434// Consume end iterator.435self.last_end = self.end_it.next();436range_end437}438None => {439let range = &self.func.lookup.ranges[range_index];440range.gen_end441}442};443444if cfg!(debug_assertions) {445match self.last_item.replace((range_start, range_end)) {446Some((_, last_end)) => debug_assert!(last_end <= range_start),447None => (),448}449}450451if range_start < range_end {452return Some((range_start, range_end));453}454// Throw away empty ranges.455debug_assert!(range_start == range_end);456}457}458}459460impl AddressTransform {461pub fn new(compilation: &Compilation<'_>, module: StaticModuleIndex) -> Self {462let mut func = BTreeMap::new();463let code_section_offset = compilation.translations[module]464.debuginfo465.wasm_file466.code_section_offset;467468for idx in compilation.translations[module]469.module470.defined_func_indices()471{472let (_, metadata) = compilation.function(module, idx);473let (fn_start, fn_end, lookup) =474build_function_lookup(&metadata.address_map, code_section_offset);475476func.insert(477fn_start,478FuncTransform {479start: fn_start,480end: fn_end,481index: idx,482lookup,483},484);485}486487let map = build_function_addr_map(compilation, module);488let func = Vec::from_iter(func);489AddressTransform { map, func }490}491492#[cfg(test)]493pub fn mock(494module_map: &wasmtime_environ::PrimaryMap<495wasmtime_environ::DefinedFuncIndex,496&crate::CompiledFunctionMetadata,497>,498wasm_file: wasmtime_environ::WasmFileInfo,499) -> Self {500use cranelift_entity::EntityRef;501502let mut translations = wasmtime_environ::PrimaryMap::new();503let mut translation = wasmtime_environ::ModuleTranslation::new(StaticModuleIndex::new(0));504translation.debuginfo.wasm_file = wasm_file;505translation506.module507.push_function(wasmtime_environ::ModuleInternedTypeIndex::from_u32(0));508translations.push(translation);509510let mut dummy_obj = object::write::Object::new(511object::BinaryFormat::Elf,512object::Architecture::Wasm32,513object::Endianness::Little,514);515let dummy_symbol = dummy_obj.add_file_symbol(Vec::new());516let func_lookup = move |_, f| (dummy_symbol, module_map[f]);517let tunables = wasmtime_environ::Tunables::default_host();518let compile = Compilation::new(519&*cranelift_codegen::isa::lookup(target_lexicon::Triple::host())520.unwrap()521.finish(cranelift_codegen::settings::Flags::new(522cranelift_codegen::settings::builder(),523))524.unwrap(),525&translations,526&func_lookup,527None,528&tunables,529);530Self::new(&compile, StaticModuleIndex::from_u32(0))531}532533fn find_func(&self, addr: u64) -> Option<&FuncTransform> {534// TODO check if we need to include end address535let func = match self.func.binary_search_by(|entry| entry.0.cmp(&addr)) {536Ok(i) => &self.func[i].1,537Err(i) => {538if i > 0 {539&self.func[i - 1].1540} else {541return None;542}543}544};545if addr >= func.start {546return Some(func);547}548None549}550551pub fn find_func_index(&self, addr: u64) -> Option<DefinedFuncIndex> {552self.find_func(addr).map(|f| f.index)553}554555fn translate_raw(&self, addr: u64) -> Option<(usize, GeneratedAddress)> {556const TOMBSTONE: u64 = u32::MAX as u64;557if addr == 0 || addr == TOMBSTONE {558// Addresses for unlinked code may be left as 0 or replaced559// with -1, depending on the linker used.560return None;561}562if let Some(func) = self.find_func(addr) {563let map = &self.map[func.index];564if addr == func.end {565// Clamp last address to the end to extend translation to the end566// of the function.567return Some((map.symbol, map.len));568}569let first_result = TransformRangeStartIter::new(func, addr).next();570first_result.map(|(address, _)| (map.symbol, address))571} else {572// Address was not found: function was not compiled?573None574}575}576577pub fn can_translate_address(&self, addr: u64) -> bool {578self.translate(addr).is_some()579}580581pub fn translate(&self, addr: u64) -> Option<write::Address> {582self.translate_raw(addr)583.map(|(symbol, address)| write::Address::Symbol {584symbol,585addend: address as i64,586})587}588589pub fn translate_ranges_raw<'a>(590&'a self,591start: u64,592end: u64,593) -> Option<(usize, impl Iterator<Item = (usize, usize)> + 'a)> {594if start == 0 {595// It's normally 0 for debug info without the linked code.596return None;597}598if let Some(func) = self.find_func(start) {599let result = TransformRangeIter::new(func, start, end);600let symbol = self.map[func.index].symbol;601return Some((symbol, result));602}603// Address was not found: function was not compiled?604None605}606607pub fn translate_ranges<'a>(608&'a self,609start: u64,610end: u64,611) -> impl Iterator<Item = (write::Address, u64)> + 'a {612enum TranslateRangesResult<'a> {613Empty,614Raw {615symbol: usize,616it: Box<dyn Iterator<Item = (usize, usize)> + 'a>,617},618}619impl<'a> Iterator for TranslateRangesResult<'a> {620type Item = (write::Address, u64);621fn next(&mut self) -> Option<Self::Item> {622match self {623TranslateRangesResult::Empty => None,624TranslateRangesResult::Raw { symbol, it } => match it.next() {625Some((start, end)) => {626debug_assert!(start < end);627Some((628write::Address::Symbol {629symbol: *symbol,630addend: start as i64,631},632(end - start) as u64,633))634}635None => None,636},637}638}639}640641match self.translate_ranges_raw(start, end) {642Some((symbol, ranges)) => TranslateRangesResult::Raw {643symbol,644it: Box::new(ranges),645},646None => TranslateRangesResult::Empty,647}648}649650pub fn map(&self) -> &PrimaryMap<DefinedFuncIndex, FunctionMap> {651&self.map652}653654pub fn func_range(&self, index: DefinedFuncIndex) -> (GeneratedAddress, GeneratedAddress) {655let map = &self.map[index];656(map.offset, map.offset + map.len)657}658659pub fn func_source_range(&self, index: DefinedFuncIndex) -> (WasmAddress, WasmAddress) {660let map = &self.map[index];661(map.wasm_start, map.wasm_end)662}663}664665#[cfg(test)]666mod tests {667use super::{AddressTransform, build_function_lookup, get_wasm_code_offset};668use crate::{CompiledFunctionMetadata, FunctionAddressMap};669use cranelift_entity::PrimaryMap;670use gimli::write::Address;671use std::mem;672use wasmtime_environ::{FilePos, InstructionAddressMap, WasmFileInfo};673674#[test]675fn test_get_wasm_code_offset() {676let offset = get_wasm_code_offset(FilePos::new(3), 1);677assert_eq!(2, offset);678let offset = get_wasm_code_offset(FilePos::new(16), 0xF000_0000);679assert_eq!(0x1000_0010, offset);680let offset = get_wasm_code_offset(FilePos::new(1), 0x20_8000_0000);681assert_eq!(0x8000_0001, offset);682}683684fn create_simple_func(wasm_offset: u32) -> FunctionAddressMap {685FunctionAddressMap {686instructions: vec![687InstructionAddressMap {688srcloc: FilePos::new(wasm_offset + 2),689code_offset: 5,690},691InstructionAddressMap {692srcloc: FilePos::default(),693code_offset: 8,694},695InstructionAddressMap {696srcloc: FilePos::new(wasm_offset + 7),697code_offset: 15,698},699InstructionAddressMap {700srcloc: FilePos::default(),701code_offset: 23,702},703]704.into(),705start_srcloc: FilePos::new(wasm_offset),706end_srcloc: FilePos::new(wasm_offset + 10),707body_offset: 0,708body_len: 30,709}710}711712#[test]713fn test_build_function_lookup_simple() {714let input = create_simple_func(11);715let (start, end, lookup) = build_function_lookup(&input, 1);716assert_eq!(10, start);717assert_eq!(20, end);718719assert_eq!(1, lookup.index.len());720let index_entry = lookup.index.into_iter().next().unwrap();721assert_eq!((10u64, vec![0].into_boxed_slice()), index_entry);722assert_eq!(1, lookup.ranges.len());723let range = &lookup.ranges[0];724assert_eq!(10, range.wasm_start);725assert_eq!(20, range.wasm_end);726assert_eq!(0, range.gen_start);727assert_eq!(30, range.gen_end);728let positions = &range.positions;729assert_eq!(2, positions.len());730assert_eq!(12, positions[0].wasm_pos);731assert_eq!(5, positions[0].gen_start);732assert_eq!(8, positions[0].gen_end);733assert_eq!(17, positions[1].wasm_pos);734assert_eq!(15, positions[1].gen_start);735assert_eq!(23, positions[1].gen_end);736}737738#[test]739fn test_build_function_lookup_two_ranges() {740let mut input = create_simple_func(11);741// append instruction with same srcloc as input.instructions[0]742let mut list = Vec::from(mem::take(&mut input.instructions));743list.push(InstructionAddressMap {744srcloc: FilePos::new(11 + 2),745code_offset: 23,746});747list.push(InstructionAddressMap {748srcloc: FilePos::default(),749code_offset: 26,750});751input.instructions = list.into();752let (start, end, lookup) = build_function_lookup(&input, 1);753assert_eq!(10, start);754assert_eq!(20, end);755756assert_eq!(2, lookup.index.len());757let index_entries = Vec::from_iter(lookup.index);758assert_eq!((10u64, vec![0].into_boxed_slice()), index_entries[0]);759assert_eq!((12u64, vec![0, 1].into_boxed_slice()), index_entries[1]);760assert_eq!(2, lookup.ranges.len());761762let range = &lookup.ranges[0];763assert_eq!(10, range.wasm_start);764assert_eq!(17, range.wasm_end);765assert_eq!(0, range.gen_start);766assert_eq!(23, range.gen_end);767let positions = &range.positions;768assert_eq!(2, positions.len());769assert_eq!(12, positions[0].wasm_pos);770assert_eq!(5, positions[0].gen_start);771assert_eq!(8, positions[0].gen_end);772assert_eq!(17, positions[1].wasm_pos);773assert_eq!(15, positions[1].gen_start);774assert_eq!(23, positions[1].gen_end);775776let range = &lookup.ranges[1];777assert_eq!(12, range.wasm_start);778assert_eq!(20, range.wasm_end);779assert_eq!(23, range.gen_start);780assert_eq!(30, range.gen_end);781let positions = &range.positions;782assert_eq!(1, positions.len());783assert_eq!(12, positions[0].wasm_pos);784assert_eq!(23, positions[0].gen_start);785assert_eq!(26, positions[0].gen_end);786}787788#[test]789fn test_addr_translate() {790// Ignore this test if cranelift doesn't support the native platform.791if cranelift_native::builder().is_err() {792return;793}794let func = CompiledFunctionMetadata {795address_map: create_simple_func(11),796..Default::default()797};798let input = PrimaryMap::from_iter([&func]);799let at = AddressTransform::mock(800&input,801WasmFileInfo {802path: None,803code_section_offset: 1,804imported_func_count: 0,805funcs: Vec::new(),806},807);808809let addr = at.translate(10);810assert_eq!(811Some(Address::Symbol {812symbol: 0,813addend: 0,814}),815addr816);817818let addr = at.translate(20);819assert_eq!(820Some(Address::Symbol {821symbol: 0,822addend: 30,823}),824addr825);826827let addr = at.translate(0);828assert_eq!(None, addr);829830let addr = at.translate(12);831assert_eq!(832Some(Address::Symbol {833symbol: 0,834addend: 5,835}),836addr837);838839let addr = at.translate(18);840assert_eq!(841Some(Address::Symbol {842symbol: 0,843addend: 23,844}),845addr846);847}848}849850851