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::string::String;
20 use std::vec::Vec;
21 use std::{mem, process};
22 
23 /// Defines jitdump record types
24 #[repr(u32)]
25 pub 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)]
41 pub 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 
50 unsafe impl object::Pod for RecordHeader {}
51 
52 /// The CodeLoadRecord is used for describing jitted functions
53 #[derive(Debug, Default, Clone, Copy)]
54 #[repr(C)]
55 pub 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 
72 unsafe impl object::Pod for CodeLoadRecord {}
73 
74 /// Describes source line information for a jitted function
75 #[derive(Debug, Default)]
76 #[repr(C)]
77 pub 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)]
93 pub 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 
102 unsafe impl object::Pod for DebugInfoRecord {}
103 
104 /// Fixed-sized header for each jitdump file
105 #[derive(Debug, Default, Clone, Copy)]
106 #[repr(C)]
107 pub 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 
128 unsafe impl object::Pod for FileHeader {}
129 
130 /// Interface for driving the creation of jitdump files
131 pub 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 
144 impl 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 
187 impl 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 
288 impl 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 }
295