1use std::convert::TryInto;
12
13use serde::Serialize;
14
15use crate::op_code::{read_operands, Opcode, DEFINITIONS};
16use crate::snapshot::{
17 read_bytecode, Reader, SnapshotError, FLAG_HAS_DEBUG_INFO, TAG_FUNCTION, TAG_INTEGER,
18 TAG_STRING,
19};
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
22#[serde(rename_all = "lowercase")]
23pub enum SnapshotSection {
24 Header,
25 Main,
26 Constants,
27 Debug,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
33#[serde(rename_all = "camelCase")]
34pub struct SnapshotRegion {
35 pub offset: usize,
36 pub length: usize,
37 pub section: SnapshotSection,
38 pub label: String,
39 pub detail: String,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
43#[serde(rename_all = "camelCase")]
44pub struct SnapshotLayout {
45 pub byte_length: usize,
46 pub format_version: u8,
47 pub abi_fingerprint: String,
49 pub has_debug_info: bool,
50 pub regions: Vec<SnapshotRegion>,
51}
52
53pub fn describe_bytecode(buf: &[u8]) -> Result<SnapshotLayout, SnapshotError> {
59 read_bytecode(buf)?;
60 Walker {
61 buf,
62 reader: Reader::new(buf),
63 regions: Vec::new(),
64 }
65 .walk()
66}
67
68struct Walker<'a> {
69 buf: &'a [u8],
70 reader: Reader<'a>,
71 regions: Vec<SnapshotRegion>,
72}
73
74impl<'a> Walker<'a> {
75 fn walk(mut self) -> Result<SnapshotLayout, SnapshotError> {
76 use SnapshotSection::{Constants, Debug, Header, Main};
77
78 self.record(
79 Header,
80 "magic",
81 |r| r.read_exact(4),
82 |_| "file signature \"MBC\\0\"".to_string(),
83 )?;
84 let format_version = self.record(Header, "version", Reader::read_u8, |version| {
85 format!("container format version {}", version)
86 })?;
87 let fingerprint_bytes = self.record(
88 Header,
89 "abi fingerprint",
90 |r| r.read_exact(4),
91 |bytes| {
92 let value = u32::from_le_bytes(bytes[..4].try_into().expect("4-byte slice"));
93 format!(
94 "0x{:08x} — FNV-1a over the opcode and builtin tables (little-endian)",
95 value
96 )
97 },
98 )?;
99 let abi_fingerprint = format!(
100 "0x{:08x}",
101 u32::from_le_bytes(fingerprint_bytes[..4].try_into().expect("4-byte slice"))
102 );
103 let flags = self.record(Header, "flags", Reader::read_u8, |flags| {
104 if flags & FLAG_HAS_DEBUG_INFO != 0 {
105 format!("0b{:08b} — debug info present", flags)
106 } else {
107 format!("0b{:08b} — debug info stripped", flags)
108 }
109 })?;
110 let has_debug_info = flags & FLAG_HAS_DEBUG_INFO != 0;
111
112 let main_len = self.record(Main, "main length", Reader::read_usize, |len| {
113 format!("{} bytes of main instructions follow (ULEB128)", len)
114 })?;
115 self.record_instructions(Main, "main", main_len)?;
116
117 let constant_count =
118 self.record(Constants, "constant count", Reader::read_usize, |count| {
119 format!("{} constants (ULEB128)", count)
120 })?;
121 let mut streams: Vec<String> = Vec::with_capacity(constant_count);
123 for index in 0..constant_count {
124 let tag_label = format!("const[{}] tag", index);
125 let tag = self.record(Constants, &tag_label, Reader::read_u8, |tag| match *tag {
126 TAG_INTEGER => "TAG_INTEGER (1) — SLEB128 value".to_string(),
127 TAG_STRING => "TAG_STRING (2) — length-prefixed UTF-8".to_string(),
128 TAG_FUNCTION => "TAG_FUNCTION (3) — name, locals, params, body".to_string(),
129 other => format!("unknown tag {}", other),
130 })?;
131 streams.push(format!("const[{}]", index));
132 match tag {
133 TAG_INTEGER => {
134 self.record(
135 Constants,
136 format!("const[{}] value", index),
137 Reader::read_sleb128,
138 |value| format!("{} (SLEB128)", value),
139 )?;
140 }
141 TAG_STRING => {
142 self.record_str(Constants, &format!("const[{}] text", index))?;
143 }
144 TAG_FUNCTION => {
145 let name = self.record_str(Constants, &format!("const[{}] name", index))?;
146 self.record(
147 Constants,
148 format!("const[{}] locals", index),
149 Reader::read_usize,
150 |count| format!("{} local slots", count),
151 )?;
152 self.record(
153 Constants,
154 format!("const[{}] params", index),
155 Reader::read_usize,
156 |count| format!("{} parameters", count),
157 )?;
158 let body_len = self.record(
159 Constants,
160 format!("const[{}] body length", index),
161 Reader::read_usize,
162 |len| format!("{} bytes of function instructions follow (ULEB128)", len),
163 )?;
164 let stream = if name.is_empty() {
165 format!("const[{}] fn", index)
166 } else {
167 format!("fn {}", name)
168 };
169 self.record_instructions(Constants, &stream, body_len)?;
170 streams[index] = stream;
171 }
172 other => return Err(SnapshotError::BadTag(other)),
173 }
174 }
175
176 if has_debug_info {
177 self.record_debug_info("main")?;
178 let entry_count =
179 self.record(Debug, "debug fn count", Reader::read_usize, |count| {
180 format!("{} function debug entries", count)
181 })?;
182 for _ in 0..entry_count {
183 let start = self.reader.position();
184 let constant_index = self.reader.read_usize()?;
185 let stream = streams
186 .get(constant_index)
187 .cloned()
188 .unwrap_or_else(|| format!("const[{}]", constant_index));
189 self.push(
190 start,
191 Debug,
192 "debug fn index".to_string(),
193 format!("debug metadata for {}", stream),
194 );
195 self.record_debug_info(&stream)?;
196 }
197 }
198
199 if self.reader.position() != self.buf.len() {
202 return Err(SnapshotError::TrailingBytes);
203 }
204 Ok(SnapshotLayout {
205 byte_length: self.buf.len(),
206 format_version,
207 abi_fingerprint,
208 has_debug_info,
209 regions: self.regions,
210 })
211 }
212
213 fn record<T>(
216 &mut self,
217 section: SnapshotSection,
218 label: impl Into<String>,
219 read: impl FnOnce(&mut Reader<'a>) -> Result<T, SnapshotError>,
220 detail: impl FnOnce(&T) -> String,
221 ) -> Result<T, SnapshotError> {
222 let start = self.reader.position();
223 let value = read(&mut self.reader)?;
224 let detail = detail(&value);
225 self.push(start, section, label.into(), detail);
226 Ok(value)
227 }
228
229 fn push(&mut self, start: usize, section: SnapshotSection, label: String, detail: String) {
230 let end = self.reader.position();
231 if end == start {
232 return;
233 }
234 self.regions.push(SnapshotRegion {
235 offset: start,
236 length: end - start,
237 section,
238 label,
239 detail,
240 });
241 }
242
243 fn record_str(
245 &mut self,
246 section: SnapshotSection,
247 label: &str,
248 ) -> Result<String, SnapshotError> {
249 let len = self.record(section, format!("{} length", label), Reader::read_usize, |len| {
250 format!("{} bytes (ULEB128)", len)
251 })?;
252 let start = self.reader.position();
253 let bytes = self.reader.read_exact(len)?;
254 let text = String::from_utf8(bytes.to_vec()).map_err(|_| SnapshotError::BadUtf8)?;
255 self.push(start, section, label.to_string(), format!("{:?}", text));
256 Ok(text)
257 }
258
259 fn record_instructions(
263 &mut self,
264 section: SnapshotSection,
265 stream: &str,
266 len: usize,
267 ) -> Result<(), SnapshotError> {
268 let start = self.reader.position();
269 let bytes = self.reader.read_exact(len)?;
270 let mut pc = 0;
271 while pc < len {
272 let opcode = Opcode::from_repr(bytes[pc]).ok_or_else(|| {
273 SnapshotError::InvalidInstruction(format!(
274 "unknown opcode 0x{:02x} (stream {}, offset {})",
275 bytes[pc], stream, pc
276 ))
277 })?;
278 let definition = DEFINITIONS.get(&opcode).ok_or_else(|| {
279 SnapshotError::InvalidInstruction(format!(
280 "missing definition for {:?} (stream {}, offset {})",
281 opcode, stream, pc
282 ))
283 })?;
284 let operand_len: usize = definition
285 .operand_widths()
286 .iter()
287 .map(|w| *w as usize)
288 .sum();
289 if pc + 1 + operand_len > len {
290 return Err(SnapshotError::InvalidInstruction(format!(
291 "truncated operands for {} (stream {}, offset {})",
292 definition.name(),
293 stream,
294 pc
295 )));
296 }
297 let (operands, _) = read_operands(definition, &bytes[pc + 1..]);
298 let mut label = definition.name().to_string();
299 for operand in &operands {
300 label.push_str(&format!(" {}", operand));
301 }
302 let end = start + pc + 1 + operand_len;
303 self.regions.push(SnapshotRegion {
304 offset: start + pc,
305 length: end - (start + pc),
306 section,
307 label,
308 detail: format!("{} pc {:04}", stream, pc),
309 });
310 pc += 1 + operand_len;
311 }
312 Ok(())
313 }
314
315 fn record_debug_info(&mut self, stream: &str) -> Result<(), SnapshotError> {
318 let count = self.record(
319 SnapshotSection::Debug,
320 format!("{} span count", stream),
321 Reader::read_usize,
322 |count| format!("{} pc→span entries (ULEB128)", count),
323 )?;
324 for _ in 0..count {
325 let start = self.reader.position();
326 let pc = self.reader.read_usize()?;
327 let span_start = self.reader.read_usize()?;
328 let span_end = self.reader.read_usize()?;
329 self.push(
330 start,
331 SnapshotSection::Debug,
332 format!("{} pc {:04}", stream, pc),
333 format!("source {}..{}", span_start, span_end),
334 );
335 }
336 let binding_count = self.record(
337 SnapshotSection::Debug,
338 format!("{} local count", stream),
339 Reader::read_usize,
340 |count| format!("{} named local slots (ULEB128)", count),
341 )?;
342 for _ in 0..binding_count {
343 let start = self.reader.position();
344 let slot = self.reader.read_usize()?;
345 let name_len = self.reader.read_usize()?;
346 let bytes = self.reader.read_exact(name_len)?;
347 let name = String::from_utf8(bytes.to_vec()).map_err(|_| SnapshotError::BadUtf8)?;
348 self.push(
349 start,
350 SnapshotSection::Debug,
351 format!("{} local {}", stream, slot),
352 format!("slot {} is {:?}", slot, name),
353 );
354 }
355 let free_count = self.record(
356 SnapshotSection::Debug,
357 format!("{} free count", stream),
358 Reader::read_usize,
359 |count| format!("{} captured names (ULEB128)", count),
360 )?;
361 for index in 0..free_count {
362 self.record_str(SnapshotSection::Debug, &format!("{} free {}", stream, index))?;
363 }
364 Ok(())
365 }
366}