1 //! Support for jitdump files which can be used by perf for profiling jitted code. 2 //! Spec definitions for the output format is as described here: 3 //! <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt> 4 //! 5 //! Usage Example: 6 //! Record 7 //! sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g --profile=jitdump test.wasm 8 //! Combine 9 //! sudo perf inject -v -j -i perf.data -o perf.jit.data 10 //! Report 11 //! sudo perf report -i perf.jit.data -F+period,srcline 12 13 use std::fmt::Debug; 14 use std::fs::{File, OpenOptions}; 15 use std::io; 16 use std::io::Write; 17 use std::path::Path; 18 use std::ptr; 19 use std::{mem, process}; 20 21 /// Defines jitdump record types 22 #[repr(u32)] 23 pub enum RecordId { 24 /// Value 0: JIT_CODE_LOAD: record describing a jitted function 25 JitCodeLoad = 0, 26 /// Value 1: JIT_CODE_MOVE: record describing an already jitted function which is moved 27 _JitCodeMove = 1, 28 /// Value 2: JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function 29 JitCodeDebugInfo = 2, 30 /// Value 3: JIT_CODE_CLOSE: record marking the end of the jit runtime (optional) 31 _JitCodeClose = 3, 32 /// Value 4: JIT_CODE_UNWINDING_INFO: record describing a function unwinding information 33 _JitCodeUnwindingInfo = 4, 34 } 35 36 /// Each record starts with this fixed size record header which describes the record that follows 37 #[derive(Debug, Default, Clone, Copy)] 38 #[repr(C)] 39 pub struct RecordHeader { 40 /// uint32_t id: a value identifying the record type (see below) 41 pub id: u32, 42 /// uint32_t total_size: the size in bytes of the record including the header. 43 pub record_size: u32, 44 /// uint64_t timestamp: a timestamp of when the record was created. 45 pub timestamp: u64, 46 } 47 48 unsafe impl object::Pod for RecordHeader {} 49 50 /// The CodeLoadRecord is used for describing jitted functions 51 #[derive(Debug, Default, Clone, Copy)] 52 #[repr(C)] 53 pub struct CodeLoadRecord { 54 /// Fixed sized header that describes this record 55 pub header: RecordHeader, 56 /// `uint32_t pid`: OS process id of the runtime generating the jitted code 57 pub pid: u32, 58 /// `uint32_t tid`: OS thread identification of the runtime thread generating the jitted code 59 pub tid: u32, 60 /// `uint64_t vma`: virtual address of jitted code start 61 pub virtual_address: u64, 62 /// `uint64_t code_addr`: code start address for the jitted code. By default vma = code_addr 63 pub address: u64, 64 /// `uint64_t code_size`: size in bytes of the generated jitted code 65 pub size: u64, 66 /// `uint64_t code_index`: unique identifier for the jitted code (see below) 67 pub index: u64, 68 } 69 70 unsafe impl object::Pod for CodeLoadRecord {} 71 72 /// Describes source line information for a jitted function 73 #[derive(Debug, Default)] 74 #[repr(C)] 75 pub struct DebugEntry { 76 /// `uint64_t code_addr`: address of function for which the debug information is generated 77 pub address: u64, 78 /// `uint32_t line`: source file line number (starting at 1) 79 pub line: u32, 80 /// `uint32_t discrim`: column discriminator, 0 is default 81 pub discriminator: u32, 82 /// `char name[n]`: source file name in ASCII, including null termination 83 pub filename: String, 84 } 85 86 /// Describes debug information for a jitted function. An array of debug entries are 87 /// appended to this record during writting. Note, this record must preceed the code 88 /// load record that describes the same jitted function. 89 #[derive(Debug, Default, Clone, Copy)] 90 #[repr(C)] 91 pub struct DebugInfoRecord { 92 /// Fixed sized header that describes this record 93 pub header: RecordHeader, 94 /// `uint64_t code_addr`: address of function for which the debug information is generated 95 pub address: u64, 96 /// `uint64_t nr_entry`: number of debug entries for the function appended to this record 97 pub count: u64, 98 } 99 100 unsafe impl object::Pod for DebugInfoRecord {} 101 102 /// Fixed-sized header for each jitdump file 103 #[derive(Debug, Default, Clone, Copy)] 104 #[repr(C)] 105 pub struct FileHeader { 106 /// `uint32_t magic`: a magic number tagging the file type. The value is 4-byte long and represents the 107 /// string "JiTD" in ASCII form. It is 0x4A695444 or 0x4454694a depending on the endianness. The field can 108 /// be used to detect the endianness of the file 109 pub magic: u32, 110 /// `uint32_t version`: a 4-byte value representing the format version. It is currently set to 2 111 pub version: u32, 112 /// `uint32_t total_size`: size in bytes of file header 113 pub size: u32, 114 /// `uint32_t elf_mach`: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h) 115 pub e_machine: u32, 116 /// `uint32_t pad1`: padding. Reserved for future use 117 pub pad1: u32, 118 /// `uint32_t pid`: JIT runtime process identification (OS specific) 119 pub pid: u32, 120 /// `uint64_t timestamp`: timestamp of when the file was created 121 pub timestamp: u64, 122 /// `uint64_t flags`: a bitmask of flags 123 pub flags: u64, 124 } 125 126 unsafe impl object::Pod for FileHeader {} 127 128 /// Interface for driving the creation of jitdump files 129 pub struct JitDumpFile { 130 /// File instance for the jit dump file 131 jitdump_file: File, 132 133 map_addr: usize, 134 135 /// Unique identifier for jitted code 136 code_index: u64, 137 138 e_machine: u32, 139 } 140 141 impl JitDumpFile { 142 /// Intialize a JitDumpAgent and write out the header 143 pub fn new(filename: impl AsRef<Path>, e_machine: u32) -> io::Result<Self> { 144 let jitdump_file = OpenOptions::new() 145 .read(true) 146 .write(true) 147 .create(true) 148 .truncate(true) 149 .open(filename.as_ref())?; 150 151 // After we make our `*.dump` file we execute an `mmap` syscall, 152 // specifically with executable permissions, to map it into our address 153 // space. This is required so `perf inject` will work later. The `perf 154 // inject` command will see that an mmap syscall happened, and it'll see 155 // the filename we mapped, and that'll trigger it to actually read and 156 // parse the file. 157 // 158 // To match what some perf examples are doing we keep this `mmap` alive 159 // until this agent goes away. 160 let map_addr = unsafe { 161 let ptr = rustix::mm::mmap( 162 ptr::null_mut(), 163 rustix::param::page_size(), 164 rustix::mm::ProtFlags::EXEC | rustix::mm::ProtFlags::READ, 165 rustix::mm::MapFlags::PRIVATE, 166 &jitdump_file, 167 0, 168 )?; 169 ptr as usize 170 }; 171 let mut state = JitDumpFile { 172 jitdump_file, 173 map_addr, 174 code_index: 0, 175 e_machine, 176 }; 177 state.write_file_header()?; 178 Ok(state) 179 } 180 } 181 182 impl JitDumpFile { 183 /// Returns timestamp from a single source 184 pub fn get_time_stamp(&self) -> u64 { 185 // We need to use `CLOCK_MONOTONIC` on Linux which is what `Instant` 186 // conveniently also uses, but `Instant` doesn't allow us to get access 187 // to nanoseconds as an internal detail, so we calculate the nanoseconds 188 // ourselves here. 189 let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic); 190 // TODO: What does it mean for either sec or nsec to be negative? 191 (ts.tv_sec * 1_000_000_000 + ts.tv_nsec) as u64 192 } 193 194 /// Returns the next code index 195 pub fn next_code_index(&mut self) -> u64 { 196 let code_index = self.code_index; 197 self.code_index += 1; 198 code_index 199 } 200 201 pub fn write_file_header(&mut self) -> io::Result<()> { 202 let header = FileHeader { 203 timestamp: self.get_time_stamp(), 204 e_machine: self.e_machine, 205 magic: 0x4A695444, 206 version: 1, 207 size: mem::size_of::<FileHeader>() as u32, 208 pad1: 0, 209 pid: process::id(), 210 flags: 0, 211 }; 212 213 self.jitdump_file.write_all(object::bytes_of(&header))?; 214 Ok(()) 215 } 216 217 pub fn write_code_load_record( 218 &mut self, 219 record_name: &str, 220 cl_record: CodeLoadRecord, 221 code_buffer: &[u8], 222 ) -> io::Result<()> { 223 self.jitdump_file.write_all(object::bytes_of(&cl_record))?; 224 self.jitdump_file.write_all(record_name.as_bytes())?; 225 self.jitdump_file.write_all(b"\0")?; 226 self.jitdump_file.write_all(code_buffer)?; 227 Ok(()) 228 } 229 230 /// Write DebugInfoRecord to open jit dump file. 231 /// Must be written before the corresponding CodeLoadRecord. 232 pub fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> io::Result<()> { 233 self.jitdump_file.write_all(object::bytes_of(&dir_record))?; 234 Ok(()) 235 } 236 237 /// Write DebugInfoRecord to open jit dump file. 238 /// Must be written before the corresponding CodeLoadRecord. 239 pub fn write_debug_info_entries(&mut self, die_entries: Vec<DebugEntry>) -> io::Result<()> { 240 for entry in die_entries.iter() { 241 self.jitdump_file 242 .write_all(object::bytes_of(&entry.address))?; 243 self.jitdump_file.write_all(object::bytes_of(&entry.line))?; 244 self.jitdump_file 245 .write_all(object::bytes_of(&entry.discriminator))?; 246 self.jitdump_file.write_all(entry.filename.as_bytes())?; 247 self.jitdump_file.write_all(b"\0")?; 248 } 249 Ok(()) 250 } 251 252 pub fn dump_code_load_record( 253 &mut self, 254 method_name: &str, 255 addr: *const u8, 256 len: usize, 257 timestamp: u64, 258 pid: u32, 259 tid: u32, 260 ) -> io::Result<()> { 261 let name_len = method_name.len() + 1; 262 let size_limit = mem::size_of::<CodeLoadRecord>(); 263 264 let rh = RecordHeader { 265 id: RecordId::JitCodeLoad as u32, 266 record_size: size_limit as u32 + name_len as u32 + len as u32, 267 timestamp, 268 }; 269 270 let clr = CodeLoadRecord { 271 header: rh, 272 pid, 273 tid, 274 virtual_address: addr as u64, 275 address: addr as u64, 276 size: len as u64, 277 index: self.next_code_index(), 278 }; 279 280 unsafe { 281 let code_buffer: &[u8] = std::slice::from_raw_parts(addr, len); 282 self.write_code_load_record(method_name, clr, code_buffer) 283 } 284 } 285 } 286 287 impl Drop for JitDumpFile { 288 fn drop(&mut self) { 289 unsafe { 290 rustix::mm::munmap(self.map_addr as *mut _, rustix::param::page_size()).unwrap(); 291 } 292 } 293 } 294