Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/jit-debug/src/perf_jitdump.rs
1692 views
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
296