Path: blob/main/crates/jit-debug/src/perf_jitdump.rs
1692 views
//! Support for jitdump files which can be used by perf for profiling jitted code.1//! Spec definitions for the output format is as described here:2//! <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt>3//!4//! Usage Example:5//! Record6//! sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g --profile=jitdump test.wasm7//! Combine8//! sudo perf inject -v -j -i perf.data -o perf.jit.data9//! Report10//! sudo perf report -i perf.jit.data -F+period,srcline1112use std::fmt::Debug;13use std::fs::{File, OpenOptions};14use std::io;15use std::io::Write;16use std::path::Path;17use std::ptr;18use std::string::String;19use std::vec::Vec;20use std::{mem, process};2122/// Defines jitdump record types23#[repr(u32)]24pub enum RecordId {25/// Value 0: JIT_CODE_LOAD: record describing a jitted function26JitCodeLoad = 0,27/// Value 1: JIT_CODE_MOVE: record describing an already jitted function which is moved28_JitCodeMove = 1,29/// Value 2: JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function30JitCodeDebugInfo = 2,31/// Value 3: JIT_CODE_CLOSE: record marking the end of the jit runtime (optional)32_JitCodeClose = 3,33/// Value 4: JIT_CODE_UNWINDING_INFO: record describing a function unwinding information34_JitCodeUnwindingInfo = 4,35}3637/// Each record starts with this fixed size record header which describes the record that follows38#[derive(Debug, Default, Clone, Copy)]39#[repr(C)]40pub struct RecordHeader {41/// uint32_t id: a value identifying the record type (see below)42pub id: u32,43/// uint32_t total_size: the size in bytes of the record including the header.44pub record_size: u32,45/// uint64_t timestamp: a timestamp of when the record was created.46pub timestamp: u64,47}4849unsafe impl object::Pod for RecordHeader {}5051/// The CodeLoadRecord is used for describing jitted functions52#[derive(Debug, Default, Clone, Copy)]53#[repr(C)]54pub struct CodeLoadRecord {55/// Fixed sized header that describes this record56pub header: RecordHeader,57/// `uint32_t pid`: OS process id of the runtime generating the jitted code58pub pid: u32,59/// `uint32_t tid`: OS thread identification of the runtime thread generating the jitted code60pub tid: u32,61/// `uint64_t vma`: virtual address of jitted code start62pub virtual_address: u64,63/// `uint64_t code_addr`: code start address for the jitted code. By default vma = code_addr64pub address: u64,65/// `uint64_t code_size`: size in bytes of the generated jitted code66pub size: u64,67/// `uint64_t code_index`: unique identifier for the jitted code (see below)68pub index: u64,69}7071unsafe impl object::Pod for CodeLoadRecord {}7273/// Describes source line information for a jitted function74#[derive(Debug, Default)]75#[repr(C)]76pub struct DebugEntry {77/// `uint64_t code_addr`: address of function for which the debug information is generated78pub address: u64,79/// `uint32_t line`: source file line number (starting at 1)80pub line: u32,81/// `uint32_t discrim`: column discriminator, 0 is default82pub discriminator: u32,83/// `char name[n]`: source file name in ASCII, including null termination84pub filename: String,85}8687/// Describes debug information for a jitted function. An array of debug entries are88/// appended to this record during writing. Note, this record must precede the code89/// load record that describes the same jitted function.90#[derive(Debug, Default, Clone, Copy)]91#[repr(C)]92pub struct DebugInfoRecord {93/// Fixed sized header that describes this record94pub header: RecordHeader,95/// `uint64_t code_addr`: address of function for which the debug information is generated96pub address: u64,97/// `uint64_t nr_entry`: number of debug entries for the function appended to this record98pub count: u64,99}100101unsafe impl object::Pod for DebugInfoRecord {}102103/// Fixed-sized header for each jitdump file104#[derive(Debug, Default, Clone, Copy)]105#[repr(C)]106pub struct FileHeader {107/// `uint32_t magic`: a magic number tagging the file type. The value is 4-byte long and represents the108/// string "JiTD" in ASCII form. It is 0x4A695444 or 0x4454694a depending on the endianness. The field can109/// be used to detect the endianness of the file110pub magic: u32,111/// `uint32_t version`: a 4-byte value representing the format version. It is currently set to 2112pub version: u32,113/// `uint32_t total_size`: size in bytes of file header114pub size: u32,115/// `uint32_t elf_mach`: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h)116pub e_machine: u32,117/// `uint32_t pad1`: padding. Reserved for future use118pub pad1: u32,119/// `uint32_t pid`: JIT runtime process identification (OS specific)120pub pid: u32,121/// `uint64_t timestamp`: timestamp of when the file was created122pub timestamp: u64,123/// `uint64_t flags`: a bitmask of flags124pub flags: u64,125}126127unsafe impl object::Pod for FileHeader {}128129/// Interface for driving the creation of jitdump files130pub struct JitDumpFile {131/// File instance for the jit dump file132jitdump_file: File,133134map_addr: usize,135map_len: usize,136137/// Unique identifier for jitted code138code_index: u64,139140e_machine: u32,141}142143impl JitDumpFile {144/// Initialize a JitDumpAgent and write out the header145pub fn new(filename: impl AsRef<Path>, e_machine: u32) -> io::Result<Self> {146let jitdump_file = OpenOptions::new()147.read(true)148.write(true)149.create(true)150.truncate(true)151.open(filename.as_ref())?;152153// After we make our `*.dump` file we execute an `mmap` syscall,154// specifically with executable permissions, to map it into our address155// space. This is required so `perf inject` will work later. The `perf156// inject` command will see that an mmap syscall happened, and it'll see157// the filename we mapped, and that'll trigger it to actually read and158// parse the file.159//160// To match what some perf examples are doing we keep this `mmap` alive161// until this agent goes away.162let map_len = 1024;163let map_addr = unsafe {164let ptr = rustix::mm::mmap(165ptr::null_mut(),166map_len,167rustix::mm::ProtFlags::EXEC | rustix::mm::ProtFlags::READ,168rustix::mm::MapFlags::PRIVATE,169&jitdump_file,1700,171)?;172ptr as usize173};174let mut state = JitDumpFile {175jitdump_file,176map_addr,177map_len,178code_index: 0,179e_machine,180};181state.write_file_header()?;182Ok(state)183}184}185186impl JitDumpFile {187/// Returns timestamp from a single source188pub fn get_time_stamp(&self) -> u64 {189// We need to use `CLOCK_MONOTONIC` on Linux which is what `Instant`190// conveniently also uses, but `Instant` doesn't allow us to get access191// to nanoseconds as an internal detail, so we calculate the nanoseconds192// ourselves here.193let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);194// TODO: What does it mean for either sec or nsec to be negative?195(ts.tv_sec * 1_000_000_000 + ts.tv_nsec) as u64196}197198/// Returns the next code index199pub fn next_code_index(&mut self) -> u64 {200let code_index = self.code_index;201self.code_index += 1;202code_index203}204205pub fn write_file_header(&mut self) -> io::Result<()> {206let header = FileHeader {207timestamp: self.get_time_stamp(),208e_machine: self.e_machine,209magic: 0x4A695444,210version: 1,211size: mem::size_of::<FileHeader>() as u32,212pad1: 0,213pid: process::id(),214flags: 0,215};216217self.jitdump_file.write_all(object::bytes_of(&header))?;218Ok(())219}220221pub fn write_code_load_record(222&mut self,223record_name: &str,224cl_record: CodeLoadRecord,225code_buffer: &[u8],226) -> io::Result<()> {227self.jitdump_file.write_all(object::bytes_of(&cl_record))?;228self.jitdump_file.write_all(record_name.as_bytes())?;229self.jitdump_file.write_all(b"\0")?;230self.jitdump_file.write_all(code_buffer)?;231Ok(())232}233234/// Write DebugInfoRecord to open jit dump file.235/// Must be written before the corresponding CodeLoadRecord.236pub fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> io::Result<()> {237self.jitdump_file.write_all(object::bytes_of(&dir_record))?;238Ok(())239}240241/// Write DebugInfoRecord to open jit dump file.242/// Must be written before the corresponding CodeLoadRecord.243pub fn write_debug_info_entries(&mut self, die_entries: Vec<DebugEntry>) -> io::Result<()> {244for entry in die_entries.iter() {245self.jitdump_file246.write_all(object::bytes_of(&entry.address))?;247self.jitdump_file.write_all(object::bytes_of(&entry.line))?;248self.jitdump_file249.write_all(object::bytes_of(&entry.discriminator))?;250self.jitdump_file.write_all(entry.filename.as_bytes())?;251self.jitdump_file.write_all(b"\0")?;252}253Ok(())254}255256pub fn dump_code_load_record(257&mut self,258method_name: &str,259code: &[u8],260timestamp: u64,261pid: u32,262tid: u32,263) -> io::Result<()> {264let name_len = method_name.len() + 1;265let size_limit = mem::size_of::<CodeLoadRecord>();266267let rh = RecordHeader {268id: RecordId::JitCodeLoad as u32,269record_size: size_limit as u32 + name_len as u32 + code.len() as u32,270timestamp,271};272273let clr = CodeLoadRecord {274header: rh,275pid,276tid,277virtual_address: code.as_ptr() as u64,278address: code.as_ptr() as u64,279size: code.len() as u64,280index: self.next_code_index(),281};282283self.write_code_load_record(method_name, clr, code)284}285}286287impl Drop for JitDumpFile {288fn drop(&mut self) {289unsafe {290rustix::mm::munmap(self.map_addr as *mut _, self.map_len).unwrap();291}292}293}294295296