diff --git a/misc/wasmtime-py/src/import.rs b/misc/wasmtime-py/src/import.rs index b9f7eb4b6ced..4db0273a1181 100644 --- a/misc/wasmtime-py/src/import.rs +++ b/misc/wasmtime-py/src/import.rs @@ -326,6 +326,7 @@ pub fn into_instance_from_obj( &data_initializers, signatures.into_boxed_slice(), None, + None, Box::new(import_obj_state), ) .expect("instance")) diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 07a08b7a4355..4e9ed79d9878 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -62,8 +62,8 @@ including calling the start function if one is present. Additional functions given with --invoke are then called. Usage: - wasmtime [-odg] [--enable-simd] [--wasi-c] [--disable-cache | --cache-config=] [--preload=...] [--env=...] [--dir=...] [--mapdir=...] [...] - wasmtime [-odg] [--enable-simd] [--wasi-c] [--disable-cache | --cache-config=] [--env=...] [--dir=...] [--mapdir=...] --invoke= [...] + wasmtime [-odg] [--enable-simd] [--jitdump] [--wasi-c] [--disable-cache | --cache-config=] [--preload=...] [--env=...] [--dir=...] [--mapdir=...] [...] + wasmtime [-odg] [--enable-simd] [--jitdump] [--wasi-c] [--disable-cache | --cache-config=] [--env=...] [--dir=...] [--mapdir=...] --invoke= [...] wasmtime --create-cache-config [--cache-config=] wasmtime --help | --version @@ -81,6 +81,7 @@ Options: -g generate debug information -d, --debug enable debug output on stderr/stdout --enable-simd enable proposed SIMD instructions + --jitdump generate perf jitdump files for runtime generated code --wasi-c enable the wasi-c implementation of WASI --preload= load an additional wasm module before loading the main module --env= pass an environment variable (\"key=value\") to the program @@ -102,6 +103,7 @@ struct Args { flag_debug: bool, flag_g: bool, flag_enable_simd: bool, + flag_jitdump: bool, flag_invoke: Option, flag_preload: Vec, flag_env: Vec, @@ -276,12 +278,20 @@ fn rmain() -> Result<(), Error> { features.simd = true; } + // Enable Jitdump if requested + let perf_profile = args.flag_jitdump; + // Enable optimization if requested. if args.flag_optimize { flag_builder.set("opt_level", "best")?; } - let config = Config::new(settings::Flags::new(flag_builder), features, debug_info); + let config = Config::new( + settings::Flags::new(flag_builder), + features, + debug_info, + perf_profile, + ); let engine = HostRef::new(Engine::new(config)); let store = HostRef::new(Store::new(engine)); diff --git a/tests/instantiate.rs b/tests/instantiate.rs index adf49456887d..109f5d2a2bd3 100644 --- a/tests/instantiate.rs +++ b/tests/instantiate.rs @@ -42,6 +42,13 @@ fn test_environ_translate() { let mut resolver = NullResolver {}; let mut compiler = Compiler::new(isa); let global_exports = Rc::new(RefCell::new(HashMap::new())); - let instance = instantiate(&mut compiler, &data, &mut resolver, global_exports, false); + let instance = instantiate( + &mut compiler, + &data, + &mut resolver, + global_exports, + false, + false, + ); assert!(instance.is_ok()); } diff --git a/wasmtime-api/src/context.rs b/wasmtime-api/src/context.rs index 76085dd86852..012f34885295 100644 --- a/wasmtime-api/src/context.rs +++ b/wasmtime-api/src/context.rs @@ -11,25 +11,41 @@ pub struct Context { compiler: Rc>, features: Features, debug_info: bool, + perf_profile: bool, } impl Context { - pub fn new(compiler: Compiler, features: Features, debug_info: bool) -> Context { + pub fn new( + compiler: Compiler, + features: Features, + debug_info: bool, + perf_profile: bool, + ) -> Context { Context { compiler: Rc::new(RefCell::new(compiler)), features, debug_info, + perf_profile, } } - pub fn create(flags: settings::Flags, features: Features, debug_info: bool) -> Context { - Context::new(create_compiler(flags), features, debug_info) + pub fn create( + flags: settings::Flags, + features: Features, + debug_info: bool, + perf_profile: bool, + ) -> Context { + Context::new(create_compiler(flags), features, debug_info, perf_profile) } pub(crate) fn debug_info(&self) -> bool { self.debug_info } + pub(crate) fn perf_profile(&self) -> bool { + self.perf_profile + } + pub(crate) fn compiler(&mut self) -> RefMut { self.compiler.borrow_mut() } diff --git a/wasmtime-api/src/instance.rs b/wasmtime-api/src/instance.rs index a3bc0274e476..5e4822918fce 100644 --- a/wasmtime-api/src/instance.rs +++ b/wasmtime-api/src/instance.rs @@ -33,6 +33,7 @@ pub fn instantiate_in_context( ) -> Result<(InstanceHandle, HashSet), Error> { let mut contexts = HashSet::new(); let debug_info = context.debug_info(); + let perf_profile = context.perf_profile(); let mut resolver = SimpleResolver { imports }; let instance = instantiate( &mut context.compiler(), @@ -40,6 +41,7 @@ pub fn instantiate_in_context( &mut resolver, exports, debug_info, + perf_profile, )?; contexts.insert(context); Ok((instance, contexts)) diff --git a/wasmtime-api/src/runtime.rs b/wasmtime-api/src/runtime.rs index 61de46a3b684..0a733d30bbfe 100644 --- a/wasmtime-api/src/runtime.rs +++ b/wasmtime-api/src/runtime.rs @@ -21,22 +21,30 @@ pub struct Config { flags: settings::Flags, features: Features, debug_info: bool, + perf_profile: bool, } impl Config { pub fn default() -> Config { Config { debug_info: false, + perf_profile: false, features: Default::default(), flags: default_flags(), } } - pub fn new(flags: settings::Flags, features: Features, debug_info: bool) -> Config { + pub fn new( + flags: settings::Flags, + features: Features, + debug_info: bool, + perf_profile: bool, + ) -> Config { Config { flags, features, debug_info, + perf_profile, } } @@ -44,6 +52,10 @@ impl Config { self.debug_info } + pub(crate) fn perf_profile(&self) -> bool { + self.perf_profile + } + pub(crate) fn flags(&self) -> &settings::Flags { &self.flags } @@ -92,9 +104,10 @@ impl Store { let flags = engine.borrow().config().flags().clone(); let features = engine.borrow().config().features().clone(); let debug_info = engine.borrow().config().debug_info(); + let perf_profile = engine.borrow().config().perf_profile(); Store { engine, - context: Context::create(flags, features, debug_info), + context: Context::create(flags, features, debug_info, perf_profile), global_exports: Rc::new(RefCell::new(HashMap::new())), signature_cache: HashMap::new(), } diff --git a/wasmtime-api/src/trampoline/create_handle.rs b/wasmtime-api/src/trampoline/create_handle.rs index f765eed99362..b55f27964743 100644 --- a/wasmtime-api/src/trampoline/create_handle.rs +++ b/wasmtime-api/src/trampoline/create_handle.rs @@ -53,6 +53,7 @@ pub(crate) fn create_handle( &data_initializers, signatures.into_boxed_slice(), None, + None, state, ) .expect("instance")) diff --git a/wasmtime-jit/src/code_memory.rs b/wasmtime-jit/src/code_memory.rs index 641577ad9d3b..e3bc80b4f837 100644 --- a/wasmtime-jit/src/code_memory.rs +++ b/wasmtime-jit/src/code_memory.rs @@ -5,7 +5,7 @@ use region; use std::boxed::Box; use std::string::String; use std::vec::Vec; -use wasmtime_runtime::{Mmap, VMFunctionBody}; +use wasmtime_runtime::{JitDumpAgent, Mmap, VMFunctionBody}; /// Memory manager for executable code. pub(crate) struct CodeMemory { @@ -99,4 +99,19 @@ impl CodeMemory { } self.published = self.mmaps.len(); } + + pub fn perf_module_load( + &mut self, + module_name: &str, + jit_dump_agent: &JitDumpAgent, + dbg_image: Option<&[u8]>, + ) -> () { + for map in &mut self.mmaps { + if map.len() > 0 { + jit_dump_agent + .clone() + .module_load(module_name, map.as_ptr(), map.len(), dbg_image); + } + } + } } diff --git a/wasmtime-jit/src/compiler.rs b/wasmtime-jit/src/compiler.rs index f1e87ce2d597..20e3f8741422 100644 --- a/wasmtime-jit/src/compiler.rs +++ b/wasmtime-jit/src/compiler.rs @@ -21,8 +21,8 @@ use wasmtime_environ::{ Relocations, Traps, Tunables, VMOffsets, }; use wasmtime_runtime::{ - get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard, - VMFunctionBody, + get_mut_trap_registry, InstantiationError, JitDumpAgent, SignatureRegistry, + TrapRegistrationGuard, VMFunctionBody, }; /// A WebAssembly code JIT compiler. @@ -207,6 +207,16 @@ impl Compiler { self.code_memory.publish(); } + pub(crate) fn perf_module_load( + &mut self, + module_name: &str, + jit_dump_agent: &JitDumpAgent, + dbg_image: Option<&[u8]>, + ) -> () { + self.code_memory + .perf_module_load(module_name, jit_dump_agent, dbg_image); + } + /// Shared signature registry. pub fn signatures(&mut self) -> &mut SignatureRegistry { &mut self.signatures diff --git a/wasmtime-jit/src/context.rs b/wasmtime-jit/src/context.rs index 144f066e75cc..ff35133f1de2 100644 --- a/wasmtime-jit/src/context.rs +++ b/wasmtime-jit/src/context.rs @@ -77,6 +77,7 @@ pub struct Context { compiler: Box, global_exports: Rc>>>, debug_info: bool, + perf_profile: bool, features: Features, } @@ -88,6 +89,7 @@ impl Context { compiler, global_exports: Rc::new(RefCell::new(HashMap::new())), debug_info: false, + perf_profile: false, features: Default::default(), } } @@ -97,6 +99,11 @@ impl Context { self.debug_info } + /// Get debug_info settings. + pub fn perf_profile(&self) -> bool { + self.perf_profile + } + /// Set debug_info settings. pub fn set_debug_info(&mut self, value: bool) { self.debug_info = value; @@ -122,6 +129,7 @@ impl Context { fn instantiate(&mut self, data: &[u8]) -> Result { self.validate(&data).map_err(SetupError::Validate)?; let debug_info = self.debug_info(); + let perf_profile = self.perf_profile(); instantiate( &mut *self.compiler, @@ -129,6 +137,7 @@ impl Context { &mut self.namespace, Rc::clone(&self.global_exports), debug_info, + perf_profile, ) } diff --git a/wasmtime-jit/src/instantiate.rs b/wasmtime-jit/src/instantiate.rs index a35daf74b2a9..421baaf87957 100644 --- a/wasmtime-jit/src/instantiate.rs +++ b/wasmtime-jit/src/instantiate.rs @@ -20,8 +20,8 @@ use wasmtime_environ::{ CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment, }; use wasmtime_runtime::{ - Export, GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, VMFunctionBody, - VMSharedSignatureIndex, + Export, GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, JitDumpAgent, + VMFunctionBody, VMSharedSignatureIndex, }; /// An error condition while setting up a wasm instance, be it validation, @@ -55,6 +55,7 @@ struct RawCompiledModule<'data> { data_initializers: Box<[DataInitializer<'data>]>, signatures: BoxedSlice, dbg_jit_registration: Option, + jit_dump_agent: Option, } impl<'data> RawCompiledModule<'data> { @@ -64,6 +65,7 @@ impl<'data> RawCompiledModule<'data> { data: &'data [u8], resolver: &mut dyn Resolver, debug_info: bool, + perf_profile: bool, ) -> Result { let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); @@ -117,6 +119,22 @@ impl<'data> RawCompiledModule<'data> { // Make all code compiled thus far executable. compiler.publish_compiled_code(); + // Create jitdump files + let jit_dump_agent = if perf_profile { + let agent = JitDumpAgent::new().ok(); + let region_name = String::from("wasm_module"); + match &agent { + Some(agent) => match &dbg_image { + Some(dbg) => compiler.perf_module_load(®ion_name, agent, Some(&dbg)), + _ => compiler.perf_module_load(®ion_name, agent, None), + }, + _ => (), + } + agent + } else { + None + }; + let dbg_jit_registration = if let Some(img) = dbg_image { let mut bytes = Vec::new(); bytes.write_all(&img).expect("all written"); @@ -133,6 +151,7 @@ impl<'data> RawCompiledModule<'data> { data_initializers: translation.data_initializers.into_boxed_slice(), signatures: signatures.into_boxed_slice(), dbg_jit_registration, + jit_dump_agent, }) } } @@ -146,6 +165,7 @@ pub struct CompiledModule { signatures: BoxedSlice, global_exports: Rc>>>, dbg_jit_registration: Option>, + jit_dump_agent: Option, } impl CompiledModule { @@ -156,8 +176,10 @@ impl CompiledModule { resolver: &mut dyn Resolver, global_exports: Rc>>>, debug_info: bool, + perf_profile: bool, ) -> Result { - let raw = RawCompiledModule::<'data>::new(compiler, data, resolver, debug_info)?; + let raw = + RawCompiledModule::<'data>::new(compiler, data, resolver, debug_info, perf_profile)?; Ok(Self::from_parts( raw.module, @@ -171,6 +193,7 @@ impl CompiledModule { .into_boxed_slice(), raw.signatures.clone(), raw.dbg_jit_registration, + raw.jit_dump_agent, )) } @@ -183,6 +206,7 @@ impl CompiledModule { data_initializers: Box<[OwnedDataInitializer]>, signatures: BoxedSlice, dbg_jit_registration: Option, + jit_dump_agent: Option, ) -> Self { Self { module: Rc::new(module), @@ -192,6 +216,7 @@ impl CompiledModule { data_initializers, signatures, dbg_jit_registration: dbg_jit_registration.map(|r| Rc::new(r)), + jit_dump_agent, } } @@ -217,6 +242,7 @@ impl CompiledModule { &data_initializers, self.signatures.clone(), self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)), + self.jit_dump_agent.clone(), Box::new(()), ) } @@ -243,7 +269,7 @@ impl OwnedDataInitializer { /// Create a new wasm instance by compiling the wasm module in `data` and instatiating it. /// -/// This is equivalent to createing a `CompiledModule` and calling `instantiate()` on it, +/// This is equivalent to creating a `CompiledModule` and calling `instantiate()` on it, /// but avoids creating an intermediate copy of the data initializers. pub fn instantiate( compiler: &mut Compiler, @@ -251,8 +277,9 @@ pub fn instantiate( resolver: &mut dyn Resolver, global_exports: Rc>>>, debug_info: bool, + perf_profile: bool, ) -> Result { - let raw = RawCompiledModule::new(compiler, data, resolver, debug_info)?; + let raw = RawCompiledModule::new(compiler, data, resolver, debug_info, perf_profile)?; InstanceHandle::new( Rc::new(raw.module), @@ -262,6 +289,7 @@ pub fn instantiate( &*raw.data_initializers, raw.signatures, raw.dbg_jit_registration.map(|r| Rc::new(r)), + raw.jit_dump_agent, Box::new(()), ) .map_err(SetupError::Instantiate) diff --git a/wasmtime-runtime/Cargo.toml b/wasmtime-runtime/Cargo.toml index 82a768511290..b809c7f5e002 100644 --- a/wasmtime-runtime/Cargo.toml +++ b/wasmtime-runtime/Cargo.toml @@ -23,6 +23,12 @@ memoffset = "0.5.1" failure = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false } indexmap = "1.0.2" +goblin = "0.0.24" +serde = { version = "1.0.99", features = ["derive"] } +scroll = "0.9.2" +gimli = "0.19.0" +object = "0.12.0" +target-lexicon = "0.4.0" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] } @@ -33,6 +39,7 @@ cc = "1.0" [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std"] +jitdump = [] [badges] maintenance = { status = "experimental" } diff --git a/wasmtime-runtime/src/instance.rs b/wasmtime-runtime/src/instance.rs index 603851ac9ce1..a0e9da025102 100644 --- a/wasmtime-runtime/src/instance.rs +++ b/wasmtime-runtime/src/instance.rs @@ -4,6 +4,7 @@ use crate::export::Export; use crate::imports::Imports; +use crate::jit_dump::JitDumpAgent; use crate::jit_int::GdbJitImageRegistration; use crate::memory::LinearMemory; use crate::mmap::Mmap; @@ -220,6 +221,9 @@ pub(crate) struct Instance { /// Optional image of JIT'ed code for debugger registration. dbg_jit_registration: Option>, + /// Agent for optional creation of jitdump files. + jit_dump_agent: Option, + /// Additional context used by compiled wasm code. This field is last, and /// represents a dynamically-sized array that extends beyond the nominal /// end of the struct (similar to a flexible array member). @@ -663,6 +667,10 @@ impl Instance { .unwrap_or_else(|| panic!("no table for index {}", table_index.index())) .get_mut(index) } + + pub(crate) fn get_jit_dump_agent(&self) -> &Option { + &self.jit_dump_agent + } } /// A handle holding an `Instance` of a WebAssembly module. @@ -681,6 +689,7 @@ impl InstanceHandle { data_initializers: &[DataInitializer<'_>], vmshared_signatures: BoxedSlice, dbg_jit_registration: Option>, + jit_dump_agent: Option, host_state: Box, ) -> Result { let mut tables = create_tables(&module); @@ -723,6 +732,7 @@ impl InstanceHandle { tables, finished_functions, dbg_jit_registration, + jit_dump_agent, host_state, vmctx: VMContext {}, }; @@ -947,6 +957,11 @@ impl InstanceHandle { ) -> Option<&mut VMCallerCheckedAnyfunc> { self.instance_mut().table_get_mut(table_index, index) } + + /// Returns a reference to the instance's JitDumpAgent + pub fn get_jit_dump_agent(&self) -> &Option { + self.instance().get_jit_dump_agent() + } } impl InstanceHandle { diff --git a/wasmtime-runtime/src/jit_dump.rs b/wasmtime-runtime/src/jit_dump.rs new file mode 100644 index 000000000000..e36793829feb --- /dev/null +++ b/wasmtime-runtime/src/jit_dump.rs @@ -0,0 +1,673 @@ +//! Support for jitdump files which can be used by perf for profiling jitted code. +//! Spec definitions for the output format is as described here: +//! https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt +//! +//! Usage Example: +//! Record +//! sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g -jitdump test.wasm +//! Combine +//! sudo perf inject -v -j -i perf.data -o perf.jit.data +//! Report +//! sudo perf report -i perf.jit.data -F+period,srcline +//! Note: For descriptive results, the WASM file being executed should contain dwarf debug data +use libc::c_int; +#[cfg(not(target_os = "windows"))] +use libc::{c_void, clock_gettime, mmap, open, sysconf}; +use object::Object; +use scroll::{IOwrite, SizeWith, NATIVE}; +use serde::{Deserialize, Serialize}; +#[cfg(not(target_os = "windows"))] +use std::ffi::CString; +use std::fmt::Debug; +use std::fs::File; +use std::io::Write; +#[cfg(not(target_os = "windows"))] +use std::os::unix::io::FromRawFd; +use std::{borrow, mem, process}; +use target_lexicon::Architecture; + +#[cfg(target_pointer_width = "64")] +use goblin::elf64 as elf; + +#[cfg(target_pointer_width = "32")] +use goblin::elf32 as elf; + +/// Defines jitdump record types +#[repr(u32)] +pub enum RecordId { + /// Value 0: JIT_CODE_LOAD: record describing a jitted function + JitCodeLoad = 0, + /// Value 1: JIT_CODE_MOVE: record describing an already jitted function which is moved + _JitCodeMove = 1, + /// Value 2: JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function + JitCodeDebugInfo = 2, + /// Value 3: JIT_CODE_CLOSE: record marking the end of the jit runtime (optional) + _JitCodeClose = 3, + /// Value 4: JIT_CODE_UNWINDING_INFO: record describing a function unwinding information + _JitCodeUnwindingInfo = 4, +} + +/// Each record starts with this fixed size record header which describes the record that follows +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct RecordHeader { + /// uint32_t id: a value identifying the record type (see below) + id: u32, + /// uint32_t total_size: the size in bytes of the record including the header. + record_size: u32, + /// uint64_t timestamp: a timestamp of when the record was created. + timestamp: u64, +} + +/// The CodeLoadRecord is used for describing jitted functions +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct CodeLoadRecord { + /// Fixed sized header that describes this record + header: RecordHeader, + /// uint32_t pid: OS process id of the runtime generating the jitted code + pid: u32, + /// uint32_t tid: OS thread identification of the runtime thread generating the jitted code + tid: u32, + /// uint64_t vma: virtual address of jitted code start + virtual_address: u64, + /// uint64_t code_addr: code start address for the jitted code. By default vma = code_addr + address: u64, + /// uint64_t code_size: size in bytes of the generated jitted code + size: u64, + /// uint64_t code_index: unique identifier for the jitted code (see below) + index: u64, +} + +/// Describes source line information for a jitted function +#[derive(Serialize, Deserialize, Debug, Default)] +#[repr(C)] +pub struct DebugEntry { + /// uint64_t code_addr: address of function for which the debug information is generated + address: u64, + /// uint32_t line: source file line number (starting at 1) + line: u32, + /// uint32_t discrim: column discriminator, 0 is default + discriminator: u32, + /// char name[n]: source file name in ASCII, including null termination + filename: String, +} + +/// Describes debug information for a jitted function. An array of debug entries are +/// appended to this record during writting. Note, this record must preceed the code +/// load record that describes the same jitted function. +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct DebugInfoRecord { + /// Fixed sized header that describes this record + header: RecordHeader, + /// uint64_t code_addr: address of function for which the debug information is generated + address: u64, + /// uint64_t nr_entry: number of debug entries for the function appended to this record + count: u64, +} + +/// Fixed-sized header for each jitdump file +#[derive(Serialize, Deserialize, Debug, Default, IOwrite, SizeWith)] +#[repr(C)] +pub struct FileHeader { + /// uint32_t magic: a magic number tagging the file type. The value is 4-byte long and represents the + /// string "JiTD" in ASCII form. It is 0x4A695444 or 0x4454694a depending on the endianness. The field can + /// be used to detect the endianness of the file + magic: u32, + /// uint32_t version: a 4-byte value representing the format version. It is currently set to 2 + version: u32, + /// uint32_t total_size: size in bytes of file header + size: u32, + /// uint32_t elf_mach: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h) + e_machine: u32, + /// uint32_t pad1: padding. Reserved for future use + pad1: u32, + /// uint32_t pid: JIT runtime process identification (OS specific) + pid: u32, + /// uint64_t timestamp: timestamp of when the file was created + timestamp: u64, + /// uint64_t flags: a bitmask of flags + flags: u64, +} + +/// Interface for driving the creation of jitdump files +#[derive(Debug)] +pub struct JitDumpAgent { + /// File instance for the jit dump file + jitdump_file: File, + /// Unique identifier for jitted code + code_index: u64, + /// Flag for experimenting with dumping code load record + /// after each function (true) or after each module. This + /// flag is currently set to true. + dump_funcs: bool, +} + +impl JitDumpAgent { + /// Intialize a JitDumpAgent and write out the header + pub fn new() -> Result { + #[cfg(target_os = "windows")] + return Err(Error::NulError); + + #[cfg(not(target_os = "windows"))] + { + let filename = format!("./jit-{}.dump", process::id()); + let mut jitdump_file; + unsafe { + let filename_c = CString::new(filename)?; + let fd = open( + filename_c.as_ptr(), + libc::O_CREAT | libc::O_TRUNC | libc::O_RDWR, + 0666, + ); + let pgsz = sysconf(libc::_SC_PAGESIZE) as usize; + mmap( + 0 as *mut c_void, + pgsz, + libc::PROT_EXEC | libc::PROT_READ, + libc::MAP_PRIVATE, + fd, + 0, + ); + jitdump_file = File::from_raw_fd(fd); + } + JitDumpAgent::write_file_header(&mut jitdump_file)?; + + Ok(Self { + jitdump_file: jitdump_file, + code_index: 0, + dump_funcs: true, + }) + } + } + + /// Returns timestamp from a single source + #[allow(unused_variables)] + fn get_time_stamp(timestamp: &mut u64) -> c_int { + #[cfg(not(target_os = "windows"))] + { + unsafe { + let mut ts = mem::MaybeUninit::zeroed().assume_init(); + clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); + // TODO: What does it mean for either sec or nsec to be negative? + *timestamp = (ts.tv_sec * 1000000000 + ts.tv_nsec) as u64; + } + } + return 0; + } + + /// Returns the ELF machine architecture. + #[allow(dead_code)] + fn get_e_machine() -> u32 { + match target_lexicon::HOST.architecture { + Architecture::X86_64 => elf::header::EM_X86_64 as u32, + Architecture::I686 => elf::header::EM_386 as u32, + Architecture::Arm => elf::header::EM_ARM as u32, + Architecture::Armv4t => elf::header::EM_ARM as u32, + Architecture::Armv5te => elf::header::EM_ARM as u32, + Architecture::Armv7 => elf::header::EM_ARM as u32, + Architecture::Armv7s => elf::header::EM_ARM as u32, + Architecture::Aarch64 => elf::header::EM_AARCH64 as u32, + _ => unimplemented!("unrecognized architecture"), + } + } + + #[allow(dead_code)] + fn write_file_header(file: &mut File) -> Result<(), Error> { + let mut header: FileHeader = Default::default(); + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + header.timestamp = timestamp; + + let e_machine = JitDumpAgent::get_e_machine(); + if e_machine != elf::header::EM_NONE as u32 { + header.e_machine = e_machine; + } + + if cfg!(target_endian = "little") { + header.magic = 0x4A695444 + } else { + header.magic = 0x4454694a + } + header.version = 1; + header.size = mem::size_of::() as u32; + header.pad1 = 0; + header.pid = process::id(); + header.flags = 0; + + file.iowrite_with(header, NATIVE)?; + Ok(()) + } + + fn write_code_load_record( + &mut self, + record_name: &str, + cl_record: CodeLoadRecord, + code_buffer: &[u8], + ) -> Result<(), Error> { + self.jitdump_file.iowrite_with(cl_record, NATIVE)?; + self.jitdump_file.write_all(record_name.as_bytes())?; + self.jitdump_file.write_all(b"\0")?; + self.jitdump_file.write_all(code_buffer)?; + Ok(()) + } + + /// Write DebugInfoRecord to open jit dump file. + /// Must be written before the corresponding CodeLoadRecord. + fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> Result<(), Error> { + self.jitdump_file.iowrite_with(dir_record, NATIVE)?; + Ok(()) + } + + /// Write DebugInfoRecord to open jit dump file. + /// Must be written before the corresponding CodeLoadRecord. + fn write_debug_info_entries(&mut self, die_entries: Vec) -> Result<(), Error> { + for entry in die_entries.iter() { + self.jitdump_file.iowrite_with(entry.address, NATIVE)?; + self.jitdump_file.iowrite_with(entry.line, NATIVE)?; + self.jitdump_file + .iowrite_with(entry.discriminator, NATIVE)?; + self.jitdump_file.write_all(entry.filename.as_bytes())?; + self.jitdump_file.write_all(b"\0")?; + } + Ok(()) + } + + /// Sent when a method is compiled and loaded into memory by the VM. + pub fn module_load( + &mut self, + module_name: &str, + addr: *const u8, + len: usize, + dbg_image: Option<&[u8]>, + ) -> () { + let pid = process::id(); + let tid = pid; // ThreadId does appear to track underlying thread. Using PID. + + if let Some(img) = &dbg_image { + let _ = self.dump_from_debug_image(img, module_name, addr, len, pid, tid); + } else { + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); + } + } + + fn dump_code_load_record( + &mut self, + method_name: &str, + addr: *const u8, + len: usize, + timestamp: u64, + pid: u32, + tid: u32, + ) -> () { + let name_len = method_name.len() + 1; + let size_limit = mem::size_of::(); + + let rh = RecordHeader { + id: RecordId::JitCodeLoad as u32, + record_size: size_limit as u32 + name_len as u32 + len as u32, + timestamp: timestamp, + }; + + let clr = CodeLoadRecord { + header: rh, + pid: pid, + tid: tid, + virtual_address: addr as u64, + address: addr as u64, + size: len as u64, + index: self.code_index, + }; + self.code_index += 1; + + unsafe { + let code_buffer: &[u8] = std::slice::from_raw_parts(addr, len); + let _ = self.write_code_load_record(method_name, clr, code_buffer); + } + } + + /// Attempts to dump debuginfo data structures, adding method and line level + /// for the jitted function. + pub fn dump_from_debug_image( + &mut self, + dbg_image: &[u8], + module_name: &str, + addr: *const u8, + len: usize, + pid: u32, + tid: u32, + ) -> Result<(), Error> { + let file = object::File::parse(&dbg_image).unwrap(); + let endian = if file.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + let load_section = |id: gimli::SectionId| -> Result, Error> { + Ok(file + .section_data_by_name(id.name()) + .unwrap_or(borrow::Cow::Borrowed(&[][..]))) + }; + + let load_section_sup = |_| Ok(borrow::Cow::Borrowed(&[][..])); + let dwarf_cow = gimli::Dwarf::load(&load_section, &load_section_sup)?; + let borrow_section: &dyn for<'a> Fn( + &'a borrow::Cow<[u8]>, + ) + -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = + &|section| gimli::EndianSlice::new(&*section, endian); + + let dwarf = dwarf_cow.borrow(&borrow_section); + + let mut iter = dwarf.units(); + while let Some(header) = iter.next()? { + let unit = match dwarf.unit(header) { + Ok(unit) => unit, + Err(_err) => { + return Ok(()); + } + }; + self.dump_entries(unit, &dwarf, module_name, addr, len, pid, tid)?; + // TODO: Temp exit to avoid duplicate addresses being covered by only + // processing the top unit + break; + } + if !self.dump_funcs { + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); + } + Ok(()) + } + + fn dump_entries( + &mut self, + unit: gimli::Unit, + dwarf: &gimli::Dwarf, + module_name: &str, + addr: *const u8, + len: usize, + pid: u32, + tid: u32, + ) -> Result<(), Error> { + let mut depth = 0; + let mut entries = unit.entries(); + + while let Some((delta_depth, entry)) = entries.next_dfs()? { + if self.dump_funcs { + let record_header = RecordHeader { + id: RecordId::JitCodeLoad as u32, + record_size: 0, + timestamp: 0, + }; + + let mut clr = CodeLoadRecord { + header: record_header, + pid: pid, + tid: tid, + virtual_address: 0, + address: 0, + size: 0, + index: 0, + }; + let mut clr_name: String = String::from(module_name); + + let mut get_debug_entry = false; + depth += delta_depth; + assert!(depth >= 0); + + if entry.tag() == gimli::constants::DW_TAG_subprogram { + get_debug_entry = true; + + let mut attrs = entry.attrs(); + while let Some(attr) = attrs.next()? { + if let Some(n) = attr.name().static_string() { + if n == "DW_AT_low_pc" { + clr.address = match attr.value() { + gimli::AttributeValue::Addr(address) => address, + _ => 0, + }; + clr.virtual_address = clr.address; + } else if n == "DW_AT_high_pc" { + clr.size = match attr.value() { + gimli::AttributeValue::Udata(data) => data, + _ => 0, + }; + } else if n == "DW_AT_name" { + clr_name = match attr.value() { + gimli::AttributeValue::DebugStrRef(offset) => { + if let Ok(s) = dwarf.debug_str.get_str(offset) { + clr_name.push_str("::"); + clr_name.push_str(&s.to_string_lossy()?); + clr_name + } else { + clr_name.push_str("::"); + clr_name.push_str("?"); + clr_name + } + } + _ => { + clr_name.push_str("??"); + clr_name + } + }; + } + } + } + } + if get_debug_entry { + // TODO: Temp check to make sure well only formed data is processed. + if clr.address == 0 { + continue; + } + // TODO: Temp check to make sure well only formed data is processed. + if clr_name == "?" { + continue; + } + if clr.address == 0 || clr.size == 0 { + clr.address = addr as u64; + clr.virtual_address = addr as u64; + clr.size = len as u64; + } + clr.header.record_size = mem::size_of::() as u32 + + (clr_name.len() + 1) as u32 + + clr.size as u32; + clr.index = self.code_index; + self.code_index += 1; + self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?; + + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + clr.header.timestamp = timestamp; + + unsafe { + let code_buffer: &[u8] = + std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize); + let _ = self.write_code_load_record(&clr_name, clr, code_buffer); + } + } + } else { + let mut func_name: String = String::from("?"); + let mut func_addr = 0; + let mut func_size = 0; + + let mut get_debug_entry = false; + depth += delta_depth; + assert!(depth >= 0); + if entry.tag() == gimli::constants::DW_TAG_subprogram { + get_debug_entry = true; + + let mut attrs = entry.attrs(); + while let Some(attr) = attrs.next()? { + if let Some(n) = attr.name().static_string() { + if n == "DW_AT_low_pc" { + func_addr = match attr.value() { + gimli::AttributeValue::Addr(address) => address, + _ => 0, + }; + } else if n == "DW_AT_high_pc" { + func_size = match attr.value() { + gimli::AttributeValue::Udata(data) => data, + _ => 0, + }; + } else if n == "DW_AT_name" { + func_name = match attr.value() { + gimli::AttributeValue::DebugStrRef(offset) => { + if let Ok(s) = dwarf.debug_str.get_str(offset) { + func_name.clear(); + func_name.push_str(&s.to_string_lossy()?); + func_name + } else { + func_name.push_str("?"); + func_name + } + } + _ => { + func_name.push_str("??"); + func_name + } + }; + } + } + } + } + if get_debug_entry { + // TODO: Temp check to make sure well only formed data is processed. + if func_addr == 0 { + continue; + } + // TODO: Temp check to make sure well only formed data is processed. + if func_name == "?" { + continue; + } + self.dump_debug_info( + &unit, + &dwarf, + func_addr, + func_size, + Some(func_name.as_str()), + )?; + } + } + } + Ok(()) + } + + fn dump_debug_info( + &mut self, + unit: &gimli::Unit, + dwarf: &gimli::Dwarf, + address: u64, + size: u64, + file_suffix: Option<&str>, + ) -> Result<(), Error> { + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + if let Some(program) = unit.line_program.clone() { + let mut debug_info_record = DebugInfoRecord { + header: RecordHeader { + id: RecordId::JitCodeDebugInfo as u32, + record_size: 0, + timestamp: timestamp, + }, + address: address, + count: 0, + }; + + let mut debug_entries = Vec::new(); + let mut debug_entries_total_filenames_len = 0; + let mut rows = program.rows(); + while let Some((header, row)) = rows.next_row()? { + let row_file_index = row.file_index() - 1; + let myfile = dwarf + .attr_string( + &unit, + header.file_names()[row_file_index as usize].path_name(), + ) + .unwrap(); + let filename = myfile.to_string_lossy()?; + let line = row.line().unwrap_or(0); + let column = match row.column() { + gimli::ColumnType::Column(column) => column, + gimli::ColumnType::LeftEdge => 0, + }; + + if (row.address() < address) || (row.address() > (address + size)) { + continue; + } + let mut debug_entry = DebugEntry { + address: row.address(), + line: line as u32, + discriminator: column as u32, + filename: filename.to_string(), + }; + + if let Some(suffix) = file_suffix { + debug_entry.filename.push_str("::"); + debug_entry.filename.push_str(suffix); + } + + debug_entries_total_filenames_len += debug_entry.filename.len() + 1; + debug_entries.push(debug_entry); + } + + debug_info_record.count = debug_entries.len() as u64; + + let debug_entries_size = (debug_info_record.count + * (mem::size_of::() as u64 - mem::size_of::() as u64)) + + debug_entries_total_filenames_len as u64; + debug_info_record.header.record_size = + mem::size_of::() as u32 + debug_entries_size as u32; + + let _ = self.write_debug_info_record(debug_info_record); + let _ = self.write_debug_info_entries(debug_entries); + } + Ok(()) + } +} + +impl Clone for JitDumpAgent { + fn clone(&self) -> Self { + Self { + jitdump_file: self.jitdump_file.try_clone().unwrap(), + code_index: self.code_index, + dump_funcs: self.dump_funcs, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + GimliError(gimli::Error), + IOError, + NulError, +} + +impl From for Error { + fn from(err: gimli::Error) -> Self { + Error::GimliError(err) + } +} + +impl From for Error { + fn from(_err: std::io::Error) -> Self { + Error::IOError + } +} + +impl From for Error { + fn from(_err: std::ffi::NulError) -> Self { + Error::NulError + } +} + +trait Reader: gimli::Reader + Send + Sync {} + +impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where + Endian: gimli::Endianity + Send + Sync +{ +} diff --git a/wasmtime-runtime/src/lib.rs b/wasmtime-runtime/src/lib.rs index b3f394bfe66a..f175616ee0da 100644 --- a/wasmtime-runtime/src/lib.rs +++ b/wasmtime-runtime/src/lib.rs @@ -32,6 +32,7 @@ extern crate failure_derive; mod export; mod imports; mod instance; +mod jit_dump; mod jit_int; mod memory; mod mmap; @@ -47,6 +48,7 @@ pub mod libcalls; pub use crate::export::Export; pub use crate::imports::Imports; pub use crate::instance::{InstanceHandle, InstantiationError, LinkError}; +pub use crate::jit_dump::JitDumpAgent; pub use crate::jit_int::GdbJitImageRegistration; pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; diff --git a/wasmtime-wasi/src/instantiate.rs b/wasmtime-wasi/src/instantiate.rs index 0b83681280bd..b22e9f8c8d53 100644 --- a/wasmtime-wasi/src/instantiate.rs +++ b/wasmtime-wasi/src/instantiate.rs @@ -133,6 +133,7 @@ pub fn instantiate_wasi( &data_initializers, signatures.into_boxed_slice(), None, + None, Box::new(wasi_ctx), ) } diff --git a/wasmtime-wast/src/spectest.rs b/wasmtime-wast/src/spectest.rs index e82eb09c13be..62fccda85a2f 100644 --- a/wasmtime-wast/src/spectest.rs +++ b/wasmtime-wast/src/spectest.rs @@ -224,6 +224,7 @@ pub fn instantiate_spectest() -> Result { &data_initializers, signatures.into_boxed_slice(), None, + None, Box::new(()), ) }