1 | use std::borrow::Cow; |
2 | |
3 | use crate::{CustomSection, Encode, Section}; |
4 | |
5 | /// The "core" custom section for coredumps, as described in the |
6 | /// [tool-conventions |
7 | /// repository](https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md). |
8 | /// |
9 | /// There are four sections that comprise a core dump: |
10 | /// - "core", which contains the name of the core dump |
11 | /// - "coremodules", a listing of modules |
12 | /// - "coreinstances", a listing of module instances |
13 | /// - "corestack", a listing of frames for a specific thread |
14 | /// |
15 | /// # Example of how these could be constructed and encoded into a module: |
16 | /// |
17 | /// ``` |
18 | /// use wasm_encoder::{ |
19 | /// CoreDumpInstancesSection, CoreDumpModulesSection, CoreDumpSection, CoreDumpStackSection, |
20 | /// CoreDumpValue, Module, |
21 | /// }; |
22 | /// let core = CoreDumpSection::new("MyModule.wasm" ); |
23 | /// |
24 | /// let mut modules = CoreDumpModulesSection::new(); |
25 | /// modules.module("my_module" ); |
26 | /// |
27 | /// let mut instances = CoreDumpInstancesSection::new(); |
28 | /// let module_idx = 0; |
29 | /// let memories = vec![1]; |
30 | /// let globals = vec![2]; |
31 | /// instances.instance(module_idx, memories, globals); |
32 | /// |
33 | /// let mut thread = CoreDumpStackSection::new("main" ); |
34 | /// let instance_index = 0; |
35 | /// let func_index = 42; |
36 | /// let code_offset = 0x1234; |
37 | /// let locals = vec![CoreDumpValue::I32(1)]; |
38 | /// let stack = vec![CoreDumpValue::I32(2)]; |
39 | /// thread.frame(instance_index, func_index, code_offset, locals, stack); |
40 | /// |
41 | /// let mut module = Module::new(); |
42 | /// module.section(&core); |
43 | /// module.section(&modules); |
44 | /// module.section(&instances); |
45 | /// module.section(&thread); |
46 | /// ``` |
47 | #[derive (Clone, Debug, Default)] |
48 | pub struct CoreDumpSection { |
49 | name: String, |
50 | } |
51 | |
52 | impl CoreDumpSection { |
53 | /// Create a new core dump section encoder |
54 | pub fn new(name: impl Into<String>) -> Self { |
55 | let name: String = name.into(); |
56 | CoreDumpSection { name } |
57 | } |
58 | |
59 | /// View the encoded section as a CustomSection. |
60 | fn as_custom<'a>(&'a self) -> CustomSection<'a> { |
61 | let mut data: Vec = vec![0]; |
62 | self.name.encode(&mut data); |
63 | CustomSection { |
64 | name: "core" .into(), |
65 | data: Cow::Owned(data), |
66 | } |
67 | } |
68 | } |
69 | |
70 | impl Encode for CoreDumpSection { |
71 | fn encode(&self, sink: &mut Vec<u8>) { |
72 | self.as_custom().encode(sink); |
73 | } |
74 | } |
75 | |
76 | impl Section for CoreDumpSection { |
77 | fn id(&self) -> u8 { |
78 | crate::core::SectionId::Custom as u8 |
79 | } |
80 | } |
81 | |
82 | /// The "coremodules" custom section for coredumps which lists the names of the |
83 | /// modules |
84 | /// |
85 | /// # Example |
86 | /// |
87 | /// ``` |
88 | /// use wasm_encoder::{CoreDumpModulesSection, Module}; |
89 | /// let mut modules_section = CoreDumpModulesSection::new(); |
90 | /// modules_section.module("my_module" ); |
91 | /// let mut module = Module::new(); |
92 | /// module.section(&modules_section); |
93 | /// ``` |
94 | #[derive (Debug)] |
95 | pub struct CoreDumpModulesSection { |
96 | num_added: u32, |
97 | bytes: Vec<u8>, |
98 | } |
99 | |
100 | impl CoreDumpModulesSection { |
101 | /// Create a new core dump modules section encoder. |
102 | pub fn new() -> Self { |
103 | CoreDumpModulesSection { |
104 | bytes: vec![], |
105 | num_added: 0, |
106 | } |
107 | } |
108 | |
109 | /// View the encoded section as a CustomSection. |
110 | pub fn as_custom(&self) -> CustomSection<'_> { |
111 | let mut data = vec![]; |
112 | self.num_added.encode(&mut data); |
113 | data.extend(self.bytes.iter().copied()); |
114 | CustomSection { |
115 | name: "coremodules" .into(), |
116 | data: Cow::Owned(data), |
117 | } |
118 | } |
119 | |
120 | /// Encode a module name into the section's bytes. |
121 | pub fn module(&mut self, module_name: impl AsRef<str>) -> &mut Self { |
122 | self.bytes.push(0x0); |
123 | module_name.as_ref().encode(&mut self.bytes); |
124 | self.num_added += 1; |
125 | self |
126 | } |
127 | |
128 | /// The number of modules that are encoded in the section. |
129 | pub fn len(&self) -> u32 { |
130 | self.num_added |
131 | } |
132 | } |
133 | |
134 | impl Encode for CoreDumpModulesSection { |
135 | fn encode(&self, sink: &mut Vec<u8>) { |
136 | self.as_custom().encode(sink); |
137 | } |
138 | } |
139 | |
140 | impl Section for CoreDumpModulesSection { |
141 | fn id(&self) -> u8 { |
142 | crate::core::SectionId::Custom as u8 |
143 | } |
144 | } |
145 | |
146 | /// The "coreinstances" section for the core dump |
147 | #[derive (Debug)] |
148 | pub struct CoreDumpInstancesSection { |
149 | num_added: u32, |
150 | bytes: Vec<u8>, |
151 | } |
152 | |
153 | impl CoreDumpInstancesSection { |
154 | /// Create a new core dump instances section encoder. |
155 | pub fn new() -> Self { |
156 | CoreDumpInstancesSection { |
157 | bytes: vec![], |
158 | num_added: 0, |
159 | } |
160 | } |
161 | |
162 | /// View the encoded section as a CustomSection. |
163 | pub fn as_custom(&self) -> CustomSection<'_> { |
164 | let mut data = vec![]; |
165 | self.num_added.encode(&mut data); |
166 | data.extend(self.bytes.iter().copied()); |
167 | CustomSection { |
168 | name: "coreinstances" .into(), |
169 | data: Cow::Owned(data), |
170 | } |
171 | } |
172 | |
173 | /// Encode an instance into the section's bytes. |
174 | pub fn instance<M, G>(&mut self, module_index: u32, memories: M, globals: G) -> &mut Self |
175 | where |
176 | M: IntoIterator<Item = u32>, |
177 | <M as IntoIterator>::IntoIter: ExactSizeIterator, |
178 | G: IntoIterator<Item = u32>, |
179 | <G as IntoIterator>::IntoIter: ExactSizeIterator, |
180 | { |
181 | self.bytes.push(0x0); |
182 | module_index.encode(&mut self.bytes); |
183 | crate::encode_vec(memories, &mut self.bytes); |
184 | crate::encode_vec(globals, &mut self.bytes); |
185 | self.num_added += 1; |
186 | self |
187 | } |
188 | |
189 | /// The number of modules that are encoded in the section. |
190 | pub fn len(&self) -> u32 { |
191 | self.num_added |
192 | } |
193 | } |
194 | |
195 | impl Encode for CoreDumpInstancesSection { |
196 | fn encode(&self, sink: &mut Vec<u8>) { |
197 | self.as_custom().encode(sink); |
198 | } |
199 | } |
200 | |
201 | impl Section for CoreDumpInstancesSection { |
202 | fn id(&self) -> u8 { |
203 | crate::core::SectionId::Custom as u8 |
204 | } |
205 | } |
206 | |
207 | /// A "corestack" custom section as described in the [tool-conventions |
208 | /// repository](https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md) |
209 | /// |
210 | /// # Example |
211 | /// |
212 | /// ``` |
213 | /// use wasm_encoder::{CoreDumpStackSection, Module, CoreDumpValue}; |
214 | /// let mut thread = CoreDumpStackSection::new("main" ); |
215 | /// |
216 | /// let instance_index = 0; |
217 | /// let func_index = 42; |
218 | /// let code_offset = 0x1234; |
219 | /// let locals = vec![CoreDumpValue::I32(1)]; |
220 | /// let stack = vec![CoreDumpValue::I32(2)]; |
221 | /// thread.frame(instance_index, func_index, code_offset, locals, stack); |
222 | /// |
223 | /// let mut module = Module::new(); |
224 | /// module.section(&thread); |
225 | /// ``` |
226 | #[derive (Clone, Debug, Default)] |
227 | pub struct CoreDumpStackSection { |
228 | frame_bytes: Vec<u8>, |
229 | count: u32, |
230 | name: String, |
231 | } |
232 | |
233 | impl CoreDumpStackSection { |
234 | /// Create a new core dump stack section encoder. |
235 | pub fn new(name: impl Into<String>) -> Self { |
236 | let name = name.into(); |
237 | CoreDumpStackSection { |
238 | frame_bytes: Vec::new(), |
239 | count: 0, |
240 | name, |
241 | } |
242 | } |
243 | |
244 | /// Add a stack frame to this coredump stack section. |
245 | pub fn frame<L, S>( |
246 | &mut self, |
247 | instanceidx: u32, |
248 | funcidx: u32, |
249 | codeoffset: u32, |
250 | locals: L, |
251 | stack: S, |
252 | ) -> &mut Self |
253 | where |
254 | L: IntoIterator<Item = CoreDumpValue>, |
255 | <L as IntoIterator>::IntoIter: ExactSizeIterator, |
256 | S: IntoIterator<Item = CoreDumpValue>, |
257 | <S as IntoIterator>::IntoIter: ExactSizeIterator, |
258 | { |
259 | self.count += 1; |
260 | self.frame_bytes.push(0); |
261 | instanceidx.encode(&mut self.frame_bytes); |
262 | funcidx.encode(&mut self.frame_bytes); |
263 | codeoffset.encode(&mut self.frame_bytes); |
264 | crate::encode_vec(locals, &mut self.frame_bytes); |
265 | crate::encode_vec(stack, &mut self.frame_bytes); |
266 | self |
267 | } |
268 | |
269 | /// View the encoded section as a CustomSection. |
270 | pub fn as_custom<'a>(&'a self) -> CustomSection<'a> { |
271 | let mut data = vec![0]; |
272 | self.name.encode(&mut data); |
273 | self.count.encode(&mut data); |
274 | data.extend(&self.frame_bytes); |
275 | CustomSection { |
276 | name: "corestack" .into(), |
277 | data: Cow::Owned(data), |
278 | } |
279 | } |
280 | } |
281 | |
282 | impl Encode for CoreDumpStackSection { |
283 | fn encode(&self, sink: &mut Vec<u8>) { |
284 | self.as_custom().encode(sink); |
285 | } |
286 | } |
287 | |
288 | impl Section for CoreDumpStackSection { |
289 | fn id(&self) -> u8 { |
290 | crate::core::SectionId::Custom as u8 |
291 | } |
292 | } |
293 | |
294 | /// Local and stack values are encoded using one byte for the type (similar to |
295 | /// Wasm's Number Types) followed by bytes representing the actual value |
296 | /// See the tool-conventions repo for more details. |
297 | #[derive (Clone, Debug)] |
298 | pub enum CoreDumpValue { |
299 | /// a missing value (usually missing because it was optimized out) |
300 | Missing, |
301 | /// An i32 value |
302 | I32(i32), |
303 | /// An i64 value |
304 | I64(i64), |
305 | /// An f32 value |
306 | F32(f32), |
307 | /// An f64 value |
308 | F64(f64), |
309 | } |
310 | |
311 | impl Encode for CoreDumpValue { |
312 | fn encode(&self, sink: &mut Vec<u8>) { |
313 | match self { |
314 | CoreDumpValue::Missing => sink.push(0x01), |
315 | CoreDumpValue::I32(x: &i32) => { |
316 | sink.push(0x7F); |
317 | x.encode(sink); |
318 | } |
319 | CoreDumpValue::I64(x: &i64) => { |
320 | sink.push(0x7E); |
321 | x.encode(sink); |
322 | } |
323 | CoreDumpValue::F32(x: &f32) => { |
324 | sink.push(0x7D); |
325 | x.encode(sink); |
326 | } |
327 | CoreDumpValue::F64(x: &f64) => { |
328 | sink.push(0x7C); |
329 | x.encode(sink); |
330 | } |
331 | } |
332 | } |
333 | } |
334 | |
335 | #[cfg (test)] |
336 | mod tests { |
337 | use super::*; |
338 | use crate::Module; |
339 | use wasmparser::{KnownCustom, Parser, Payload}; |
340 | |
341 | // Create new core dump section and test whether it is properly encoded and |
342 | // parsed back out by wasmparser |
343 | #[test ] |
344 | fn test_roundtrip_core() { |
345 | let core = CoreDumpSection::new("test.wasm" ); |
346 | let mut module = Module::new(); |
347 | module.section(&core); |
348 | |
349 | let wasm_bytes = module.finish(); |
350 | |
351 | let mut parser = Parser::new(0).parse_all(&wasm_bytes); |
352 | match parser.next() { |
353 | Some(Ok(Payload::Version { .. })) => {} |
354 | _ => panic!("" ), |
355 | } |
356 | |
357 | let payload = parser |
358 | .next() |
359 | .expect("parser is not empty" ) |
360 | .expect("element is a payload" ); |
361 | match payload { |
362 | Payload::CustomSection(section) => { |
363 | assert_eq!(section.name(), "core" ); |
364 | let core = match section.as_known() { |
365 | KnownCustom::CoreDump(s) => s, |
366 | _ => panic!("not coredump" ), |
367 | }; |
368 | assert_eq!(core.name, "test.wasm" ); |
369 | } |
370 | _ => panic!("unexpected payload" ), |
371 | } |
372 | } |
373 | |
374 | #[test ] |
375 | fn test_roundtrip_coremodules() { |
376 | let mut coremodules = CoreDumpModulesSection::new(); |
377 | coremodules.module("test_module" ); |
378 | |
379 | let mut module = crate::Module::new(); |
380 | module.section(&coremodules); |
381 | |
382 | let wasm_bytes = module.finish(); |
383 | |
384 | let mut parser = Parser::new(0).parse_all(&wasm_bytes); |
385 | match parser.next() { |
386 | Some(Ok(Payload::Version { .. })) => {} |
387 | _ => panic!("" ), |
388 | } |
389 | |
390 | let payload = parser |
391 | .next() |
392 | .expect("parser is not empty" ) |
393 | .expect("element is a payload" ); |
394 | match payload { |
395 | Payload::CustomSection(section) => { |
396 | assert_eq!(section.name(), "coremodules" ); |
397 | let modules = match section.as_known() { |
398 | KnownCustom::CoreDumpModules(s) => s, |
399 | _ => panic!("not coremodules" ), |
400 | }; |
401 | assert_eq!(modules.modules[0], "test_module" ); |
402 | } |
403 | _ => panic!("unexpected payload" ), |
404 | } |
405 | } |
406 | |
407 | #[test ] |
408 | fn test_roundtrip_coreinstances() { |
409 | let mut coreinstances = CoreDumpInstancesSection::new(); |
410 | let module_index = 0; |
411 | let memories = vec![42]; |
412 | let globals = vec![17]; |
413 | coreinstances.instance(module_index, memories, globals); |
414 | |
415 | let mut module = Module::new(); |
416 | module.section(&coreinstances); |
417 | let wasm_bytes = module.finish(); |
418 | |
419 | let mut parser = Parser::new(0).parse_all(&wasm_bytes); |
420 | match parser.next() { |
421 | Some(Ok(Payload::Version { .. })) => {} |
422 | _ => panic!("" ), |
423 | } |
424 | |
425 | let payload = parser |
426 | .next() |
427 | .expect("parser is not empty" ) |
428 | .expect("element is a payload" ); |
429 | match payload { |
430 | Payload::CustomSection(section) => { |
431 | assert_eq!(section.name(), "coreinstances" ); |
432 | let coreinstances = match section.as_known() { |
433 | KnownCustom::CoreDumpInstances(s) => s, |
434 | _ => panic!("not coreinstances" ), |
435 | }; |
436 | assert_eq!(coreinstances.instances.len(), 1); |
437 | let instance = coreinstances |
438 | .instances |
439 | .first() |
440 | .expect("instance is encoded" ); |
441 | assert_eq!(instance.module_index, 0); |
442 | assert_eq!(instance.memories.len(), 1); |
443 | assert_eq!(instance.globals.len(), 1); |
444 | } |
445 | _ => panic!("unexpected payload" ), |
446 | } |
447 | } |
448 | |
449 | // Create new corestack section and test whether it is properly encoded and |
450 | // parsed back out by wasmparser |
451 | #[test ] |
452 | fn test_roundtrip_corestack() { |
453 | let mut corestack = CoreDumpStackSection::new("main" ); |
454 | corestack.frame( |
455 | 0, |
456 | 12, |
457 | 0, |
458 | vec![CoreDumpValue::I32(10)], |
459 | vec![CoreDumpValue::I32(42)], |
460 | ); |
461 | let mut module = Module::new(); |
462 | module.section(&corestack); |
463 | let wasm_bytes = module.finish(); |
464 | |
465 | let mut parser = Parser::new(0).parse_all(&wasm_bytes); |
466 | match parser.next() { |
467 | Some(Ok(Payload::Version { .. })) => {} |
468 | _ => panic!("" ), |
469 | } |
470 | |
471 | let payload = parser |
472 | .next() |
473 | .expect("parser is not empty" ) |
474 | .expect("element is a payload" ); |
475 | match payload { |
476 | Payload::CustomSection(section) => { |
477 | assert_eq!(section.name(), "corestack" ); |
478 | let corestack = match section.as_known() { |
479 | KnownCustom::CoreDumpStack(s) => s, |
480 | _ => panic!("not a corestack section" ), |
481 | }; |
482 | assert_eq!(corestack.name, "main" ); |
483 | assert_eq!(corestack.frames.len(), 1); |
484 | let frame = corestack |
485 | .frames |
486 | .first() |
487 | .expect("frame is encoded in corestack" ); |
488 | assert_eq!(frame.instanceidx, 0); |
489 | assert_eq!(frame.funcidx, 12); |
490 | assert_eq!(frame.codeoffset, 0); |
491 | assert_eq!(frame.locals.len(), 1); |
492 | match frame.locals.first().expect("frame contains a local" ) { |
493 | &wasmparser::CoreDumpValue::I32(val) => assert_eq!(val, 10), |
494 | _ => panic!("unexpected local value" ), |
495 | } |
496 | assert_eq!(frame.stack.len(), 1); |
497 | match frame.stack.first().expect("stack contains a value" ) { |
498 | &wasmparser::CoreDumpValue::I32(val) => assert_eq!(val, 42), |
499 | _ => panic!("unexpected stack value" ), |
500 | } |
501 | } |
502 | _ => panic!("unexpected payload" ), |
503 | } |
504 | } |
505 | |
506 | #[test ] |
507 | fn test_encode_coredump_section() { |
508 | let core = CoreDumpSection::new("test" ); |
509 | |
510 | let mut encoded = vec![]; |
511 | core.encode(&mut encoded); |
512 | |
513 | #[rustfmt::skip] |
514 | assert_eq!(encoded, vec![ |
515 | // section length |
516 | 11, |
517 | // name length |
518 | 4, |
519 | // section name (core) |
520 | b'c' ,b'o' ,b'r' ,b'e' , |
521 | // process-info (0, data length, data) |
522 | 0, 4, b't' , b'e' , b's' , b't' , |
523 | ]); |
524 | } |
525 | |
526 | #[test ] |
527 | fn test_encode_coremodules_section() { |
528 | let mut modules = CoreDumpModulesSection::new(); |
529 | modules.module("mod1" ); |
530 | modules.module("mod2" ); |
531 | |
532 | let mut encoded = vec![]; |
533 | modules.encode(&mut encoded); |
534 | |
535 | #[rustfmt::skip] |
536 | assert_eq!(encoded, vec![ |
537 | // section length |
538 | 25, |
539 | // name length |
540 | 11, |
541 | // section name (coremodules) |
542 | b'c' ,b'o' ,b'r' ,b'e' ,b'm' ,b'o' ,b'd' ,b'u' ,b'l' ,b'e' ,b's' , |
543 | // module count |
544 | 2, |
545 | // 0x0, name-length, module name (mod1) |
546 | 0x0, 4, b'm' ,b'o' ,b'd' ,b'1' , |
547 | // 0x0, name-length, module name (mod2) |
548 | 0x0, 4, b'm' ,b'o' ,b'd' ,b'2' |
549 | ]); |
550 | } |
551 | |
552 | #[test ] |
553 | fn test_encode_coreinstances_section() { |
554 | let mut instances = CoreDumpInstancesSection::new(); |
555 | instances.instance(0, vec![42], vec![17]); |
556 | |
557 | let mut encoded = vec![]; |
558 | instances.encode(&mut encoded); |
559 | |
560 | #[rustfmt::skip] |
561 | assert_eq!(encoded, vec![ |
562 | // section length |
563 | 21, |
564 | // name length |
565 | 13, |
566 | // section name (coreinstances) |
567 | b'c' ,b'o' ,b'r' ,b'e' ,b'i' ,b'n' ,b's' ,b't' ,b'a' ,b'n' ,b'c' ,b'e' ,b's' , |
568 | // instance count |
569 | 1, |
570 | // 0x0, module_idx |
571 | 0x0, 0, |
572 | // memories count, memories |
573 | 1, 42, |
574 | // globals count, globals |
575 | 1, 17 |
576 | ]); |
577 | } |
578 | |
579 | #[test ] |
580 | fn test_encode_corestack_section() { |
581 | let mut thread = CoreDumpStackSection::new("main" ); |
582 | thread.frame( |
583 | 0, |
584 | 42, |
585 | 51, |
586 | vec![CoreDumpValue::I32(1)], |
587 | vec![CoreDumpValue::I32(2)], |
588 | ); |
589 | |
590 | let mut encoded = vec![]; |
591 | thread.encode(&mut encoded); |
592 | |
593 | #[rustfmt::skip] |
594 | assert_eq!( |
595 | encoded, |
596 | vec![ |
597 | // section length |
598 | 27, |
599 | // length of name. |
600 | 9, |
601 | // section name (corestack) |
602 | b'c' ,b'o' ,b'r' ,b'e' ,b's' ,b't' ,b'a' ,b'c' ,b'k' , |
603 | // 0x0, thread name length |
604 | 0, 4, |
605 | // thread name (main) |
606 | b'm' ,b'a' ,b'i' ,b'n' , |
607 | // frame count |
608 | 1, |
609 | // 0x0, instanceidx, funcidx, codeoffset |
610 | 0, 0, 42, 51, |
611 | // local count |
612 | 1, |
613 | // local value type |
614 | 0x7F, |
615 | // local value |
616 | 1, |
617 | // stack count |
618 | 1, |
619 | // stack value type |
620 | 0x7F, |
621 | // stack value |
622 | 2 |
623 | |
624 | ] |
625 | ); |
626 | } |
627 | } |
628 | |