Move address maps to a section of the compiled image (#3240)
This commit moves the `address_map` field of `FunctionInfo` into a custom-encoded section of the executable. The goal of this commit is, as previous commits, to push less data through `bincode`. The `address_map` field is actually extremely large and has huge benefits of not being decoded when we load a module. This data is only used for traps and such as well, so it's not overly important that it's massaged in to precise data the runtime can extremely speedily use. The `FunctionInfo` type does retain a tiny bit of information about the function itself (it's start source location), but other than that the `FunctionAddressMap` structure is moved from `wasmtime-environ` to `wasmtime-cranelift` since it's now no longer needed outside of that context.
This commit is contained in:
@@ -3,7 +3,7 @@ use crate::func_environ::{get_func_name, FuncEnvironment};
|
|||||||
use crate::obj::ObjectBuilder;
|
use crate::obj::ObjectBuilder;
|
||||||
use crate::{
|
use crate::{
|
||||||
blank_sig, func_signature, indirect_signature, value_type, wasmtime_call_conv,
|
blank_sig, func_signature, indirect_signature, value_type, wasmtime_call_conv,
|
||||||
CompiledFunction, Relocation, RelocationTarget,
|
CompiledFunction, FunctionAddressMap, Relocation, RelocationTarget,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags};
|
use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags};
|
||||||
@@ -26,7 +26,7 @@ use std::convert::TryFrom;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
CompileError, FilePos, FlagValue, FunctionAddressMap, FunctionBodyData, FunctionInfo,
|
AddressMapSection, CompileError, FilePos, FlagValue, FunctionBodyData, FunctionInfo,
|
||||||
InstructionAddressMap, Module, ModuleTranslation, StackMapInformation, TrapCode,
|
InstructionAddressMap, Module, ModuleTranslation, StackMapInformation, TrapCode,
|
||||||
TrapInformation, Tunables, TypeTables, VMOffsets,
|
TrapInformation, Tunables, TypeTables, VMOffsets,
|
||||||
};
|
};
|
||||||
@@ -209,10 +209,11 @@ impl wasmtime_environ::Compiler for Compiler {
|
|||||||
stack_slots: context.func.stack_slots,
|
stack_slots: context.func.stack_slots,
|
||||||
unwind_info,
|
unwind_info,
|
||||||
info: FunctionInfo {
|
info: FunctionInfo {
|
||||||
address_map: address_transform,
|
|
||||||
traps: trap_sink.traps,
|
traps: trap_sink.traps,
|
||||||
|
start_srcloc: address_transform.start_srcloc,
|
||||||
stack_maps: stack_map_sink.finish(),
|
stack_maps: stack_map_sink.finish(),
|
||||||
},
|
},
|
||||||
|
address_map: address_transform,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,9 +248,11 @@ impl wasmtime_environ::Compiler for Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut builder = ObjectBuilder::new(obj, &translation.module);
|
let mut builder = ObjectBuilder::new(obj, &translation.module);
|
||||||
|
let mut addrs = AddressMapSection::default();
|
||||||
|
|
||||||
for (i, func) in funcs.iter() {
|
for (i, func) in funcs.iter() {
|
||||||
builder.func(i, func);
|
let range = builder.func(i, func);
|
||||||
|
addrs.push(range, &func.address_map.instructions);
|
||||||
}
|
}
|
||||||
for (i, func) in trampolines.iter() {
|
for (i, func) in trampolines.iter() {
|
||||||
builder.trampoline(*i, func);
|
builder.trampoline(*i, func);
|
||||||
@@ -287,6 +290,8 @@ impl wasmtime_environ::Compiler for Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder.finish(&*self.isa)?;
|
builder.finish(&*self.isa)?;
|
||||||
|
addrs.append_to(obj);
|
||||||
|
|
||||||
Ok(funcs.into_iter().map(|(_, f)| f.info).collect())
|
Ok(funcs.into_iter().map(|(_, f)| f.info).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +532,7 @@ impl Compiler {
|
|||||||
stack_slots: Default::default(),
|
stack_slots: Default::default(),
|
||||||
value_labels_ranges: Default::default(),
|
value_labels_ranges: Default::default(),
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
|
address_map: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use crate::CompiledFunctions;
|
use crate::{CompiledFunctions, FunctionAddressMap};
|
||||||
use gimli::write;
|
use gimli::write;
|
||||||
use more_asserts::assert_le;
|
use more_asserts::assert_le;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{DefinedFuncIndex, EntityRef, FilePos, PrimaryMap, WasmFileInfo};
|
||||||
DefinedFuncIndex, EntityRef, FilePos, FunctionAddressMap, PrimaryMap, WasmFileInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type GeneratedAddress = usize;
|
pub type GeneratedAddress = usize;
|
||||||
pub type WasmAddress = u64;
|
pub type WasmAddress = u64;
|
||||||
@@ -199,7 +197,7 @@ fn build_function_addr_map(
|
|||||||
) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
|
) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
|
||||||
let mut map = PrimaryMap::new();
|
let mut map = PrimaryMap::new();
|
||||||
for (_, f) in funcs {
|
for (_, f) in funcs {
|
||||||
let ft = &f.info.address_map;
|
let ft = &f.address_map;
|
||||||
let mut fn_map = Vec::new();
|
let mut fn_map = Vec::new();
|
||||||
for t in ft.instructions.iter() {
|
for t in ft.instructions.iter() {
|
||||||
if t.srcloc.file_offset().is_none() {
|
if t.srcloc.file_offset().is_none() {
|
||||||
@@ -460,7 +458,7 @@ impl AddressTransform {
|
|||||||
|
|
||||||
let mut func = BTreeMap::new();
|
let mut func = BTreeMap::new();
|
||||||
for (i, f) in funcs {
|
for (i, f) in funcs {
|
||||||
let ft = &f.info.address_map;
|
let ft = &f.address_map;
|
||||||
let (fn_start, fn_end, lookup) = build_function_lookup(ft, code_section_offset);
|
let (fn_start, fn_end, lookup) = build_function_lookup(ft, code_section_offset);
|
||||||
|
|
||||||
func.insert(
|
func.insert(
|
||||||
@@ -611,14 +609,12 @@ impl AddressTransform {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
|
use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
|
||||||
use crate::{CompiledFunction, CompiledFunctions};
|
use crate::{CompiledFunction, CompiledFunctions, FunctionAddressMap};
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use gimli::write::Address;
|
use gimli::write::Address;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{FilePos, InstructionAddressMap, WasmFileInfo};
|
||||||
FilePos, FunctionAddressMap, FunctionInfo, InstructionAddressMap, WasmFileInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_wasm_code_offset() {
|
fn test_get_wasm_code_offset() {
|
||||||
@@ -660,11 +656,8 @@ mod tests {
|
|||||||
|
|
||||||
fn create_simple_module(address_map: FunctionAddressMap) -> CompiledFunctions {
|
fn create_simple_module(address_map: FunctionAddressMap) -> CompiledFunctions {
|
||||||
PrimaryMap::from_iter(vec![CompiledFunction {
|
PrimaryMap::from_iter(vec![CompiledFunction {
|
||||||
info: FunctionInfo {
|
|
||||||
address_map,
|
address_map,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -849,7 +849,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use crate::CompiledFunction;
|
use crate::CompiledFunction;
|
||||||
use gimli::{self, constants, Encoding, EndianSlice, Expression, RunTimeEndian};
|
use gimli::{self, constants, Encoding, EndianSlice, Expression, RunTimeEndian};
|
||||||
use wasmtime_environ::{FilePos, FunctionInfo};
|
use wasmtime_environ::FilePos;
|
||||||
|
|
||||||
macro_rules! dw_op {
|
macro_rules! dw_op {
|
||||||
(DW_OP_WASM_location) => {
|
(DW_OP_WASM_location) => {
|
||||||
@@ -1177,13 +1177,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_mock_address_transform() -> AddressTransform {
|
fn create_mock_address_transform() -> AddressTransform {
|
||||||
|
use crate::FunctionAddressMap;
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
|
use wasmtime_environ::InstructionAddressMap;
|
||||||
use wasmtime_environ::WasmFileInfo;
|
use wasmtime_environ::WasmFileInfo;
|
||||||
use wasmtime_environ::{FunctionAddressMap, InstructionAddressMap};
|
|
||||||
let mut module_map = PrimaryMap::new();
|
let mut module_map = PrimaryMap::new();
|
||||||
let code_section_offset: u32 = 100;
|
let code_section_offset: u32 = 100;
|
||||||
module_map.push(CompiledFunction {
|
module_map.push(CompiledFunction {
|
||||||
info: FunctionInfo {
|
|
||||||
address_map: FunctionAddressMap {
|
address_map: FunctionAddressMap {
|
||||||
instructions: vec![
|
instructions: vec![
|
||||||
InstructionAddressMap {
|
InstructionAddressMap {
|
||||||
@@ -1210,8 +1210,6 @@ mod tests {
|
|||||||
body_len: 30,
|
body_len: 30,
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
});
|
});
|
||||||
let fi = WasmFileInfo {
|
let fi = WasmFileInfo {
|
||||||
code_section_offset: code_section_offset.into(),
|
code_section_offset: code_section_offset.into(),
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ use cranelift_codegen::isa::{unwind::UnwindInfo, CallConv, TargetIsa};
|
|||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmFuncType, WasmType};
|
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmFuncType, WasmType};
|
||||||
use target_lexicon::CallingConvention;
|
use target_lexicon::CallingConvention;
|
||||||
use wasmtime_environ::{FunctionInfo, Module, TypeTables};
|
use wasmtime_environ::{FilePos, FunctionInfo, InstructionAddressMap, Module, TypeTables};
|
||||||
|
|
||||||
pub use builder::builder;
|
pub use builder::builder;
|
||||||
|
|
||||||
@@ -118,6 +118,10 @@ pub struct CompiledFunction {
|
|||||||
/// The unwind information.
|
/// The unwind information.
|
||||||
unwind_info: Option<UnwindInfo>,
|
unwind_info: Option<UnwindInfo>,
|
||||||
|
|
||||||
|
/// Information used to translate from binary offsets back to the original
|
||||||
|
/// location found in the wasm input.
|
||||||
|
address_map: FunctionAddressMap,
|
||||||
|
|
||||||
relocations: Vec<Relocation>,
|
relocations: Vec<Relocation>,
|
||||||
value_labels_ranges: cranelift_codegen::ValueLabelsRanges,
|
value_labels_ranges: cranelift_codegen::ValueLabelsRanges,
|
||||||
stack_slots: ir::StackSlots,
|
stack_slots: ir::StackSlots,
|
||||||
@@ -125,6 +129,32 @@ pub struct CompiledFunction {
|
|||||||
info: FunctionInfo,
|
info: FunctionInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Function and its instructions addresses mappings.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
struct FunctionAddressMap {
|
||||||
|
/// An array of data for the instructions in this function, indicating where
|
||||||
|
/// each instruction maps back to in the original function.
|
||||||
|
///
|
||||||
|
/// This array is sorted least-to-greatest by the `code_offset` field.
|
||||||
|
/// Additionally the span of each `InstructionAddressMap` is implicitly the
|
||||||
|
/// gap between it and the next item in the array.
|
||||||
|
instructions: Box<[InstructionAddressMap]>,
|
||||||
|
|
||||||
|
/// Function's initial offset in the source file, specified in bytes from
|
||||||
|
/// the front of the file.
|
||||||
|
start_srcloc: FilePos,
|
||||||
|
|
||||||
|
/// Function's end offset in the source file, specified in bytes from
|
||||||
|
/// the front of the file.
|
||||||
|
end_srcloc: FilePos,
|
||||||
|
|
||||||
|
/// Generated function body offset if applicable, otherwise 0.
|
||||||
|
body_offset: usize,
|
||||||
|
|
||||||
|
/// Generated function body length.
|
||||||
|
body_len: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// A record of a relocation to perform.
|
/// A record of a relocation to perform.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
struct Relocation {
|
struct Relocation {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ use object::{
|
|||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::ops::Range;
|
||||||
use wasmtime_environ::obj;
|
use wasmtime_environ::obj;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
DefinedFuncIndex, EntityRef, FuncIndex, Module, PrimaryMap, SignatureIndex,
|
DefinedFuncIndex, EntityRef, FuncIndex, Module, PrimaryMap, SignatureIndex,
|
||||||
@@ -157,7 +158,11 @@ impl<'a> ObjectBuilder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_func(&mut self, name: Vec<u8>, func: &'a CompiledFunction) -> SymbolId {
|
/// Appends the `func` specified named `name` to this object.
|
||||||
|
///
|
||||||
|
/// Returns the symbol associated with the function as well as the range
|
||||||
|
/// that the function resides within the text section.
|
||||||
|
fn append_func(&mut self, name: Vec<u8>, func: &'a CompiledFunction) -> (SymbolId, Range<u64>) {
|
||||||
let off = self
|
let off = self
|
||||||
.obj
|
.obj
|
||||||
.append_section_data(self.text_section, &func.body, 1);
|
.append_section_data(self.text_section, &func.body, 1);
|
||||||
@@ -207,15 +212,22 @@ impl<'a> ObjectBuilder<'a> {
|
|||||||
if !func.relocations.is_empty() {
|
if !func.relocations.is_empty() {
|
||||||
self.pending_relocations.push((off, &func.relocations));
|
self.pending_relocations.push((off, &func.relocations));
|
||||||
}
|
}
|
||||||
symbol_id
|
(symbol_id, off..off + func.body.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn func(&mut self, index: DefinedFuncIndex, func: &'a CompiledFunction) {
|
/// Pushes a new defined function from the a wasm module into this object,
|
||||||
|
/// returning the range that the compiled code will live at relative in the
|
||||||
|
/// text section of the final executable.
|
||||||
|
///
|
||||||
|
/// Note that functions must be pushed in the order of their
|
||||||
|
/// `DefinedFuncIndex`.
|
||||||
|
pub fn func(&mut self, index: DefinedFuncIndex, func: &'a CompiledFunction) -> Range<u64> {
|
||||||
assert_eq!(self.jump_tables.push(&func.jt_offsets), index);
|
assert_eq!(self.jump_tables.push(&func.jt_offsets), index);
|
||||||
let index = self.module.func_index(index);
|
let index = self.module.func_index(index);
|
||||||
let name = obj::func_symbol_name(index);
|
let name = obj::func_symbol_name(index);
|
||||||
let symbol_id = self.append_func(name.into_bytes(), func);
|
let (symbol_id, range) = self.append_func(name.into_bytes(), func);
|
||||||
assert_eq!(self.func_symbols.push(symbol_id), index);
|
assert_eq!(self.func_symbols.push(symbol_id), index);
|
||||||
|
range
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trampoline(&mut self, sig: SignatureIndex, func: &'a CompiledFunction) {
|
pub fn trampoline(&mut self, sig: SignatureIndex, func: &'a CompiledFunction) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ log = { version = "0.4.8", default-features = false }
|
|||||||
more-asserts = "0.2.1"
|
more-asserts = "0.2.1"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
gimli = { version = "0.25.0", default-features = false, features = ['read'] }
|
gimli = { version = "0.25.0", default-features = false, features = ['read'] }
|
||||||
object = { version = "0.26.0", default-features = false, features = ['write_core', 'elf'] }
|
object = { version = "0.26.0", default-features = false, features = ['read_core', 'write_core', 'elf'] }
|
||||||
target-lexicon = "0.12"
|
target-lexicon = "0.12"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
//! Data structures to provide transformation of the source
|
//! Data structures to provide transformation of the source
|
||||||
// addresses of a WebAssembly module into the native code.
|
// addresses of a WebAssembly module into the native code.
|
||||||
|
|
||||||
|
use object::write::{Object, StandardSegment};
|
||||||
|
use object::{Bytes, LittleEndian, SectionKind, U32Bytes};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
/// Single source location to generated address mapping.
|
/// Single source location to generated address mapping.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -15,32 +19,6 @@ pub struct InstructionAddressMap {
|
|||||||
pub code_offset: u32,
|
pub code_offset: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function and its instructions addresses mappings.
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
|
||||||
pub struct FunctionAddressMap {
|
|
||||||
/// An array of data for the instructions in this function, indicating where
|
|
||||||
/// each instruction maps back to in the original function.
|
|
||||||
///
|
|
||||||
/// This array is sorted least-to-greatest by the `code_offset` field.
|
|
||||||
/// Additionally the span of each `InstructionAddressMap` is implicitly the
|
|
||||||
/// gap between it and the next item in the array.
|
|
||||||
pub instructions: Box<[InstructionAddressMap]>,
|
|
||||||
|
|
||||||
/// Function's initial offset in the source file, specified in bytes from
|
|
||||||
/// the front of the file.
|
|
||||||
pub start_srcloc: FilePos,
|
|
||||||
|
|
||||||
/// Function's end offset in the source file, specified in bytes from
|
|
||||||
/// the front of the file.
|
|
||||||
pub end_srcloc: FilePos,
|
|
||||||
|
|
||||||
/// Generated function body offset if applicable, otherwise 0.
|
|
||||||
pub body_offset: usize,
|
|
||||||
|
|
||||||
/// Generated function body length.
|
|
||||||
pub body_len: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A position within an original source file,
|
/// A position within an original source file,
|
||||||
///
|
///
|
||||||
/// This structure is used as a newtype wrapper around a 32-bit integer which
|
/// This structure is used as a newtype wrapper around a 32-bit integer which
|
||||||
@@ -74,3 +52,140 @@ impl Default for FilePos {
|
|||||||
FilePos(u32::MAX)
|
FilePos(u32::MAX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder for the address map section of a wasmtime compilation image.
|
||||||
|
///
|
||||||
|
/// This builder is used to conveniently built the `ELF_WASMTIME_ADDRMAP`
|
||||||
|
/// section by compilers, and provides utilities to directly insert the results
|
||||||
|
/// into an `Object`.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct AddressMapSection {
|
||||||
|
offsets: Vec<U32Bytes<LittleEndian>>,
|
||||||
|
positions: Vec<U32Bytes<LittleEndian>>,
|
||||||
|
last_offset: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A custom Wasmtime-specific section of our compilation image which stores
|
||||||
|
/// mapping data from offsets in the image to offset in the original wasm
|
||||||
|
/// binary.
|
||||||
|
///
|
||||||
|
/// This section has a custom binary encoding. Currently its encoding is:
|
||||||
|
///
|
||||||
|
/// * The section starts with a 32-bit little-endian integer. This integer is
|
||||||
|
/// how many entries are in the following two arrays.
|
||||||
|
/// * Next is an array with the previous count number of 32-bit little-endian
|
||||||
|
/// integers. This array is a sorted list of relative offsets within the text
|
||||||
|
/// section. This is intended to be a lookup array to perform a binary search
|
||||||
|
/// on an offset within the text section on this array.
|
||||||
|
/// * Finally there is another array, with the same count as before, also of
|
||||||
|
/// 32-bit little-endian integers. These integers map 1:1 with the previous
|
||||||
|
/// array of offsets, and correspond to what the original offset was in the
|
||||||
|
/// wasm file.
|
||||||
|
///
|
||||||
|
/// Decoding this section is intentionally simple, it only requires loading a
|
||||||
|
/// 32-bit little-endian integer plus some bounds checks. Reading this section
|
||||||
|
/// is done with the `lookup_file_pos` function below. Reading involves
|
||||||
|
/// performing a binary search on the first array using the index found for the
|
||||||
|
/// native code offset to index into the second array and find the wasm code
|
||||||
|
/// offset.
|
||||||
|
///
|
||||||
|
/// At this time this section has an alignment of 1, which means all reads of it
|
||||||
|
/// are unaligned. Additionally at this time the 32-bit encodings chosen here
|
||||||
|
/// mean that >=4gb text sections are not supported.
|
||||||
|
pub const ELF_WASMTIME_ADDRMAP: &str = ".wasmtime.addrmap";
|
||||||
|
|
||||||
|
impl AddressMapSection {
|
||||||
|
/// Pushes a new set of instruction mapping information for a function added
|
||||||
|
/// in the exectuable.
|
||||||
|
///
|
||||||
|
/// The `func` argument here is the range of the function, relative to the
|
||||||
|
/// start of the text section in the executable. The `instrs` provided are
|
||||||
|
/// the descriptors for instructions in the function and their various
|
||||||
|
/// mappings back to original source positions.
|
||||||
|
///
|
||||||
|
/// This is required to be called for `func` values that are strictly
|
||||||
|
/// increasing in addresses (e.g. as the object is built). Additionally the
|
||||||
|
/// `instrs` map must be sorted based on code offset in the native text
|
||||||
|
/// section.
|
||||||
|
pub fn push(&mut self, func: Range<u64>, instrs: &[InstructionAddressMap]) {
|
||||||
|
// NB: for now this only supports <=4GB text sections in object files.
|
||||||
|
// Alternative schemes will need to be created for >32-bit offsets to
|
||||||
|
// avoid making this section overly large.
|
||||||
|
let func_start = u32::try_from(func.start).unwrap();
|
||||||
|
let func_end = u32::try_from(func.end).unwrap();
|
||||||
|
|
||||||
|
self.offsets.reserve(instrs.len());
|
||||||
|
self.positions.reserve(instrs.len());
|
||||||
|
for map in instrs {
|
||||||
|
// Sanity-check to ensure that functions are pushed in-order, otherwise
|
||||||
|
// the `offsets` array won't be sorted which is our goal.
|
||||||
|
let pos = func_start + map.code_offset;
|
||||||
|
assert!(pos >= self.last_offset);
|
||||||
|
self.offsets.push(U32Bytes::new(LittleEndian, pos));
|
||||||
|
self.positions
|
||||||
|
.push(U32Bytes::new(LittleEndian, map.srcloc.0));
|
||||||
|
self.last_offset = pos;
|
||||||
|
}
|
||||||
|
self.last_offset = func_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishes encoding this section into the `Object` provided.
|
||||||
|
pub fn append_to(self, obj: &mut Object) {
|
||||||
|
let section = obj.add_section(
|
||||||
|
obj.segment_name(StandardSegment::Data).to_vec(),
|
||||||
|
ELF_WASMTIME_ADDRMAP.as_bytes().to_vec(),
|
||||||
|
SectionKind::ReadOnlyData,
|
||||||
|
);
|
||||||
|
|
||||||
|
// NB: this matches the encoding expected by `lookup` below.
|
||||||
|
let amt = u32::try_from(self.offsets.len()).unwrap();
|
||||||
|
obj.append_section_data(section, &amt.to_le_bytes(), 1);
|
||||||
|
obj.append_section_data(section, object::bytes_of_slice(&self.offsets), 1);
|
||||||
|
obj.append_section_data(section, object::bytes_of_slice(&self.positions), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup an `offset` within an encoded address map section, returning the
|
||||||
|
/// original `FilePos` that corresponds to the offset, if found.
|
||||||
|
///
|
||||||
|
/// This function takes a `section` as its first argument which must have been
|
||||||
|
/// created with `AddressMapSection` above. This is intended to be the raw
|
||||||
|
/// `ELF_WASMTIME_ADDRMAP` section from the compilation artifact.
|
||||||
|
///
|
||||||
|
/// The `offset` provided is a relative offset from the start of the text
|
||||||
|
/// section of the pc that is being looked up. If `offset` is out of range or
|
||||||
|
/// doesn't correspond to anything in this file then `None` is returned.
|
||||||
|
pub fn lookup_file_pos(section: &[u8], offset: usize) -> Option<FilePos> {
|
||||||
|
let mut section = Bytes(section);
|
||||||
|
// NB: this matches the encoding written by `append_to` above.
|
||||||
|
let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
|
||||||
|
let count = usize::try_from(count.get(LittleEndian)).ok()?;
|
||||||
|
let (offsets, section) =
|
||||||
|
object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
|
||||||
|
let (positions, section) =
|
||||||
|
object::slice_from_bytes::<U32Bytes<LittleEndian>>(section, count).ok()?;
|
||||||
|
debug_assert!(section.is_empty());
|
||||||
|
|
||||||
|
// First perform a binary search on the `offsets` array. This is a sorted
|
||||||
|
// array of offsets within the text section, which is conveniently what our
|
||||||
|
// `offset` also is. Note that we are somewhat unlikely to find a precise
|
||||||
|
// match on the element in the array, so we're largely interested in which
|
||||||
|
// "bucket" the `offset` falls into.
|
||||||
|
let offset = u32::try_from(offset).ok()?;
|
||||||
|
let index = match offsets.binary_search_by_key(&offset, |v| v.get(LittleEndian)) {
|
||||||
|
// Exact hit!
|
||||||
|
Ok(i) => i,
|
||||||
|
|
||||||
|
// This *would* be at the first slot in the array, so no
|
||||||
|
// instructions cover `pc`.
|
||||||
|
Err(0) => return None,
|
||||||
|
|
||||||
|
// This would be at the `nth` slot, so we're at the `n-1`th slot.
|
||||||
|
Err(n) => n - 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Using the `index` we found of which bucket `offset` corresponds to we can
|
||||||
|
// lookup the actual `FilePos` value in the `positions` array.
|
||||||
|
let pos = positions.get(index)?;
|
||||||
|
Some(FilePos(pos.get(LittleEndian)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
//! module.
|
//! module.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DefinedFuncIndex, FunctionAddressMap, FunctionBodyData, ModuleTranslation, PrimaryMap,
|
DefinedFuncIndex, FilePos, FunctionBodyData, ModuleTranslation, PrimaryMap, StackMap, Tunables,
|
||||||
StackMap, Tunables, TypeTables, WasmError, WasmFuncType,
|
TypeTables, WasmError, WasmFuncType,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use object::write::Object;
|
use object::write::Object;
|
||||||
@@ -21,7 +21,7 @@ use thiserror::Error;
|
|||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub struct FunctionInfo {
|
pub struct FunctionInfo {
|
||||||
pub traps: Vec<TrapInformation>,
|
pub traps: Vec<TrapInformation>,
|
||||||
pub address_map: FunctionAddressMap,
|
pub start_srcloc: FilePos,
|
||||||
pub stack_maps: Vec<StackMapInformation>,
|
pub stack_maps: Vec<StackMapInformation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use thiserror::Error;
|
|||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
CompileError, DefinedFuncIndex, FunctionInfo, InstanceSignature, InstanceTypeIndex, Module,
|
CompileError, DefinedFuncIndex, FunctionInfo, InstanceSignature, InstanceTypeIndex, Module,
|
||||||
ModuleSignature, ModuleTranslation, ModuleTypeIndex, PrimaryMap, SignatureIndex,
|
ModuleSignature, ModuleTranslation, ModuleTypeIndex, PrimaryMap, SignatureIndex,
|
||||||
StackMapInformation, Tunables, WasmFuncType,
|
StackMapInformation, Tunables, WasmFuncType, ELF_WASMTIME_ADDRMAP,
|
||||||
};
|
};
|
||||||
use wasmtime_runtime::{GdbJitImageRegistration, InstantiationError, VMFunctionBody, VMTrampoline};
|
use wasmtime_runtime::{GdbJitImageRegistration, InstantiationError, VMFunctionBody, VMTrampoline};
|
||||||
|
|
||||||
@@ -268,6 +268,7 @@ impl ModuleCode {
|
|||||||
/// A compiled wasm module, ready to be instantiated.
|
/// A compiled wasm module, ready to be instantiated.
|
||||||
pub struct CompiledModule {
|
pub struct CompiledModule {
|
||||||
wasm_data: Range<usize>,
|
wasm_data: Range<usize>,
|
||||||
|
address_map_data: Range<usize>,
|
||||||
artifacts: CompilationArtifacts,
|
artifacts: CompilationArtifacts,
|
||||||
module: Arc<Module>,
|
module: Arc<Module>,
|
||||||
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
||||||
@@ -313,6 +314,7 @@ impl CompiledModule {
|
|||||||
let module = Arc::new(info.module);
|
let module = Arc::new(info.module);
|
||||||
let funcs = info.funcs;
|
let funcs = info.funcs;
|
||||||
let wasm_data = subslice_range(section(ELF_WASM_DATA)?, &artifacts.obj);
|
let wasm_data = subslice_range(section(ELF_WASM_DATA)?, &artifacts.obj);
|
||||||
|
let address_map_data = subslice_range(section(ELF_WASMTIME_ADDRMAP)?, &artifacts.obj);
|
||||||
|
|
||||||
// Allocate all of the compiled functions into executable memory,
|
// Allocate all of the compiled functions into executable memory,
|
||||||
// copying over their contents.
|
// copying over their contents.
|
||||||
@@ -334,6 +336,7 @@ impl CompiledModule {
|
|||||||
module,
|
module,
|
||||||
artifacts,
|
artifacts,
|
||||||
wasm_data,
|
wasm_data,
|
||||||
|
address_map_data,
|
||||||
code: Arc::new(ModuleCode {
|
code: Arc::new(ModuleCode {
|
||||||
range: (start, end),
|
range: (start, end),
|
||||||
code_memory,
|
code_memory,
|
||||||
@@ -384,6 +387,12 @@ impl CompiledModule {
|
|||||||
&self.artifacts.obj[self.wasm_data.clone()]
|
&self.artifacts.obj[self.wasm_data.clone()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the encoded address map section used to pass to
|
||||||
|
/// `wasmtime_environ::lookup_file_pos`.
|
||||||
|
pub fn address_map_data(&self) -> &[u8] {
|
||||||
|
&self.artifacts.obj[self.address_map_data.clone()]
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a reference-counting pointer to a module.
|
/// Return a reference-counting pointer to a module.
|
||||||
pub fn module(&self) -> &Arc<Module> {
|
pub fn module(&self) -> &Arc<Module> {
|
||||||
&self.module
|
&self.module
|
||||||
@@ -421,10 +430,13 @@ impl CompiledModule {
|
|||||||
|
|
||||||
/// Lookups a defined function by a program counter value.
|
/// Lookups a defined function by a program counter value.
|
||||||
///
|
///
|
||||||
/// Returns the defined function index, the start address, and the end address (exclusive).
|
/// Returns the defined function index and the relative address of
|
||||||
pub fn func_by_pc(&self, pc: usize) -> Option<(DefinedFuncIndex, usize, usize)> {
|
/// `text_offfset` within the function itself.
|
||||||
|
pub fn func_by_text_offset(&self, text_offset: usize) -> Option<(DefinedFuncIndex, u32)> {
|
||||||
let functions = self.finished_functions();
|
let functions = self.finished_functions();
|
||||||
|
|
||||||
|
let text_section = self.code().range().0;
|
||||||
|
let pc = text_section + text_offset;
|
||||||
let index = match functions.binary_search_values_by_key(&pc, |body| unsafe {
|
let index = match functions.binary_search_values_by_key(&pc, |body| unsafe {
|
||||||
debug_assert!(!(**body).is_empty());
|
debug_assert!(!(**body).is_empty());
|
||||||
// Return the inclusive "end" of the function
|
// Return the inclusive "end" of the function
|
||||||
@@ -453,7 +465,7 @@ impl CompiledModule {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((index, start, end))
|
Some((index, (text_offset - (start - text_section)) as u32))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the function information for a given function index.
|
/// Gets the function information for a given function index.
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ use std::{
|
|||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
};
|
};
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{EntityRef, FilePos, StackMap, TrapInformation};
|
||||||
DefinedFuncIndex, EntityRef, FilePos, FunctionAddressMap, StackMap, TrapInformation,
|
|
||||||
};
|
|
||||||
use wasmtime_jit::CompiledModule;
|
use wasmtime_jit::CompiledModule;
|
||||||
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
|
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
|
||||||
|
|
||||||
@@ -15,11 +13,6 @@ lazy_static::lazy_static! {
|
|||||||
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
|
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn func_by_pc(module: &CompiledModule, pc: usize) -> Option<(DefinedFuncIndex, u32)> {
|
|
||||||
let (index, start, _) = module.func_by_pc(pc)?;
|
|
||||||
Some((index, (pc - start) as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for registering modules with a store.
|
/// Used for registering modules with a store.
|
||||||
///
|
///
|
||||||
/// The map is from the ending (exclusive) address for the module code to
|
/// The map is from the ending (exclusive) address for the module code to
|
||||||
@@ -121,31 +114,10 @@ struct RegisteredModule {
|
|||||||
signatures: Arc<SignatureCollection>,
|
signatures: Arc<SignatureCollection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisteredModule {
|
|
||||||
fn instr_pos(offset: u32, addr_map: &FunctionAddressMap) -> Option<usize> {
|
|
||||||
// Use our relative position from the start of the function to find the
|
|
||||||
// machine instruction that corresponds to `pc`, which then allows us to
|
|
||||||
// map that to a wasm original source location.
|
|
||||||
match addr_map
|
|
||||||
.instructions
|
|
||||||
.binary_search_by_key(&offset, |map| map.code_offset)
|
|
||||||
{
|
|
||||||
// Exact hit!
|
|
||||||
Ok(pos) => Some(pos),
|
|
||||||
|
|
||||||
// This *would* be at the first slot in the array, so no
|
|
||||||
// instructions cover `pc`.
|
|
||||||
Err(0) => None,
|
|
||||||
|
|
||||||
// This would be at the `nth` slot, so we're at the `n-1`th slot.
|
|
||||||
Err(n) => Some(n - 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleInfo for RegisteredModule {
|
impl ModuleInfo for RegisteredModule {
|
||||||
fn lookup_stack_map(&self, pc: usize) -> Option<&StackMap> {
|
fn lookup_stack_map(&self, pc: usize) -> Option<&StackMap> {
|
||||||
let (index, offset) = func_by_pc(&self.module, pc)?;
|
let text_offset = pc - self.start;
|
||||||
|
let (index, func_offset) = self.module.func_by_text_offset(text_offset)?;
|
||||||
let info = self.module.func_info(index);
|
let info = self.module.func_info(index);
|
||||||
|
|
||||||
// Do a binary search to find the stack map for the given offset.
|
// Do a binary search to find the stack map for the given offset.
|
||||||
@@ -192,7 +164,7 @@ impl ModuleInfo for RegisteredModule {
|
|||||||
|
|
||||||
let index = match info
|
let index = match info
|
||||||
.stack_maps
|
.stack_maps
|
||||||
.binary_search_by_key(&offset, |i| i.code_offset)
|
.binary_search_by_key(&func_offset, |i| i.code_offset)
|
||||||
{
|
{
|
||||||
// Exact hit.
|
// Exact hit.
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
@@ -246,23 +218,22 @@ impl GlobalModuleRegistry {
|
|||||||
let modules = GLOBAL_MODULES.read().unwrap();
|
let modules = GLOBAL_MODULES.read().unwrap();
|
||||||
|
|
||||||
match modules.module(pc) {
|
match modules.module(pc) {
|
||||||
Some(entry) => match func_by_pc(&entry.module, pc) {
|
Some((entry, text_offset)) => {
|
||||||
Some((index, offset)) => {
|
wasmtime_environ::lookup_file_pos(entry.module.address_map_data(), text_offset)
|
||||||
let info = entry.module.func_info(index);
|
.is_some()
|
||||||
RegisteredModule::instr_pos(offset, &info.address_map).is_some()
|
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
},
|
|
||||||
None => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module(&self, pc: usize) -> Option<&GlobalRegisteredModule> {
|
/// Returns, if found, the corresponding module for the `pc` as well as the
|
||||||
|
/// pc transformed to a relative offset within the text section.
|
||||||
|
fn module(&self, pc: usize) -> Option<(&GlobalRegisteredModule, usize)> {
|
||||||
let (end, info) = self.0.range(pc..).next()?;
|
let (end, info) = self.0.range(pc..).next()?;
|
||||||
if pc < info.start || *end < pc {
|
if pc < info.start || *end < pc {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(info)
|
Some((info, pc - info.start))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work with the global instance of `GlobalModuleRegistry`. Note that only
|
// Work with the global instance of `GlobalModuleRegistry`. Note that only
|
||||||
@@ -280,8 +251,8 @@ impl GlobalModuleRegistry {
|
|||||||
/// boolean indicates whether the engine used to compile this module is
|
/// boolean indicates whether the engine used to compile this module is
|
||||||
/// using environment variables to control debuginfo parsing.
|
/// using environment variables to control debuginfo parsing.
|
||||||
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, bool, bool)> {
|
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, bool, bool)> {
|
||||||
let module = self.module(pc)?;
|
let (module, offset) = self.module(pc)?;
|
||||||
module.lookup_frame_info(pc).map(|info| {
|
module.lookup_frame_info(offset).map(|info| {
|
||||||
(
|
(
|
||||||
info,
|
info,
|
||||||
module.has_unparsed_debuginfo(),
|
module.has_unparsed_debuginfo(),
|
||||||
@@ -292,7 +263,8 @@ impl GlobalModuleRegistry {
|
|||||||
|
|
||||||
/// Fetches trap information about a program counter in a backtrace.
|
/// Fetches trap information about a program counter in a backtrace.
|
||||||
pub(crate) fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
|
pub(crate) fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
|
||||||
self.module(pc)?.lookup_trap_info(pc)
|
let (module, offset) = self.module(pc)?;
|
||||||
|
module.lookup_trap_info(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a new region of code, described by `(start, end)` and with
|
/// Registers a new region of code, described by `(start, end)` and with
|
||||||
@@ -336,20 +308,22 @@ impl GlobalRegisteredModule {
|
|||||||
///
|
///
|
||||||
/// Returns an object if this `pc` is known to this module, or returns `None`
|
/// Returns an object if this `pc` is known to this module, or returns `None`
|
||||||
/// if no information can be found.
|
/// if no information can be found.
|
||||||
pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
|
pub fn lookup_frame_info(&self, text_offset: usize) -> Option<FrameInfo> {
|
||||||
let (index, offset) = func_by_pc(&self.module, pc)?;
|
let (index, _func_offset) = self.module.func_by_text_offset(text_offset)?;
|
||||||
let info = self.module.func_info(index);
|
let info = self.module.func_info(index);
|
||||||
let pos = RegisteredModule::instr_pos(offset, &info.address_map);
|
let instr = wasmtime_environ::lookup_file_pos(self.module.address_map_data(), text_offset);
|
||||||
|
|
||||||
// In debug mode for now assert that we found a mapping for `pc` within
|
// In debug mode for now assert that we found a mapping for `pc` within
|
||||||
// the function, because otherwise something is buggy along the way and
|
// the function, because otherwise something is buggy along the way and
|
||||||
// not accounting for all the instructions. This isn't super critical
|
// not accounting for all the instructions. This isn't super critical
|
||||||
// though so we can omit this check in release mode.
|
// though so we can omit this check in release mode.
|
||||||
debug_assert!(pos.is_some(), "failed to find instruction for {:x}", pc);
|
debug_assert!(
|
||||||
|
instr.is_some(),
|
||||||
|
"failed to find instruction for {:#x}",
|
||||||
|
text_offset
|
||||||
|
);
|
||||||
|
|
||||||
let instr = pos
|
let instr = instr.unwrap_or(info.start_srcloc);
|
||||||
.map(|i| info.address_map.instructions[i].srcloc)
|
|
||||||
.unwrap_or(info.address_map.start_srcloc);
|
|
||||||
|
|
||||||
// Use our wasm-relative pc to symbolize this frame. If there's a
|
// Use our wasm-relative pc to symbolize this frame. If there's a
|
||||||
// symbolication context (dwarf debug info) available then we can try to
|
// symbolication context (dwarf debug info) available then we can try to
|
||||||
@@ -393,18 +367,18 @@ impl GlobalRegisteredModule {
|
|||||||
func_index: index.index() as u32,
|
func_index: index.index() as u32,
|
||||||
func_name: module.func_names.get(&index).cloned(),
|
func_name: module.func_names.get(&index).cloned(),
|
||||||
instr,
|
instr,
|
||||||
func_start: info.address_map.start_srcloc,
|
func_start: info.start_srcloc,
|
||||||
symbols,
|
symbols,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches trap information about a program counter in a backtrace.
|
/// Fetches trap information about a program counter in a backtrace.
|
||||||
pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
|
pub fn lookup_trap_info(&self, text_offset: usize) -> Option<&TrapInformation> {
|
||||||
let (index, offset) = func_by_pc(&self.module, pc)?;
|
let (index, func_offset) = self.module.func_by_text_offset(text_offset)?;
|
||||||
let info = self.module.func_info(index);
|
let info = self.module.func_info(index);
|
||||||
let idx = info
|
let idx = info
|
||||||
.traps
|
.traps
|
||||||
.binary_search_by_key(&offset, |info| info.code_offset)
|
.binary_search_by_key(&func_offset, |info| info.code_offset)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
Some(&info.traps[idx])
|
Some(&info.traps[idx])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user