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 writing. Note, this record must precede 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     map_len: usize,
135 
136     /// Unique identifier for jitted code
137     code_index: u64,
138 
139     e_machine: u32,
140 }
141 
142 impl JitDumpFile {
143     /// Initialize a JitDumpAgent and write out the header
144     pub fn new(filename: impl AsRef<Path>, e_machine: u32) -> io::Result<Self> {
145         let jitdump_file = OpenOptions::new()
146             .read(true)
147             .write(true)
148             .create(true)
149             .truncate(true)
150             .open(filename.as_ref())?;
151 
152         // After we make our `*.dump` file we execute an `mmap` syscall,
153         // specifically with executable permissions, to map it into our address
154         // space. This is required so `perf inject` will work later. The `perf
155         // inject` command will see that an mmap syscall happened, and it'll see
156         // the filename we mapped, and that'll trigger it to actually read and
157         // parse the file.
158         //
159         // To match what some perf examples are doing we keep this `mmap` alive
160         // until this agent goes away.
161         let map_len = 1024;
162         let map_addr = unsafe {
163             let ptr = rustix::mm::mmap(
164                 ptr::null_mut(),
165                 map_len,
166                 rustix::mm::ProtFlags::EXEC | rustix::mm::ProtFlags::READ,
167                 rustix::mm::MapFlags::PRIVATE,
168                 &jitdump_file,
169                 0,
170             )?;
171             ptr as usize
172         };
173         let mut state = JitDumpFile {
174             jitdump_file,
175             map_addr,
176             map_len,
177             code_index: 0,
178             e_machine,
179         };
180         state.write_file_header()?;
181         Ok(state)
182     }
183 }
184 
185 impl JitDumpFile {
186     /// Returns timestamp from a single source
187     pub fn get_time_stamp(&self) -> u64 {
188         // We need to use `CLOCK_MONOTONIC` on Linux which is what `Instant`
189         // conveniently also uses, but `Instant` doesn't allow us to get access
190         // to nanoseconds as an internal detail, so we calculate the nanoseconds
191         // ourselves here.
192         let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
193         // TODO: What does it mean for either sec or nsec to be negative?
194         (ts.tv_sec * 1_000_000_000 + ts.tv_nsec) as u64
195     }
196 
197     /// Returns the next code index
198     pub fn next_code_index(&mut self) -> u64 {
199         let code_index = self.code_index;
200         self.code_index += 1;
201         code_index
202     }
203 
204     pub fn write_file_header(&mut self) -> io::Result<()> {
205         let header = FileHeader {
206             timestamp: self.get_time_stamp(),
207             e_machine: self.e_machine,
208             magic: 0x4A695444,
209             version: 1,
210             size: mem::size_of::<FileHeader>() as u32,
211             pad1: 0,
212             pid: process::id(),
213             flags: 0,
214         };
215 
216         self.jitdump_file.write_all(object::bytes_of(&header))?;
217         Ok(())
218     }
219 
220     pub fn write_code_load_record(
221         &mut self,
222         record_name: &str,
223         cl_record: CodeLoadRecord,
224         code_buffer: &[u8],
225     ) -> io::Result<()> {
226         self.jitdump_file.write_all(object::bytes_of(&cl_record))?;
227         self.jitdump_file.write_all(record_name.as_bytes())?;
228         self.jitdump_file.write_all(b"\0")?;
229         self.jitdump_file.write_all(code_buffer)?;
230         Ok(())
231     }
232 
233     /// Write DebugInfoRecord to open jit dump file.
234     /// Must be written before the corresponding CodeLoadRecord.
235     pub fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> io::Result<()> {
236         self.jitdump_file.write_all(object::bytes_of(&dir_record))?;
237         Ok(())
238     }
239 
240     /// Write DebugInfoRecord to open jit dump file.
241     /// Must be written before the corresponding CodeLoadRecord.
242     pub fn write_debug_info_entries(&mut self, die_entries: Vec<DebugEntry>) -> io::Result<()> {
243         for entry in die_entries.iter() {
244             self.jitdump_file
245                 .write_all(object::bytes_of(&entry.address))?;
246             self.jitdump_file.write_all(object::bytes_of(&entry.line))?;
247             self.jitdump_file
248                 .write_all(object::bytes_of(&entry.discriminator))?;
249             self.jitdump_file.write_all(entry.filename.as_bytes())?;
250             self.jitdump_file.write_all(b"\0")?;
251         }
252         Ok(())
253     }
254 
255     pub fn dump_code_load_record(
256         &mut self,
257         method_name: &str,
258         code: &[u8],
259         timestamp: u64,
260         pid: u32,
261         tid: u32,
262     ) -> io::Result<()> {
263         let name_len = method_name.len() + 1;
264         let size_limit = mem::size_of::<CodeLoadRecord>();
265 
266         let rh = RecordHeader {
267             id: RecordId::JitCodeLoad as u32,
268             record_size: size_limit as u32 + name_len as u32 + code.len() as u32,
269             timestamp,
270         };
271 
272         let clr = CodeLoadRecord {
273             header: rh,
274             pid,
275             tid,
276             virtual_address: code.as_ptr() as u64,
277             address: code.as_ptr() as u64,
278             size: code.len() as u64,
279             index: self.next_code_index(),
280         };
281 
282         self.write_code_load_record(method_name, clr, code)
283     }
284 }
285 
286 impl Drop for JitDumpFile {
287     fn drop(&mut self) {
288         unsafe {
289             rustix::mm::munmap(self.map_addr as *mut _, self.map_len).unwrap();
290         }
291     }
292 }
293