From 94001b38aaef5331ce46a854bee3dfe1673241d7 Mon Sep 17 00:00:00 2001 From: Johnnie Birch <45402135+jlb6740@users.noreply.github.com> Date: Tue, 10 Sep 2019 18:17:19 -0700 Subject: [PATCH] Adds perf jitdump support Patch adds support for the perf jitdump file specification. With this patch it should be possible to see profile data for code generated and maped at runtime. Specifically the patch adds support for the JIT_CODE_LOAD and the JIT_DEBUG_INFO record as described in the specification. Dumping jitfiles is enabled with the --jitdump flag. When the -g flag is also used there is an attempt to dump file and line number information where this option would be most useful when the WASM file already includes DWARF debug information. --- src/bin/wasmtime.rs | 11 +- wasmtime-api/src/context.rs | 12 +- wasmtime-api/src/instance.rs | 2 + wasmtime-api/src/runtime.rs | 12 +- wasmtime-api/src/trampoline/create_handle.rs | 1 + wasmtime-jit/Cargo.toml | 2 + wasmtime-jit/src/code_memory.rs | 10 +- wasmtime-jit/src/compiler.rs | 6 +- wasmtime-jit/src/context.rs | 9 + wasmtime-jit/src/instantiate.rs | 37 +- wasmtime-runtime/Cargo.toml | 17 + wasmtime-runtime/src/instance.rs | 15 + wasmtime-runtime/src/jit_dump.rs | 701 +++++++++++++++++++ wasmtime-runtime/src/lib.rs | 2 + wasmtime-wasi/src/instantiate.rs | 1 + wasmtime-wast/src/spectest.rs | 1 + 16 files changed, 825 insertions(+), 14 deletions(-) create mode 100644 wasmtime-runtime/src/jit_dump.rs diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index b770b48a57b6..c6e159143700 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] [--cache | --cache-config=] [--create-cache-config] [--preload=...] [--env=...] [--dir=...] [--mapdir=...] [...] - wasmtime [-odg] [--enable-simd] [--wasi-c] [--cache | --cache-config=] [--create-cache-config] [--env=...] [--dir=...] [--mapdir=...] --invoke= [...] + wasmtime [-odg] [--enable-simd] [--jitdump] [--wasi-c] [--cache | --cache-config=] [--create-cache-config] [--preload=...] [--env=...] [--dir=...] [--mapdir=...] [...] + wasmtime [-odg] [--enable-simd] [--jitdump] [--wasi-c] [--cache | --cache-config=] [--create-cache-config] [--env=...] [--dir=...] [--mapdir=...] --invoke= [...] wasmtime --help | --version Options: @@ -78,6 +78,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 @@ -99,6 +100,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, @@ -254,12 +256,15 @@ 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/wasmtime-api/src/context.rs b/wasmtime-api/src/context.rs index 76085dd86852..136e4da3b759 100644 --- a/wasmtime-api/src/context.rs +++ b/wasmtime-api/src/context.rs @@ -11,25 +11,31 @@ 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..af3e6a7729dd 100644 --- a/wasmtime-api/src/runtime.rs +++ b/wasmtime-api/src/runtime.rs @@ -21,22 +21,25 @@ 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 +47,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 +99,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/Cargo.toml b/wasmtime-jit/Cargo.toml index d69b6c99326f..8688275c63b7 100644 --- a/wasmtime-jit/Cargo.toml +++ b/wasmtime-jit/Cargo.toml @@ -24,6 +24,8 @@ failure_derive = { version = "0.1.3", default-features = false } target-lexicon = { version = "0.4.0", default-features = false } hashbrown = { version = "0.6.0", optional = true } wasmparser = "0.36.0" +gimli = "0.19.0" +object = "0.14.0" [features] default = ["std"] diff --git a/wasmtime-jit/src/code_memory.rs b/wasmtime-jit/src/code_memory.rs index 641577ad9d3b..b2da0ca0c362 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::{Mmap, VMFunctionBody, JitDumpAgent}; /// Memory manager for executable code. pub(crate) struct CodeMemory { @@ -99,4 +99,12 @@ 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 76a91ef5cbf8..fac786259dee 100644 --- a/wasmtime-jit/src/compiler.rs +++ b/wasmtime-jit/src/compiler.rs @@ -19,7 +19,7 @@ use wasmtime_environ::{ Compilation, CompileError, Compiler as _C, FunctionBodyData, Module, ModuleVmctxInfo, Relocations, Tunables, VMOffsets, }; -use wasmtime_runtime::{InstantiationError, SignatureRegistry, VMFunctionBody}; +use wasmtime_runtime::{InstantiationError, SignatureRegistry, VMFunctionBody, JitDumpAgent}; /// A WebAssembly code JIT compiler. /// @@ -181,6 +181,10 @@ 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 b04dd5d7a57f..218b4f0c2b94 100644 --- a/wasmtime-jit/src/context.rs +++ b/wasmtime-jit/src/context.rs @@ -78,6 +78,7 @@ pub struct Context { compiler: Box, global_exports: Rc>>>, debug_info: bool, + perf_profile: bool, features: Features, } @@ -89,6 +90,7 @@ impl Context { compiler, global_exports: Rc::new(RefCell::new(HashMap::new())), debug_info: false, + perf_profile: false, features: Default::default(), } } @@ -98,6 +100,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; @@ -127,6 +134,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, @@ -134,6 +142,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..7f934e852f97 100644 --- a/wasmtime-jit/src/instantiate.rs +++ b/wasmtime-jit/src/instantiate.rs @@ -20,7 +20,7 @@ use wasmtime_environ::{ CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment, }; use wasmtime_runtime::{ - Export, GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, VMFunctionBody, + Export, GdbJitImageRegistration, JitDumpAgent, Imports, InstanceHandle, InstantiationError, VMFunctionBody, VMSharedSignatureIndex, }; @@ -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,24 @@ 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 +153,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 +167,7 @@ pub struct CompiledModule { signatures: BoxedSlice, global_exports: Rc>>>, dbg_jit_registration: Option>, + jit_dump_agent: Option, } impl CompiledModule { @@ -156,8 +178,9 @@ 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 +194,7 @@ impl CompiledModule { .into_boxed_slice(), raw.signatures.clone(), raw.dbg_jit_registration, + raw.jit_dump_agent, )) } @@ -183,6 +207,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 +217,7 @@ impl CompiledModule { data_initializers, signatures, dbg_jit_registration: dbg_jit_registration.map(|r| Rc::new(r)), + jit_dump_agent, } } @@ -217,6 +243,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 +270,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 +278,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 +290,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 ae5898747eb6..c3481acc5e3b 100644 --- a/wasmtime-runtime/Cargo.toml +++ b/wasmtime-runtime/Cargo.toml @@ -23,6 +23,22 @@ 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_derive = "1.0.99" +serde = { version = "1.0.99", features = ["derive"] } +scroll = "0.9.2" +scroll_derive = "0.9.5" +memmap = "0.7.0" +itertools = "0.8.0" +gimli = "0.19.0" +object = "0.12.0" +regex = "1.3.1" +fallible-iterator = "0.2.0" +typed-arena = "1.6.0" +num_cpus = "1.10.1" +crossbeam = "0.7.2" +getopts = "0.2.21" +target-lexicon = "0.4.0" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] } @@ -33,6 +49,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 c6c92590b39a..84b81eefa75e 100644 --- a/wasmtime-runtime/src/instance.rs +++ b/wasmtime-runtime/src/instance.rs @@ -5,6 +5,7 @@ use crate::export::Export; use crate::imports::Imports; use crate::jit_int::GdbJitImageRegistration; +use crate::jit_dump::JitDumpAgent; use crate::memory::LinearMemory; use crate::mmap::Mmap; use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; @@ -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..a1b889d8551a --- /dev/null +++ b/wasmtime-runtime/src/jit_dump.rs @@ -0,0 +1,701 @@ +//! 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, c_void, clockid_t, mmap, open, sysconf, timespec}; +use object::Object; +use scroll::{IOwrite, SizeWith, NATIVE}; +use serde::{Deserialize, Serialize}; +use std::ffi::CString; +use std::fmt::Debug; +use std::fs::File; +use std::io::Write; +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; + +/* + * The following record types are defined: + * Value 0 : JIT_CODE_LOAD: record describing a jitted function + * Value 1 : JIT_CODE_MOVE: record describing an already jitted function which is moved + * Value 2 : JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function + * Value 3 : JIT_CODE_CLOSE: record marking the end of the jit runtime (optional) + * Value 4 : JIT_CODE_UNWINDING_INFO: record describing a function unwinding information + */ +#[repr(u32)] +pub enum RecordId { + JitCodeLoad = 0, + _JitCodeMove = 1, + JitCodeDebugInfo = 2, + _JitCodeClose = 3, + _JitCodeUnwindingInfo = 4, +} + +/* + * The record header is specified in order as follows: + * uint32_t id: a value identifying the record type (see below) + * uint32_t total_size: the size in bytes of the record including the header. + * uint64_t timestamp: a timestamp of when the record was created. + */ +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct RecordHeader { + id: u32, + record_size: u32, + timestamp: u64, +} + +/* + * The CoadLoadRecord has the following fields following the fixed-size record header in order + * uint32_t pid: OS process id of the runtime generating the jitted code + * uint32_t tid: OS thread identification of the runtime thread generating the jitted code + * uint64_t vma: virtual address of jitted code start + * uint64_t code_addr: code start address for the jitted code. By default vma = code_addr + * uint64_t code_size: size in bytes of the generated jitted code + * uint64_t code_index: unique identifier for the jitted code (see below) + * char[n]: function name in ASCII including the null termination + * native code: raw byte encoding of the jitted code + */ + +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct CodeLoadRecord { + header: RecordHeader, + pid: u32, + tid: u32, + virtual_address: u64, + address: u64, + size: u64, + index: u64, + // Name is appended during writing + // Native code is appended during writing +} + +/* +* The DebugEntry describes the source line information. It is defined as follows in order: +* uint64_t code_addr: address of function for which the debug information is generated +* uint32_t line: source file line number (starting at 1) +* uint32_t discrim: column discriminator, 0 is default +* char name[n]: source file name in ASCII, including null termination +*/ +#[derive(Serialize, Deserialize, Debug, Default)] +#[repr(C)] +pub struct DebugEntry { + address: u64, + line: u32, + discriminator: u32, + filename: String, +} + +/* + * The record has the following fields following the fixed-size record header in order: + * uint64_t code_addr: address of function for which the debug information is generated + * uint64_t nr_entry : number of debug entries for the function + * debug_entry[n]: array of nr_entry debug entries for the function + */ +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct DebugInfoRecord { + header: RecordHeader, + address: u64, + count: u64, + // DebugEntry array is appeneded during writing. +} + +/* + * Each jitdump file starts with a fixed size header containing the following fields in order: + * + * 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 + * uint32_t version : a 4-byte value representing the format version. It is currently set to 2 + * uint32_t total_size: size in bytes of file header + * uint32_t elf_mach : ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h) + * uint32_t pad1 : padding. Reserved for future use + * uint32_t pid : JIT runtime process identification (OS specific) + * uint64_t timestamp : timestamp of when the file was created + * uint64_t flags : a bitmask of flags + */ + +#[derive(Serialize, Deserialize, Debug, Default, IOwrite, SizeWith)] +#[repr(C)] +pub struct FileHeader { + magic: u32, + version: u32, + size: u32, + e_machine: u32, + pad1: u32, + pid: u32, + timestamp: u64, + flags: u64, +} + +extern "C" { + fn clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_int; +} + +/// Interface for driving the creation of jitdump files +#[derive(Debug)] +pub struct JitDumpAgent { + jitdump_file: File, + code_index: u64, + dump_funcs: bool, +} + +impl JitDumpAgent { + /// Intialize a JitDumpAgent and write out the header + pub fn new() -> Result { + 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, + }) + } + + fn get_time_stamp(timestamp: &mut u64) -> c_int { + unsafe { + let mut ts = mem::MaybeUninit::zeroed().assume_init(); + if clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) != 0 { + println!("Error getting timestamp"); + } + // 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; + } + + // Get the ELF machine architecture. + 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"), + } + } + + 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; + + // Get arch value + let e_machine = JitDumpAgent::get_e_machine(); + if e_machine != elf::header::EM_NONE as u32 { + header.e_machine = e_machine; + } + + // Remaning header values + 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); + } + } + + /// TODO: Sent when a component of the virtual machine is generated dynamically (such as trampolines created during invoke) + pub fn code_generated(&self) -> () {} + + 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 details to the jitdump image + 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(&[][..]))) + }; + + // Load a supplementary section. We don't have a supplementary object file, + // so always return an empty slice. + let load_section_sup = |_| Ok(borrow::Cow::Borrowed(&[][..])); + + let dwarf_cow = gimli::Dwarf::load(&load_section, &load_section_sup)?; + + // Borrow a `Cow<[u8]>` to create an `EndianSlice`. + let borrow_section: &dyn for<'a> Fn( + &'a borrow::Cow<[u8]>, + ) + -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = + &|section| gimli::EndianSlice::new(&*section, endian); + + // Create `EndianSlice`s for all of the sections. + 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)?; + // Temp ... 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 { + // Temp check to make sure well only formed data is processed. + if clr.address == 0 { + continue; + } + // 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; + + //println!("\n<<<<<<<<<<<<<<<<<< New Code Load Record >>>>>>>>>>>>>>>>>>"); + //println!("CodeLoadRecord.header.id: {}", clr.header.id); + //println!("CodeLoadRecord.header.timestamp: {}", clr.header.timestamp); + //println!("CodeLoadRecord.pid: {}", clr.pid); + //println!("CodeLoadRecord.tid: {}", clr.tid); + //println!("CodeLoadRecord.address: {:x}", clr.address); + //println!("CodeLoadRecord.virtual_address: {:x}", clr.virtual_address); + //println!("CodeLoadRecord.size: {}", clr.size); + //println!("CodeLoadRecord_function_name: {}", clr_name); + + 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 { + // Temp check to make sure well only formed data is processed. + if func_addr == 0 { + continue; + } + // 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); + //println!("\n<<<<<<<<<<<<<<<<<< New Debug Entry {}::{}", filename.to_string(), line); + } + + 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; + + //println!("\n<<<<<<<<<<<<<<<<<< New Debug Info Record >>>>>>>>>>>>>>>>>>"); + //println!("DebugInfoRecord.header.id: {:x}", debug_info_record.header.id); + //println!("DebugInfoRecord.header.timestamp: {}", debug_info_record.header.timestamp); + //println!("DebugInfoRecord.address: {:x}", debug_info_record.address); + //println!("DebugInfoRecord.header.record_size: {}", debug_info_record.header.record_size); + //println!("DebugInfoRecord.count: {}", debug_info_record.count); + + 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 8efb012a16e9..587e63f3867a 100644 --- a/wasmtime-runtime/src/lib.rs +++ b/wasmtime-runtime/src/lib.rs @@ -33,6 +33,7 @@ mod export; mod imports; mod instance; mod jit_int; +mod jit_dump; mod memory; mod mmap; mod sig_registry; @@ -47,6 +48,7 @@ pub use crate::export::Export; pub use crate::imports::Imports; pub use crate::instance::{InstanceHandle, InstantiationError, LinkError}; pub use crate::jit_int::GdbJitImageRegistration; +pub use crate::jit_dump::JitDumpAgent; pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; 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(()), ) }