1use crate::{CompiledModule, ProfilingAgent};
15use anyhow::Result;
16use object::{Object, ObjectSection};
17use std::sync::Mutex;
18use std::{borrow, mem, process};
19use target_lexicon::Architecture;
20use wasmtime_environ::EntityRef;
21use wasmtime_jit_debug::perf_jitdump::*;
22
23use object::elf;
24
25pub struct JitDumpAgent {
27 dump_funcs: bool,
31}
32
33static JITDUMP_FILE: Mutex<Option<JitDumpFile>> = Mutex::new(None);
35
36impl JitDumpAgent {
37 pub fn new() -> Result<Self> {
39 let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
40
41 if jitdump_file.is_none() {
42 let filename = format!("./jit-{}.dump", process::id());
43 let e_machine = match target_lexicon::HOST.architecture {
44 Architecture::X86_64 => elf::EM_X86_64 as u32,
45 Architecture::X86_32(_) => elf::EM_386 as u32,
46 Architecture::Arm(_) => elf::EM_ARM as u32,
47 Architecture::Aarch64(_) => elf::EM_AARCH64 as u32,
48 Architecture::S390x => elf::EM_S390 as u32,
49 _ => unimplemented!("unrecognized architecture"),
50 };
51 *jitdump_file = Some(JitDumpFile::new(filename, e_machine)?);
52 }
53
54 Ok(JitDumpAgent { dump_funcs: true })
55 }
56}
57
58impl ProfilingAgent for JitDumpAgent {
59 fn module_load(&self, module: &CompiledModule, dbg_image: Option<&[u8]>) {
61 let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
62 let jitdump_file = jitdump_file.as_mut().unwrap();
63
64 let pid = process::id();
65 let tid = pid; for (idx, func) in module.finished_functions() {
68 let addr = func.as_ptr();
69 let len = func.len();
70 if let Some(img) = &dbg_image {
71 if let Err(err) =
72 self.dump_from_debug_image(jitdump_file, img, "wasm", addr, len, pid, tid)
73 {
74 println!(
75 "Jitdump: module_load failed dumping from debug image: {:?}\n",
76 err
77 );
78 }
79 } else {
80 let timestamp = jitdump_file.get_time_stamp();
81 let name = super::debug_name(module, idx);
82 if let Err(err) =
83 jitdump_file.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
84 {
85 println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
86 }
87 }
88 }
89
90 for (idx, func, len) in module.trampolines() {
92 let (addr, len) = (func as usize as *const u8, len);
93 let timestamp = jitdump_file.get_time_stamp();
94 let name = format!("wasm::trampoline[{}]", idx.index());
95 if let Err(err) =
96 jitdump_file.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
97 {
98 println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
99 }
100 }
101 }
102
103 fn load_single_trampoline(&self, name: &str, addr: *const u8, size: usize, pid: u32, tid: u32) {
104 let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
105 let jitdump_file = jitdump_file.as_mut().unwrap();
106
107 let timestamp = jitdump_file.get_time_stamp();
108 if let Err(err) = jitdump_file.dump_code_load_record(&name, addr, size, timestamp, pid, tid)
109 {
110 println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
111 }
112 }
113}
114
115impl JitDumpAgent {
116 pub fn dump_from_debug_image(
119 &self,
120 jitdump_file: &mut JitDumpFile,
121 dbg_image: &[u8],
122 module_name: &str,
123 addr: *const u8,
124 len: usize,
125 pid: u32,
126 tid: u32,
127 ) -> Result<()> {
128 let file = object::File::parse(dbg_image).unwrap();
129 let endian = if file.is_little_endian() {
130 gimli::RunTimeEndian::Little
131 } else {
132 gimli::RunTimeEndian::Big
133 };
134
135 let load_section = |id: gimli::SectionId| -> Result<borrow::Cow<[u8]>> {
136 if let Some(section) = file.section_by_name(id.name()) {
137 Ok(section.data()?.into())
138 } else {
139 Ok((&[] as &[u8]).into())
140 }
141 };
142
143 let dwarf_cow = gimli::Dwarf::load(&load_section)?;
144 let borrow_section: &dyn for<'a> Fn(
145 &'a borrow::Cow<[u8]>,
146 )
147 -> gimli::EndianSlice<'a, gimli::RunTimeEndian> =
148 &|section| gimli::EndianSlice::new(&*section, endian);
149
150 let dwarf = dwarf_cow.borrow(&borrow_section);
151
152 let mut iter = dwarf.units();
153 while let Some(header) = iter.next()? {
154 let unit = match dwarf.unit(header) {
155 Ok(unit) => unit,
156 Err(_err) => {
157 return Ok(());
158 }
159 };
160 self.dump_entries(jitdump_file, unit, &dwarf, module_name, addr, len, pid, tid)?;
161 break;
164 }
165 if !self.dump_funcs {
166 let timestamp = jitdump_file.get_time_stamp();
167 if let Err(err) =
168 jitdump_file.dump_code_load_record(module_name, addr, len, timestamp, pid, tid)
169 {
170 println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
171 }
172 }
173 Ok(())
174 }
175
176 fn dump_entries<R: Reader>(
177 &self,
178 jitdump_file: &mut JitDumpFile,
179 unit: gimli::Unit<R>,
180 dwarf: &gimli::Dwarf<R>,
181 module_name: &str,
182 addr: *const u8,
183 len: usize,
184 pid: u32,
185 tid: u32,
186 ) -> Result<()> {
187 let mut depth = 0;
188 let mut entries = unit.entries();
189 while let Some((delta_depth, entry)) = entries.next_dfs()? {
190 if self.dump_funcs {
191 let record_header = RecordHeader {
192 id: RecordId::JitCodeLoad as u32,
193 record_size: 0,
194 timestamp: 0,
195 };
196
197 let mut clr = CodeLoadRecord {
198 header: record_header,
199 pid,
200 tid,
201 virtual_address: 0,
202 address: 0,
203 size: 0,
204 index: 0,
205 };
206 let mut clr_name: String = String::from(module_name);
207 let mut get_debug_entry = false;
208 depth += delta_depth;
209 assert!(depth >= 0);
210
211 if entry.tag() == gimli::constants::DW_TAG_subprogram {
212 get_debug_entry = true;
213
214 let mut attrs = entry.attrs();
215 while let Some(attr) = attrs.next()? {
216 if let Some(n) = attr.name().static_string() {
217 if n == "DW_AT_low_pc" {
218 clr.address = match attr.value() {
219 gimli::AttributeValue::Addr(address) => address,
220 _ => 0,
221 };
222 clr.virtual_address = clr.address;
223 } else if n == "DW_AT_high_pc" {
224 clr.size = match attr.value() {
225 gimli::AttributeValue::Udata(data) => data,
226 _ => 0,
227 };
228 } else if n == "DW_AT_name" {
229 clr_name = match attr.value() {
230 gimli::AttributeValue::DebugStrRef(offset) => {
231 if let Ok(s) = dwarf.debug_str.get_str(offset) {
232 clr_name.push_str("::");
233 clr_name.push_str(&s.to_string_lossy()?);
234 clr_name
235 } else {
236 clr_name.push_str("::");
237 clr_name.push_str("?");
238 clr_name
239 }
240 }
241 _ => {
242 clr_name.push_str("??");
243 clr_name
244 }
245 };
246 }
247 }
248 }
249 }
250 if get_debug_entry {
251 if clr.address == 0 {
253 continue;
254 }
255 if clr_name == "?" {
257 continue;
258 }
259 if clr.address == 0 || clr.size == 0 {
260 clr.address = addr as u64;
261 clr.virtual_address = addr as u64;
262 clr.size = len as u64;
263 }
264 clr.header.record_size = mem::size_of::<CodeLoadRecord>() as u32
265 + (clr_name.len() + 1) as u32
266 + clr.size as u32;
267 clr.index = jitdump_file.next_code_index();
268 self.dump_debug_info(jitdump_file, &unit, &dwarf, clr.address, clr.size, None)?;
269
270 clr.header.timestamp = jitdump_file.get_time_stamp();
271
272 unsafe {
273 let code_buffer: &[u8] =
274 std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize);
275 let _ = jitdump_file.write_code_load_record(&clr_name, clr, code_buffer);
276 }
277 }
278 } else {
279 let mut func_name: String = String::from("?");
280 let mut func_addr = 0;
281 let mut func_size = 0;
282
283 let mut get_debug_entry = false;
284 depth += delta_depth;
285 assert!(depth >= 0);
286 if entry.tag() == gimli::constants::DW_TAG_subprogram {
287 get_debug_entry = true;
288
289 let mut attrs = entry.attrs();
290 while let Some(attr) = attrs.next()? {
291 if let Some(n) = attr.name().static_string() {
292 if n == "DW_AT_low_pc" {
293 func_addr = match attr.value() {
294 gimli::AttributeValue::Addr(address) => address,
295 _ => 0,
296 };
297 } else if n == "DW_AT_high_pc" {
298 func_size = match attr.value() {
299 gimli::AttributeValue::Udata(data) => data,
300 _ => 0,
301 };
302 } else if n == "DW_AT_name" {
303 func_name = match attr.value() {
304 gimli::AttributeValue::DebugStrRef(offset) => {
305 if let Ok(s) = dwarf.debug_str.get_str(offset) {
306 func_name.clear();
307 func_name.push_str(&s.to_string_lossy()?);
308 func_name
309 } else {
310 func_name.push_str("?");
311 func_name
312 }
313 }
314 _ => {
315 func_name.push_str("??");
316 func_name
317 }
318 };
319 }
320 }
321 }
322 }
323 if get_debug_entry {
324 if func_addr == 0 {
326 continue;
327 }
328 if func_name == "?" {
330 continue;
331 }
332 self.dump_debug_info(
333 jitdump_file,
334 &unit,
335 &dwarf,
336 func_addr,
337 func_size,
338 Some(func_name.as_str()),
339 )?;
340 }
341 }
342 }
343 Ok(())
344 }
345
346 fn dump_debug_info<R: Reader>(
347 &self,
348 jitdump_file: &mut JitDumpFile,
349 unit: &gimli::Unit<R>,
350 dwarf: &gimli::Dwarf<R>,
351 address: u64,
352 size: u64,
353 file_suffix: Option<&str>,
354 ) -> Result<()> {
355 let timestamp = jitdump_file.get_time_stamp();
356 if let Some(program) = unit.line_program.clone() {
357 let mut debug_info_record = DebugInfoRecord {
358 header: RecordHeader {
359 id: RecordId::JitCodeDebugInfo as u32,
360 record_size: 0,
361 timestamp,
362 },
363 address,
364 count: 0,
365 };
366
367 let mut debug_entries = Vec::new();
368 let mut debug_entries_total_filenames_len = 0;
369 let mut rows = program.rows();
370 while let Some((header, row)) = rows.next_row()? {
371 let row_file_index = row.file_index() - 1;
372 let myfile = dwarf
373 .attr_string(
374 &unit,
375 header.file_names()[row_file_index as usize].path_name(),
376 )
377 .unwrap();
378 let filename = myfile.to_string_lossy()?;
379 let line = row.line().map(|nonzero| nonzero.get()).unwrap_or(0);
380 let column = match row.column() {
381 gimli::ColumnType::Column(column) => column.get(),
382 gimli::ColumnType::LeftEdge => 0,
383 };
384
385 if (row.address() < address) || (row.address() > (address + size)) {
386 continue;
387 }
388 let mut debug_entry = DebugEntry {
389 address: row.address(),
390 line: line as u32,
391 discriminator: column as u32,
392 filename: filename.to_string(),
393 };
394
395 if let Some(suffix) = file_suffix {
396 debug_entry.filename.push_str("::");
397 debug_entry.filename.push_str(suffix);
398 }
399
400 debug_entries_total_filenames_len += debug_entry.filename.len() + 1;
401 debug_entries.push(debug_entry);
402 }
403
404 debug_info_record.count = debug_entries.len() as u64;
405
406 let debug_entries_size = (debug_info_record.count
407 * (mem::size_of::<DebugEntry>() as u64 - mem::size_of::<String>() as u64))
408 + debug_entries_total_filenames_len as u64;
409 debug_info_record.header.record_size =
410 mem::size_of::<DebugInfoRecord>() as u32 + debug_entries_size as u32;
411
412 let _ = jitdump_file.write_debug_info_record(debug_info_record);
413 let _ = jitdump_file.write_debug_info_entries(debug_entries);
414 }
415 Ok(())
416 }
417}
418
419trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
420
421impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
422 Endian: gimli::Endianity + Send + Sync
423{
424}