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