Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/debug/transform/simulate.rs
1693 views
1
use super::AddressTransform;
2
use super::expression::{CompiledExpression, FunctionFrameInfo};
3
use super::utils::append_vmctx_info;
4
use crate::debug::Compilation;
5
use crate::translate::get_vmctx_value_label;
6
use anyhow::{Context, Error};
7
use cranelift_codegen::isa::TargetIsa;
8
use gimli::LineEncoding;
9
use gimli::write;
10
use std::collections::{HashMap, HashSet};
11
use std::path::PathBuf;
12
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
13
use wasmtime_environ::{
14
DebugInfoData, EntityRef, FunctionMetadata, PrimaryMap, StaticModuleIndex, WasmFileInfo,
15
WasmValType,
16
};
17
18
const PRODUCER_NAME: &str = "wasmtime";
19
20
macro_rules! assert_dwarf_str {
21
($s:expr) => {{
22
let s = $s;
23
if cfg!(debug_assertions) {
24
// Perform check the same way as gimli does it.
25
let bytes: Vec<u8> = s.clone().into();
26
debug_assert!(!bytes.contains(&0), "DWARF string shall not have NULL byte");
27
}
28
s
29
}};
30
}
31
32
fn generate_line_info(
33
addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
34
translated: &HashSet<usize>,
35
out_encoding: gimli::Encoding,
36
w: &WasmFileInfo,
37
comp_dir_id: write::StringId,
38
name_id: write::StringId,
39
name: &str,
40
) -> Result<(write::LineProgram, write::FileId), Error> {
41
let out_comp_dir = write::LineString::StringRef(comp_dir_id);
42
let out_comp_name = write::LineString::StringRef(name_id);
43
44
let line_encoding = LineEncoding::default();
45
46
let mut out_program = write::LineProgram::new(
47
out_encoding,
48
line_encoding,
49
out_comp_dir,
50
None,
51
out_comp_name,
52
None,
53
);
54
55
let file_index = out_program.add_file(
56
write::LineString::String(name.as_bytes().to_vec()),
57
out_program.default_directory(),
58
None,
59
);
60
61
let maps = addr_tr.iter().flat_map(|(_, transform)| {
62
transform.map().iter().filter_map(|(_, map)| {
63
if translated.contains(&map.symbol) {
64
None
65
} else {
66
Some((map.symbol, map))
67
}
68
})
69
});
70
71
for (symbol, map) in maps {
72
let base_addr = map.offset;
73
out_program.begin_sequence(Some(write::Address::Symbol {
74
symbol,
75
addend: base_addr as i64,
76
}));
77
78
// Always emit a row for offset zero - debuggers expect this.
79
out_program.row().address_offset = 0;
80
out_program.row().file = file_index;
81
out_program.row().line = 0; // Special line number for non-user code.
82
out_program.row().discriminator = 1;
83
out_program.row().is_statement = true;
84
out_program.generate_row();
85
86
let mut is_prologue_end = true;
87
for addr_map in map.addresses.iter() {
88
let address_offset = (addr_map.generated - base_addr) as u64;
89
out_program.row().address_offset = address_offset;
90
let wasm_offset = w.code_section_offset + addr_map.wasm;
91
out_program.row().line = wasm_offset;
92
out_program.row().discriminator = 1;
93
out_program.row().prologue_end = is_prologue_end;
94
out_program.generate_row();
95
96
is_prologue_end = false;
97
}
98
let end_addr = (base_addr + map.len - 1) as u64;
99
out_program.end_sequence(end_addr);
100
}
101
102
Ok((out_program, file_index))
103
}
104
105
fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
106
if s.contains('\x00') { None } else { Some(s) }
107
}
108
109
fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
110
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
111
let module_name = di
112
.name_section
113
.module_name
114
.and_then(check_invalid_chars_in_name)
115
.map(|s| s.to_string())
116
.unwrap_or_else(|| format!("<gen-{}>.wasm", NEXT_ID.fetch_add(1, SeqCst)));
117
let path = format!("/<wasm-module>/{module_name}");
118
PathBuf::from(path)
119
}
120
121
struct WasmTypesDieRefs {
122
i32: write::UnitEntryId,
123
i64: write::UnitEntryId,
124
f32: write::UnitEntryId,
125
f64: write::UnitEntryId,
126
}
127
128
fn add_wasm_types(
129
unit: &mut write::Unit,
130
root_id: write::UnitEntryId,
131
out_strings: &mut write::StringTable,
132
) -> WasmTypesDieRefs {
133
macro_rules! def_type {
134
($id:literal, $size:literal, $enc:path) => {{
135
let die_id = unit.add(root_id, gimli::DW_TAG_base_type);
136
let die = unit.get_mut(die_id);
137
die.set(
138
gimli::DW_AT_name,
139
write::AttributeValue::StringRef(out_strings.add($id)),
140
);
141
die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));
142
die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));
143
die_id
144
}};
145
}
146
147
let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);
148
let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);
149
let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
150
let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
151
152
WasmTypesDieRefs {
153
i32: i32_die_id,
154
i64: i64_die_id,
155
f32: f32_die_id,
156
f64: f64_die_id,
157
}
158
}
159
160
fn resolve_var_type(
161
index: usize,
162
wasm_types: &WasmTypesDieRefs,
163
func_meta: &FunctionMetadata,
164
) -> Option<(write::UnitEntryId, bool)> {
165
let (ty, is_param) = if index < func_meta.params.len() {
166
(func_meta.params[index], true)
167
} else {
168
let mut i = (index - func_meta.params.len()) as u32;
169
let mut j = 0;
170
while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
171
i -= func_meta.locals[j].0;
172
j += 1;
173
}
174
if j >= func_meta.locals.len() {
175
// Ignore the var index out of bound.
176
return None;
177
}
178
(func_meta.locals[j].1, false)
179
};
180
let type_die_id = match ty {
181
WasmValType::I32 => wasm_types.i32,
182
WasmValType::I64 => wasm_types.i64,
183
WasmValType::F32 => wasm_types.f32,
184
WasmValType::F64 => wasm_types.f64,
185
_ => {
186
// Ignore unsupported types.
187
return None;
188
}
189
};
190
Some((type_die_id, is_param))
191
}
192
193
fn generate_vars(
194
unit: &mut write::Unit,
195
die_id: write::UnitEntryId,
196
addr_tr: &AddressTransform,
197
frame_info: &FunctionFrameInfo,
198
scope_ranges: &[(u64, u64)],
199
vmctx_ptr_die_ref: write::Reference,
200
wasm_types: &WasmTypesDieRefs,
201
func_meta: &FunctionMetadata,
202
locals_names: Option<&HashMap<u32, &str>>,
203
out_strings: &mut write::StringTable,
204
isa: &dyn TargetIsa,
205
) -> Result<(), Error> {
206
let vmctx_label = get_vmctx_value_label();
207
208
// Normalize order of ValueLabelsRanges keys to have reproducible results.
209
let mut vars = frame_info.value_ranges.keys().collect::<Vec<_>>();
210
vars.sort_by(|a, b| a.index().cmp(&b.index()));
211
212
for label in vars {
213
if label.index() == vmctx_label.index() {
214
append_vmctx_info(
215
unit,
216
die_id,
217
vmctx_ptr_die_ref,
218
addr_tr,
219
Some(frame_info),
220
scope_ranges,
221
out_strings,
222
isa,
223
)?;
224
} else {
225
let var_index = label.index();
226
let (type_die_id, is_param) =
227
if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {
228
result
229
} else {
230
// Skipping if type of local cannot be detected.
231
continue;
232
};
233
234
let loc_list_id = {
235
let locs = CompiledExpression::from_label(*label)
236
.build_with_locals(scope_ranges, addr_tr, Some(frame_info), isa)
237
.expressions
238
.map(|i| {
239
i.map(|(begin, length, data)| write::Location::StartLength {
240
begin,
241
length,
242
data,
243
})
244
})
245
.collect::<Result<Vec<_>, _>>()?;
246
unit.locations.add(write::LocationList(locs))
247
};
248
249
let var_id = unit.add(
250
die_id,
251
if is_param {
252
gimli::DW_TAG_formal_parameter
253
} else {
254
gimli::DW_TAG_variable
255
},
256
);
257
let var = unit.get_mut(var_id);
258
259
let name_id = match locals_names
260
.and_then(|m| m.get(&(var_index as u32)))
261
.and_then(|s| check_invalid_chars_in_name(s))
262
{
263
Some(n) => out_strings.add(assert_dwarf_str!(n)),
264
None => out_strings.add(format!("var{var_index}")),
265
};
266
267
var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
268
var.set(
269
gimli::DW_AT_type,
270
write::AttributeValue::UnitRef(type_die_id),
271
);
272
var.set(
273
gimli::DW_AT_location,
274
write::AttributeValue::LocationListRef(loc_list_id),
275
);
276
}
277
}
278
Ok(())
279
}
280
281
fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {
282
path.clone()
283
.to_str()
284
.and_then(move |s| if s.contains('\x00') { None } else { Some(path) })
285
}
286
287
/// Generate "simulated" native DWARF for functions lacking WASM-level DWARF.
288
pub fn generate_simulated_dwarf(
289
compilation: &mut Compilation<'_>,
290
addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
291
translated: &HashSet<usize>,
292
out_encoding: gimli::Encoding,
293
vmctx_ptr_die_refs: &PrimaryMap<StaticModuleIndex, write::Reference>,
294
out_units: &mut write::UnitTable,
295
out_strings: &mut write::StringTable,
296
isa: &dyn TargetIsa,
297
) -> Result<(), Error> {
298
let (wasm_file, path) = {
299
let di = &compilation.translations.iter().next().unwrap().1.debuginfo;
300
let path = di
301
.wasm_file
302
.path
303
.to_owned()
304
.and_then(check_invalid_chars_in_path)
305
.unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
306
(&di.wasm_file, path)
307
};
308
309
let (unit, root_id, file_id) = {
310
let comp_dir_id = out_strings.add(assert_dwarf_str!(
311
path.parent()
312
.context("path dir")?
313
.to_str()
314
.context("path dir encoding")?
315
));
316
let name = path
317
.file_name()
318
.context("path name")?
319
.to_str()
320
.context("path name encoding")?;
321
let name_id = out_strings.add(assert_dwarf_str!(name));
322
323
let (out_program, file_id) = generate_line_info(
324
addr_tr,
325
translated,
326
out_encoding,
327
wasm_file,
328
comp_dir_id,
329
name_id,
330
name,
331
)?;
332
333
let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));
334
let unit = out_units.get_mut(unit_id);
335
336
let root_id = unit.root();
337
let root = unit.get_mut(root_id);
338
339
let id = out_strings.add(PRODUCER_NAME);
340
root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));
341
root.set(
342
gimli::DW_AT_language,
343
write::AttributeValue::Language(gimli::DW_LANG_C11),
344
);
345
root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
346
root.set(
347
gimli::DW_AT_stmt_list,
348
write::AttributeValue::LineProgramRef,
349
);
350
root.set(
351
gimli::DW_AT_comp_dir,
352
write::AttributeValue::StringRef(comp_dir_id),
353
);
354
(unit, root_id, file_id)
355
};
356
357
let wasm_types = add_wasm_types(unit, root_id, out_strings);
358
let mut unit_ranges = vec![];
359
for (module, index) in compilation.indexes().collect::<Vec<_>>() {
360
let (symbol, _) = compilation.function(module, index);
361
if translated.contains(&symbol) {
362
continue;
363
}
364
365
let addr_tr = &addr_tr[module];
366
let map = &addr_tr.map()[index];
367
let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
368
let die = unit.get_mut(die_id);
369
let low_pc = write::Address::Symbol {
370
symbol,
371
addend: map.offset as i64,
372
};
373
let code_length = map.len as u64;
374
die.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(low_pc));
375
die.set(
376
gimli::DW_AT_high_pc,
377
write::AttributeValue::Udata(code_length),
378
);
379
unit_ranges.push(write::Range::StartLength {
380
begin: low_pc,
381
length: code_length,
382
});
383
384
let translation = &compilation.translations[module];
385
let func_index = translation.module.func_index(index);
386
let di = &translation.debuginfo;
387
let id = match di
388
.name_section
389
.func_names
390
.get(&func_index)
391
.and_then(|s| check_invalid_chars_in_name(s))
392
{
393
Some(n) => out_strings.add(assert_dwarf_str!(n)),
394
None => out_strings.add(format!("wasm-function[{}]", func_index.as_u32())),
395
};
396
397
die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
398
399
die.set(
400
gimli::DW_AT_decl_file,
401
write::AttributeValue::FileIndex(Some(file_id)),
402
);
403
404
let f_start = map.addresses[0].wasm;
405
let wasm_offset = di.wasm_file.code_section_offset + f_start;
406
die.set(
407
gimli::DW_AT_decl_line,
408
write::AttributeValue::Udata(wasm_offset),
409
);
410
411
let frame_info = compilation.function_frame_info(module, index);
412
let source_range = addr_tr.func_source_range(index);
413
generate_vars(
414
unit,
415
die_id,
416
addr_tr,
417
&frame_info,
418
&[(source_range.0, source_range.1)],
419
vmctx_ptr_die_refs[module],
420
&wasm_types,
421
&di.wasm_file.funcs[index.as_u32() as usize],
422
di.name_section.locals_names.get(&func_index),
423
out_strings,
424
isa,
425
)?;
426
}
427
let unit_ranges_id = unit.ranges.add(write::RangeList(unit_ranges));
428
unit.get_mut(root_id).set(
429
gimli::DW_AT_ranges,
430
write::AttributeValue::RangeListRef(unit_ranges_id),
431
);
432
433
Ok(())
434
}
435
436