diff --git a/Cargo.lock b/Cargo.lock index c3c97d1e63..db3415e182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3628,10 +3628,20 @@ dependencies = [ "target-lexicon", "thiserror", "wasmtime-environ", + "wasmtime-jit-debug", "wasmtime-runtime", "winapi", ] +[[package]] +name = "wasmtime-jit-debug" +version = "0.34.0" +dependencies = [ + "lazy_static", + "object", + "rustix", +] + [[package]] name = "wasmtime-runtime" version = "0.34.0" @@ -3641,7 +3651,6 @@ dependencies = [ "cc", "cfg-if 1.0.0", "indexmap", - "lazy_static", "libc", "log", "mach", @@ -3655,6 +3664,7 @@ dependencies = [ "userfaultfd", "wasmtime-environ", "wasmtime-fiber", + "wasmtime-jit-debug", "winapi", ] diff --git a/crates/jit-debug/Cargo.toml b/crates/jit-debug/Cargo.toml new file mode 100644 index 0000000000..1de216cac0 --- /dev/null +++ b/crates/jit-debug/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "wasmtime-jit-debug" +version = "0.34.0" +authors = ["The Wasmtime Project Developers"] +description = "JIT debug interfaces support for Wasmtime" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["development-tools::debugging"] +keywords = ["gdb", "jit"] +repository = "https://github.com/bytecodealliance/wasmtime" +readme = "README.md" +edition = "2018" + +[dependencies] +lazy_static = {version = "1.3.0", optional = true } +object = { version = "0.27.0", default-features = false, features = ["std", "read_core"], optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +rustix = { version = "0.33.0", optional = true } + +[badges] +maintenance = { status = "actively-developed" } + +[features] +gdb_jit_int = ["lazy_static"] +perf_jitdump = ["rustix", "object"] diff --git a/crates/jit-debug/README.md b/crates/jit-debug/README.md new file mode 100644 index 0000000000..c2acc0c83a --- /dev/null +++ b/crates/jit-debug/README.md @@ -0,0 +1 @@ +This is the `wasmtime-jit-debug` crate, which contains JIT debug interfaces support for Wasmtime. diff --git a/crates/runtime/src/jit_int.rs b/crates/jit-debug/src/gdb_jit_int.rs similarity index 100% rename from crates/runtime/src/jit_int.rs rename to crates/jit-debug/src/gdb_jit_int.rs diff --git a/crates/jit-debug/src/lib.rs b/crates/jit-debug/src/lib.rs new file mode 100644 index 0000000000..c8cf286a8e --- /dev/null +++ b/crates/jit-debug/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "gdb_jit_int")] +pub mod gdb_jit_int; + +#[cfg(all(feature = "perf_jitdump", target_os = "linux"))] +pub mod perf_jitdump; diff --git a/crates/jit-debug/src/perf_jitdump.rs b/crates/jit-debug/src/perf_jitdump.rs new file mode 100644 index 0000000000..a39cbd57e6 --- /dev/null +++ b/crates/jit-debug/src/perf_jitdump.rs @@ -0,0 +1,293 @@ +//! Support for jitdump files which can be used by perf for profiling jitted code. +//! Spec definitions for the output format is as described here: +//! +//! +//! 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 + +use std::fmt::Debug; +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::Write; +use std::path::Path; +use std::ptr; +use std::{mem, process}; + +/// 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(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct RecordHeader { + /// uint32_t id: a value identifying the record type (see below) + pub id: u32, + /// uint32_t total_size: the size in bytes of the record including the header. + pub record_size: u32, + /// uint64_t timestamp: a timestamp of when the record was created. + pub timestamp: u64, +} + +unsafe impl object::Pod for RecordHeader {} + +/// The CodeLoadRecord is used for describing jitted functions +#[derive(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct CodeLoadRecord { + /// Fixed sized header that describes this record + pub header: RecordHeader, + /// `uint32_t pid`: OS process id of the runtime generating the jitted code + pub pid: u32, + /// `uint32_t tid`: OS thread identification of the runtime thread generating the jitted code + pub tid: u32, + /// `uint64_t vma`: virtual address of jitted code start + pub virtual_address: u64, + /// `uint64_t code_addr`: code start address for the jitted code. By default vma = code_addr + pub address: u64, + /// `uint64_t code_size`: size in bytes of the generated jitted code + pub size: u64, + /// `uint64_t code_index`: unique identifier for the jitted code (see below) + pub index: u64, +} + +unsafe impl object::Pod for CodeLoadRecord {} + +/// Describes source line information for a jitted function +#[derive(Debug, Default)] +#[repr(C)] +pub struct DebugEntry { + /// `uint64_t code_addr`: address of function for which the debug information is generated + pub address: u64, + /// `uint32_t line`: source file line number (starting at 1) + pub line: u32, + /// `uint32_t discrim`: column discriminator, 0 is default + pub discriminator: u32, + /// `char name[n]`: source file name in ASCII, including null termination + pub 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(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct DebugInfoRecord { + /// Fixed sized header that describes this record + pub header: RecordHeader, + /// `uint64_t code_addr`: address of function for which the debug information is generated + pub address: u64, + /// `uint64_t nr_entry`: number of debug entries for the function appended to this record + pub count: u64, +} + +unsafe impl object::Pod for DebugInfoRecord {} + +/// Fixed-sized header for each jitdump file +#[derive(Debug, Default, Clone, Copy)] +#[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 + pub magic: u32, + /// `uint32_t version`: a 4-byte value representing the format version. It is currently set to 2 + pub version: u32, + /// `uint32_t total_size`: size in bytes of file header + pub size: u32, + /// `uint32_t elf_mach`: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h) + pub e_machine: u32, + /// `uint32_t pad1`: padding. Reserved for future use + pub pad1: u32, + /// `uint32_t pid`: JIT runtime process identification (OS specific) + pub pid: u32, + /// `uint64_t timestamp`: timestamp of when the file was created + pub timestamp: u64, + /// `uint64_t flags`: a bitmask of flags + pub flags: u64, +} + +unsafe impl object::Pod for FileHeader {} + +/// Interface for driving the creation of jitdump files +pub struct JitDumpFile { + /// File instance for the jit dump file + jitdump_file: File, + + map_addr: usize, + + /// Unique identifier for jitted code + code_index: u64, + + e_machine: u32, +} + +impl JitDumpFile { + /// Intialize a JitDumpAgent and write out the header + pub fn new(filename: impl AsRef, e_machine: u32) -> io::Result { + let jitdump_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(filename.as_ref())?; + + // After we make our `*.dump` file we execute an `mmap` syscall, + // specifically with executable permissions, to map it into our address + // space. This is required so `perf inject` will work later. The `perf + // inject` command will see that an mmap syscall happened, and it'll see + // the filename we mapped, and that'll trigger it to actually read and + // parse the file. + // + // To match what some perf examples are doing we keep this `mmap` alive + // until this agent goes away. + let map_addr = unsafe { + let ptr = rustix::io::mmap( + ptr::null_mut(), + rustix::process::page_size(), + rustix::io::ProtFlags::EXEC | rustix::io::ProtFlags::READ, + rustix::io::MapFlags::PRIVATE, + &jitdump_file, + 0, + )?; + ptr as usize + }; + let mut state = JitDumpFile { + jitdump_file, + map_addr, + code_index: 0, + e_machine, + }; + state.write_file_header()?; + Ok(state) + } +} + +impl JitDumpFile { + /// Returns timestamp from a single source + pub fn get_time_stamp(&self) -> u64 { + // We need to use `CLOCK_MONOTONIC` on Linux which is what `Instant` + // conveniently also uses, but `Instant` doesn't allow us to get access + // to nanoseconds as an internal detail, so we calculate the nanoseconds + // ourselves here. + let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic); + // TODO: What does it mean for either sec or nsec to be negative? + (ts.tv_sec * 1_000_000_000 + ts.tv_nsec) as u64 + } + + /// Returns the next code index + pub fn next_code_index(&mut self) -> u64 { + let code_index = self.code_index; + self.code_index += 1; + code_index + } + + pub fn write_file_header(&mut self) -> io::Result<()> { + let header = FileHeader { + timestamp: self.get_time_stamp(), + e_machine: self.e_machine, + magic: 0x4A695444, + version: 1, + size: mem::size_of::() as u32, + pad1: 0, + pid: process::id(), + flags: 0, + }; + + self.jitdump_file.write_all(object::bytes_of(&header))?; + Ok(()) + } + + pub fn write_code_load_record( + &mut self, + record_name: &str, + cl_record: CodeLoadRecord, + code_buffer: &[u8], + ) -> io::Result<()> { + self.jitdump_file.write_all(object::bytes_of(&cl_record))?; + 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. + pub fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> io::Result<()> { + self.jitdump_file.write_all(object::bytes_of(&dir_record))?; + Ok(()) + } + + /// Write DebugInfoRecord to open jit dump file. + /// Must be written before the corresponding CodeLoadRecord. + pub fn write_debug_info_entries(&mut self, die_entries: Vec) -> io::Result<()> { + for entry in die_entries.iter() { + self.jitdump_file + .write_all(object::bytes_of(&entry.address))?; + self.jitdump_file.write_all(object::bytes_of(&entry.line))?; + self.jitdump_file + .write_all(object::bytes_of(&entry.discriminator))?; + self.jitdump_file.write_all(entry.filename.as_bytes())?; + self.jitdump_file.write_all(b"\0")?; + } + Ok(()) + } + + pub fn dump_code_load_record( + &mut self, + method_name: &str, + addr: *const u8, + len: usize, + timestamp: u64, + pid: u32, + tid: u32, + ) -> io::Result<()> { + 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, + }; + + let clr = CodeLoadRecord { + header: rh, + pid, + tid, + virtual_address: addr as u64, + address: addr as u64, + size: len as u64, + index: self.next_code_index(), + }; + + unsafe { + let code_buffer: &[u8] = std::slice::from_raw_parts(addr, len); + self.write_code_load_record(method_name, clr, code_buffer) + } + } +} + +impl Drop for JitDumpFile { + fn drop(&mut self) { + unsafe { + rustix::io::munmap(self.map_addr as *mut _, rustix::process::page_size()).unwrap(); + } + } +} diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 44c52dc306..d65ef3a7e8 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [dependencies] wasmtime-environ = { path = "../environ", version = "=0.34.0" } +wasmtime-jit-debug = { path = "../jit-debug", version = "=0.34.0", features = ["perf_jitdump"], optional = true } wasmtime-runtime = { path = "../runtime", version = "=0.34.0" } region = "2.2.0" thiserror = "1.0.4" @@ -35,7 +36,7 @@ winapi = { version = "0.3.8", features = ["winnt", "impl-default"] } rustix = "0.33.0" [features] -jitdump = [] +jitdump = ['wasmtime-jit-debug'] vtune = ['ittapi-rs'] [badges] diff --git a/crates/jit/src/profiling/jitdump_linux.rs b/crates/jit/src/profiling/jitdump_linux.rs index f96a4dff5e..8ff1c3a11f 100644 --- a/crates/jit/src/profiling/jitdump_linux.rs +++ b/crates/jit/src/profiling/jitdump_linux.rs @@ -14,124 +14,14 @@ use crate::{CompiledModule, ProfilingAgent}; use anyhow::Result; use object::{Object, ObjectSection}; -use std::fmt::Debug; -use std::fs::{File, OpenOptions}; -use std::io::Write; -use std::ptr; use std::sync::Mutex; use std::{borrow, mem, process}; use target_lexicon::Architecture; use wasmtime_environ::EntityRef; +use wasmtime_jit_debug::perf_jitdump::*; use object::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(Debug, Default, Clone, Copy)] -#[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, -} - -unsafe impl object::Pod for RecordHeader {} - -/// The CodeLoadRecord is used for describing jitted functions -#[derive(Debug, Default, Clone, Copy)] -#[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, -} - -unsafe impl object::Pod for CodeLoadRecord {} - -/// Describes source line information for a jitted function -#[derive(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(Debug, Default, Clone, Copy)] -#[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, -} - -unsafe impl object::Pod for DebugInfoRecord {} - -/// Fixed-sized header for each jitdump file -#[derive(Debug, Default, Clone, Copy)] -#[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, -} - -unsafe impl object::Pod for FileHeader {} - /// Interface for driving the creation of jitdump files pub struct JitDumpAgent { // Note that we use a mutex internally to serialize writing out to our @@ -141,13 +31,7 @@ pub struct JitDumpAgent { } struct State { - /// File instance for the jit dump file - jitdump_file: File, - - map_addr: usize, - - /// Unique identifier for jitted code - code_index: u64, + jitdump_file: JitDumpFile, /// Flag for experimenting with dumping code load record /// after each function (true) or after each module. This @@ -159,42 +43,23 @@ impl JitDumpAgent { /// Intialize a JitDumpAgent and write out the header pub fn new() -> Result { let filename = format!("./jit-{}.dump", process::id()); - let jitdump_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(&filename)?; - // After we make our `*.dump` file we execute an `mmap` syscall, - // specifically with executable permissions, to map it into our address - // space. This is required so `perf inject` will work later. The `perf - // inject` command will see that an mmap syscall happened, and it'll see - // the filename we mapped, and that'll trigger it to actually read and - // parse the file. - // - // To match what some perf examples are doing we keep this `mmap` alive - // until this agent goes away. - let map_addr = unsafe { - let ptr = rustix::io::mmap( - ptr::null_mut(), - rustix::process::page_size(), - rustix::io::ProtFlags::EXEC | rustix::io::ProtFlags::READ, - rustix::io::MapFlags::PRIVATE, - &jitdump_file, - 0, - )?; - ptr as usize + let e_machine = match target_lexicon::HOST.architecture { + Architecture::X86_64 => elf::EM_X86_64 as u32, + Architecture::X86_32(_) => elf::EM_386 as u32, + Architecture::Arm(_) => elf::EM_ARM as u32, + Architecture::Aarch64(_) => elf::EM_AARCH64 as u32, + Architecture::S390x => elf::EM_S390 as u32, + _ => unimplemented!("unrecognized architecture"), }; - let mut state = State { - jitdump_file, - map_addr, - code_index: 0, - dump_funcs: true, - }; - state.write_file_header()?; + + let jitdump_file = JitDumpFile::new(filename, e_machine)?; + Ok(JitDumpAgent { - state: Mutex::new(state), + state: Mutex::new(State { + jitdump_file, + dump_funcs: true, + }), }) } } @@ -212,80 +77,6 @@ impl ProfilingAgent for JitDumpAgent { } impl State { - /// Returns timestamp from a single source - fn get_time_stamp(&self) -> u64 { - // We need to use `CLOCK_MONOTONIC` on Linux which is what `Instant` - // conveniently also uses, but `Instant` doesn't allow us to get access - // to nanoseconds as an internal detail, so we calculate the nanoseconds - // ourselves here. - let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic); - // TODO: What does it mean for either sec or nsec to be negative? - (ts.tv_sec * 1_000_000_000 + ts.tv_nsec) as u64 - } - - /// Returns the ELF machine architecture. - fn get_e_machine(&self) -> u32 { - match target_lexicon::HOST.architecture { - Architecture::X86_64 => elf::EM_X86_64 as u32, - Architecture::X86_32(_) => elf::EM_386 as u32, - Architecture::Arm(_) => elf::EM_ARM as u32, - Architecture::Aarch64(_) => elf::EM_AARCH64 as u32, - Architecture::S390x => elf::EM_S390 as u32, - _ => unimplemented!("unrecognized architecture"), - } - } - - fn write_file_header(&mut self) -> Result<()> { - let header = FileHeader { - timestamp: self.get_time_stamp(), - e_machine: self.get_e_machine(), - magic: 0x4A695444, - version: 1, - size: mem::size_of::() as u32, - pad1: 0, - pid: process::id(), - flags: 0, - }; - - self.jitdump_file.write_all(object::bytes_of(&header))?; - Ok(()) - } - - fn write_code_load_record( - &mut self, - record_name: &str, - cl_record: CodeLoadRecord, - code_buffer: &[u8], - ) -> Result<()> { - self.jitdump_file.write_all(object::bytes_of(&cl_record))?; - 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<()> { - self.jitdump_file.write_all(object::bytes_of(&dir_record))?; - 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<()> { - for entry in die_entries.iter() { - self.jitdump_file - .write_all(object::bytes_of(&entry.address))?; - self.jitdump_file.write_all(object::bytes_of(&entry.line))?; - self.jitdump_file - .write_all(object::bytes_of(&entry.discriminator))?; - 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: &CompiledModule, dbg_image: Option<&[u8]>) { let pid = process::id(); @@ -301,18 +92,28 @@ impl State { ); } } else { - let timestamp = self.get_time_stamp(); + let timestamp = self.jitdump_file.get_time_stamp(); let name = super::debug_name(module, idx); - self.dump_code_load_record(&name, addr, len, timestamp, pid, tid); + if let Err(err) = self + .jitdump_file + .dump_code_load_record(&name, addr, len, timestamp, pid, tid) + { + println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); + } } } // Note: these are the trampolines into exported functions. for (idx, func, len) in module.trampolines() { let (addr, len) = (func as usize as *const u8, len); - let timestamp = self.get_time_stamp(); + let timestamp = self.jitdump_file.get_time_stamp(); let name = format!("wasm::trampoline[{}]", idx.index()); - self.dump_code_load_record(&name, addr, len, timestamp, pid, tid); + if let Err(err) = self + .jitdump_file + .dump_code_load_record(&name, addr, len, timestamp, pid, tid) + { + println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); + } } } @@ -324,44 +125,12 @@ impl State { pid: u32, tid: u32, ) { - let timestamp = self.get_time_stamp(); - self.dump_code_load_record(name, addr, size, 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, - }; - - let clr = CodeLoadRecord { - header: rh, - pid, - 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); - if let Err(err) = self.write_code_load_record(method_name, clr, code_buffer) { - println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); - } + let timestamp = self.jitdump_file.get_time_stamp(); + if let Err(err) = self + .jitdump_file + .dump_code_load_record(&name, addr, size, timestamp, pid, tid) + { + println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); } } @@ -414,8 +183,13 @@ impl State { break; } if !self.dump_funcs { - let timestamp = self.get_time_stamp(); - self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); + let timestamp = self.jitdump_file.get_time_stamp(); + if let Err(err) = + self.jitdump_file + .dump_code_load_record(module_name, addr, len, timestamp, pid, tid) + { + println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); + } } Ok(()) } @@ -510,16 +284,17 @@ impl State { 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; + clr.index = self.jitdump_file.next_code_index(); self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?; - clr.header.timestamp = self.get_time_stamp(); + clr.header.timestamp = self.jitdump_file.get_time_stamp(); 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); + let _ = + self.jitdump_file + .write_code_load_record(&clr_name, clr, code_buffer); } } } else { @@ -597,7 +372,7 @@ impl State { size: u64, file_suffix: Option<&str>, ) -> Result<()> { - let timestamp = self.get_time_stamp(); + let timestamp = self.jitdump_file.get_time_stamp(); if let Some(program) = unit.line_program.clone() { let mut debug_info_record = DebugInfoRecord { header: RecordHeader { @@ -654,21 +429,13 @@ impl State { 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); + let _ = self.jitdump_file.write_debug_info_record(debug_info_record); + let _ = self.jitdump_file.write_debug_info_entries(debug_entries); } Ok(()) } } -impl Drop for State { - fn drop(&mut self) { - unsafe { - rustix::io::munmap(self.map_addr as *mut _, rustix::process::page_size()).unwrap(); - } - } -} - trait Reader: gimli::Reader + Send + Sync {} impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 9a620698c3..f3e8c90da2 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -13,6 +13,7 @@ edition = "2018" [dependencies] wasmtime-environ = { path = "../environ", version = "=0.34.0" } wasmtime-fiber = { path = "../fiber", version = "=0.34.0", optional = true } +wasmtime-jit-debug = { path = "../jit-debug", version = "=0.34.0", features = ["gdb_jit_int"] } region = "2.1.0" libc = { version = "0.2.112", default-features = false } log = "0.4.8" @@ -22,7 +23,6 @@ thiserror = "1.0.4" more-asserts = "0.2.1" cfg-if = "1.0" backtrace = "0.3.61" -lazy_static = "1.3.0" rand = "0.8.3" anyhow = "1.0.38" memfd = { version = "0.4.1", optional = true } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 7b3ac04af9..7ff5474538 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -34,7 +34,6 @@ mod export; mod externref; mod imports; mod instance; -mod jit_int; mod memory; mod mmap; mod mmap_vec; @@ -45,6 +44,8 @@ mod vmcontext; pub mod debug_builtins; pub mod libcalls; +pub use wasmtime_jit_debug::gdb_jit_int::GdbJitImageRegistration; + pub use crate::export::*; pub use crate::externref::*; pub use crate::imports::Imports; @@ -56,7 +57,6 @@ pub use crate::instance::{ pub use crate::instance::{ InstanceLimits, ModuleLimits, PoolingAllocationStrategy, PoolingInstanceAllocator, }; -pub use crate::jit_int::GdbJitImageRegistration; pub use crate::memory::{DefaultMemoryCreator, Memory, RuntimeLinearMemory, RuntimeMemoryCreator}; pub use crate::mmap::Mmap; pub use crate::mmap_vec::MmapVec; diff --git a/scripts/publish.rs b/scripts/publish.rs index 1069285f9f..9e7c20d3ac 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -40,6 +40,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wiggle-generate", "wiggle-macro", // wasmtime + "wasmtime-jit-debug", "wasmtime-fiber", "wasmtime-environ", "wasmtime-runtime",