wasmtime_internal_jit_debug/
perf_jitdump.rs

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