Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/explorer/src/lib.rs
3054 views
1
//! > **⚠️ Warning ⚠️**: this crate is an internal-only crate for the Wasmtime
2
//! > project and is not intended for general use. APIs are not strictly
3
//! > reviewed for safety and usage outside of Wasmtime may have bugs. If
4
//! > you're interested in using this feel free to file an issue on the
5
//! > Wasmtime repository to start a discussion about doing so, but otherwise
6
//! > be aware that your usage of this crate is not supported.
7
8
use capstone::arch::BuildsCapstone;
9
use serde_derive::Serialize;
10
use std::{
11
fs::File,
12
io::{Write, read_to_string},
13
path::Path,
14
str::FromStr,
15
};
16
use wasmtime::{Result, ToWasmtimeResult as _};
17
use wasmtime_environ::demangle_function_name;
18
19
pub fn generate(
20
config: &wasmtime::Config,
21
target: Option<&str>,
22
clif_dir: Option<&Path>,
23
wasm: &[u8],
24
dest: &mut dyn Write,
25
) -> Result<()> {
26
let target = match target {
27
None => target_lexicon::Triple::host(),
28
Some(target) => target_lexicon::Triple::from_str(target)?,
29
};
30
31
let wat = annotate_wat(wasm)?;
32
let wat_json = serde_json::to_string(&wat)?;
33
let asm = annotate_asm(config, &target, wasm)?;
34
let asm_json = serde_json::to_string(&asm)?;
35
let clif_json = clif_dir
36
.map::<wasmtime::Result<String>, _>(|clif_dir| {
37
let clif = annotate_clif(clif_dir, &asm)?;
38
Ok(serde_json::to_string(&clif)?)
39
})
40
.transpose()?;
41
42
let index_css = include_str!("./index.css");
43
let index_js = include_str!("./index.js");
44
45
write!(
46
dest,
47
r#"
48
<!DOCTYPE html>
49
<html>
50
<head>
51
<title>Wasmtime Compiler Explorer</title>
52
<style>
53
{index_css}
54
</style>
55
</head>
56
<body class="hbox">
57
<pre id="wat"></pre>
58
"#
59
)?;
60
if clif_json.is_some() {
61
write!(dest, r#"<div id="clif"></div>"#)?;
62
}
63
write!(
64
dest,
65
r#"
66
<div id="asm"></div>
67
<script>
68
window.WAT = {wat_json};
69
"#
70
)?;
71
if let Some(clif_json) = clif_json {
72
write!(
73
dest,
74
r#"
75
window.CLIF = {clif_json};
76
"#
77
)?;
78
}
79
write!(
80
dest,
81
r#"
82
window.ASM = {asm_json};
83
</script>
84
<script>
85
{index_js}
86
</script>
87
</body>
88
</html>
89
"#
90
)?;
91
Ok(())
92
}
93
94
#[derive(Serialize, Clone, Copy, Debug)]
95
struct WasmOffset(u32);
96
97
#[derive(Serialize, Debug)]
98
struct AnnotatedWat {
99
chunks: Vec<AnnotatedWatChunk>,
100
}
101
102
#[derive(Serialize, Debug)]
103
struct AnnotatedWatChunk {
104
wasm_offset: Option<WasmOffset>,
105
wat: String,
106
}
107
108
fn annotate_wat(wasm: &[u8]) -> Result<AnnotatedWat> {
109
let printer = wasmprinter::Config::new();
110
let mut storage = String::new();
111
let chunks = printer
112
.offsets_and_lines(wasm, &mut storage)
113
.to_wasmtime_result()?
114
.map(|(offset, wat)| AnnotatedWatChunk {
115
wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())),
116
wat: wat.to_string(),
117
})
118
.collect();
119
Ok(AnnotatedWat { chunks })
120
}
121
122
#[derive(Serialize, Debug)]
123
struct AnnotatedAsm {
124
functions: Vec<AnnotatedFunction>,
125
}
126
127
#[derive(Serialize, Debug)]
128
struct AnnotatedFunction {
129
func_index: u32,
130
name: Option<String>,
131
demangled_name: Option<String>,
132
instructions: Vec<AnnotatedInstruction>,
133
}
134
135
#[derive(Serialize, Debug)]
136
struct AnnotatedInstruction {
137
wasm_offset: Option<WasmOffset>,
138
address: u32,
139
bytes: Vec<u8>,
140
mnemonic: Option<String>,
141
operands: Option<String>,
142
}
143
144
fn annotate_asm(
145
config: &wasmtime::Config,
146
target: &target_lexicon::Triple,
147
wasm: &[u8],
148
) -> Result<AnnotatedAsm> {
149
let engine = wasmtime::Engine::new(config)?;
150
let module = wasmtime::Module::new(&engine, wasm)?;
151
152
let text = module.text();
153
let address_map: Vec<_> = module
154
.address_map()
155
.ok_or_else(|| wasmtime::format_err!("address maps must be enabled in the config"))?
156
.collect();
157
158
let mut address_map_iter = address_map.into_iter().peekable();
159
let mut current_entry = address_map_iter.next();
160
let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> {
161
// Consume any entries that happened before the current function for the
162
// first instruction.
163
while current_entry.map_or(false, |cur| cur.0 < start) {
164
current_entry = address_map_iter.next();
165
}
166
167
// Next advance the address map up to the current `address` specified,
168
// including it.
169
while address_map_iter.peek().map_or(false, |next_entry| {
170
u32::try_from(next_entry.0).unwrap() <= address
171
}) {
172
current_entry = address_map_iter.next();
173
}
174
current_entry.and_then(|entry| entry.1.map(WasmOffset))
175
};
176
177
let functions = module
178
.functions()
179
.map(|function| {
180
let body = &text[function.offset..][..function.len];
181
182
let mut cs = match target.architecture {
183
target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new()
184
.arm64()
185
.mode(capstone::arch::arm64::ArchMode::Arm)
186
.build()
187
.map_err(|e| wasmtime::format_err!("{e}"))?,
188
target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new()
189
.riscv()
190
.mode(capstone::arch::riscv::ArchMode::RiscV64)
191
.build()
192
.map_err(|e| wasmtime::format_err!("{e}"))?,
193
target_lexicon::Architecture::S390x => capstone::Capstone::new()
194
.sysz()
195
.mode(capstone::arch::sysz::ArchMode::Default)
196
.build()
197
.map_err(|e| wasmtime::format_err!("{e}"))?,
198
target_lexicon::Architecture::X86_64 => capstone::Capstone::new()
199
.x86()
200
.mode(capstone::arch::x86::ArchMode::Mode64)
201
.build()
202
.map_err(|e| wasmtime::format_err!("{e}"))?,
203
_ => wasmtime::bail!("Unsupported target: {target}"),
204
};
205
206
// This tells capstone to skip over anything that looks like data,
207
// such as inline constant pools and things like that. This also
208
// additionally is required to skip over trapping instructions on
209
// AArch64.
210
cs.set_skipdata(true).unwrap();
211
212
let instructions = cs
213
.disasm_all(body, function.offset as u64)
214
.map_err(|e| wasmtime::format_err!("{e}"))?;
215
let instructions = instructions
216
.iter()
217
.map(|inst| {
218
let address = u32::try_from(inst.address()).unwrap();
219
let wasm_offset = wasm_offset_for_address(function.offset, address);
220
Ok(AnnotatedInstruction {
221
wasm_offset,
222
address,
223
bytes: inst.bytes().to_vec(),
224
mnemonic: inst.mnemonic().map(ToString::to_string),
225
operands: inst.op_str().map(ToString::to_string),
226
})
227
})
228
.collect::<Result<Vec<_>>>()?;
229
230
let demangled_name = if let Some(name) = &function.name {
231
let mut demangled = String::new();
232
if demangle_function_name(&mut demangled, &name).is_ok() {
233
Some(demangled)
234
} else {
235
None
236
}
237
} else {
238
None
239
};
240
241
Ok(AnnotatedFunction {
242
func_index: function.index.as_u32(),
243
name: function.name,
244
demangled_name,
245
instructions,
246
})
247
})
248
.collect::<Result<Vec<_>>>()?;
249
250
Ok(AnnotatedAsm { functions })
251
}
252
253
#[derive(Serialize, Debug)]
254
struct AnnotatedClif {
255
functions: Vec<AnnotatedClifFunction>,
256
}
257
258
#[derive(Serialize, Debug)]
259
struct AnnotatedClifFunction {
260
func_index: u32,
261
name: Option<String>,
262
demangled_name: Option<String>,
263
instructions: Vec<AnnotatedClifInstruction>,
264
}
265
266
#[derive(Serialize, Debug)]
267
struct AnnotatedClifInstruction {
268
wasm_offset: Option<WasmOffset>,
269
clif: String,
270
}
271
272
fn annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif> {
273
let mut clif = AnnotatedClif {
274
functions: Vec::new(),
275
};
276
for function in &asm.functions {
277
let function_path = clif_dir.join(format!("wasm_func_{}.clif", function.func_index));
278
if !function_path.exists() {
279
continue;
280
}
281
let mut clif_function = AnnotatedClifFunction {
282
func_index: function.func_index,
283
name: function.name.clone(),
284
demangled_name: function.demangled_name.clone(),
285
instructions: Vec::new(),
286
};
287
let file = File::open(&function_path)?;
288
for mut line in read_to_string(file)?.lines() {
289
if line.is_empty() {
290
continue;
291
}
292
let mut wasm_offset = None;
293
if line.starts_with('@') {
294
wasm_offset = Some(WasmOffset(u32::from_str_radix(&line[1..5], 16)?));
295
line = &line[28..];
296
} else if line.starts_with(" ") {
297
line = &line[28..];
298
}
299
clif_function.instructions.push(AnnotatedClifInstruction {
300
wasm_offset,
301
clif: line.to_string(),
302
});
303
}
304
clif.functions.push(clif_function);
305
}
306
Ok(clif)
307
}
308
309