Skip to content

Commit

Permalink
🤓 [wasm-parser] Add readers for core dump custom sections. (#1012)
Browse files Browse the repository at this point in the history
* 🤓 [wasm-parser] Add readers for core dump custom sections.

* Add a roundtrip test now that we have both parsing and encoding
  • Loading branch information
itsrainy authored May 3, 2023
1 parent 34216db commit cf1d02d
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 0 deletions.
76 changes: 76 additions & 0 deletions crates/wasm-encoder/src/core/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,82 @@ impl Encode for CoreDumpValue {
mod tests {
use super::*;

// Create new core dump and core dump stack sections and test whether they
// are properly encoded and parsed back out by wasmparser
#[test]
fn test_roundtrip() {
use crate::Module;
use wasmparser::{BinaryReader, FromReader, Parser, Payload};

let core = CoreDumpSection::new("test.wasm");
let mut corestack = CoreDumpStackSection::new("main");
corestack.frame(
12,
0,
vec![CoreDumpValue::I32(10)],
vec![CoreDumpValue::I32(42)],
);
let mut module = Module::new();
module.section(&core);
module.section(&corestack);
let wasm_bytes = module.finish();

let mut parser = Parser::new(0).parse_all(&wasm_bytes);
match parser.next() {
Some(Ok(Payload::Version { .. })) => {}
_ => panic!(""),
}

let payload = parser
.next()
.expect("parser is not empty")
.expect("element is a payload");
match payload {
Payload::CustomSection(section) => {
assert_eq!(section.name(), "core");
let core = wasmparser::CoreDumpSection::from_reader(&mut BinaryReader::new(
section.data(),
))
.expect("data is readable into a core dump section");
assert_eq!(core.name, "test.wasm");
}
_ => panic!("unexpected payload"),
}

let payload = parser
.next()
.expect("parser is not empty")
.expect("element is a payload");
match payload {
Payload::CustomSection(section) => {
assert_eq!(section.name(), "corestack");
let corestack = wasmparser::CoreDumpStackSection::from_reader(
&mut BinaryReader::new(section.data()),
)
.expect("data is readable into a core dump stack section");
assert_eq!(corestack.name, "main");
assert_eq!(corestack.frames.len(), 1);
let frame = corestack
.frames
.first()
.expect("frame is encoded in corestack");
assert_eq!(frame.funcidx, 12);
assert_eq!(frame.codeoffset, 0);
assert_eq!(frame.locals.len(), 1);
match frame.locals.first().expect("frame contains a local") {
&wasmparser::CoreDumpValue::I32(val) => assert_eq!(val, 10),
_ => panic!("unexpected local value"),
}
assert_eq!(frame.stack.len(), 1);
match frame.stack.first().expect("stack contains a value") {
&wasmparser::CoreDumpValue::I32(val) => assert_eq!(val, 42),
_ => panic!("unexpected stack value"),
}
}
_ => panic!("unexpected payload"),
}
}

#[test]
fn test_encode_coredump_section() {
let core = CoreDumpSection::new("test");
Expand Down
2 changes: 2 additions & 0 deletions crates/wasmparser/src/readers/core.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod code;
mod coredumps;
mod custom;
mod data;
mod elements;
Expand All @@ -16,6 +17,7 @@ mod tags;
mod types;

pub use self::code::*;
pub use self::coredumps::*;
pub use self::custom::*;
pub use self::data::*;
pub use self::elements::*;
Expand Down
150 changes: 150 additions & 0 deletions crates/wasmparser/src/readers/core/coredumps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use crate::{BinaryReader, FromReader, Result};

/// The data portion of a custom section representing a core dump. Per the
/// tool-conventions repo, this section just specifies the executable name that
/// the core dump came from while the rest of the core dump information is
/// contained in a corestack custom section
///
/// # Examples
///
/// ```
/// use wasmparser::{ BinaryReader, CoreDumpSection, FromReader, Result };
/// let data: &[u8] = &[0x00, 0x09, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x77, 0x61,
/// 0x73, 0x6d];
/// let mut reader = BinaryReader::new(data);
/// let core = CoreDumpSection::from_reader(&mut reader).unwrap();
/// assert!(core.name == "test.wasm")
/// ```
pub struct CoreDumpSection<'a> {
/// The name of the process that created the core dump
pub name: &'a str,
}

impl<'a> FromReader<'a> for CoreDumpSection<'a> {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
let pos = reader.original_position();
if reader.read_u8()? != 0 {
bail!(pos, "invalid start byte for core dump name");
}
let name = reader.read_string()?;
Ok(CoreDumpSection { name })
}
}

/// The data portion of a custom section representing a core dump stack. The
/// structure of this follows the coredump spec in the tool-conventions repo
///
/// # Examples
///
/// ```
/// let data: &[u8] = &[0x00, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x01, 0x00, 0x2a,
/// 0x33, 0x01, 0x7f, 0x01, 0x01, 0x7f, 0x02];
/// use wasmparser::{ BinaryReader, CoreDumpStackSection, FromReader };
/// let mut reader = BinaryReader::new(data);
/// let corestack = CoreDumpStackSection::from_reader(&mut reader).unwrap();
/// assert!(corestack.name == "main");
/// assert!(corestack.frames.len() == 1);
/// let frame = &corestack.frames[0];
/// assert!(frame.funcidx == 42);
/// assert!(frame.codeoffset == 51);
/// assert!(frame.locals.len() == 1);
/// assert!(frame.stack.len() == 1);
/// ```
pub struct CoreDumpStackSection<'a> {
/// The thread name
pub name: &'a str,
/// The stack frames for the core dump
pub frames: Vec<CoreDumpStackFrame>,
}

impl<'a> FromReader<'a> for CoreDumpStackSection<'a> {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
let pos = reader.original_position();
if reader.read_u8()? != 0 {
bail!(pos, "invalid start byte for core dump stack name");
}
let name = reader.read_string()?;
let mut frames = vec![];
for _ in 0..reader.read_var_u32()? {
frames.push(CoreDumpStackFrame::from_reader(reader)?);
}
Ok(CoreDumpStackSection {
name: name,
frames: frames,
})
}
}

/// A single stack frame from a core dump
#[derive(Debug)]
pub struct CoreDumpStackFrame {
/// The function index in the module
pub funcidx: u32,
/// The instruction's offset relative to the function's start
pub codeoffset: u32,
/// The locals for this stack frame (including function parameters)
pub locals: Vec<CoreDumpValue>,
/// The values on the stack
pub stack: Vec<CoreDumpValue>,
}

impl<'a> FromReader<'a> for CoreDumpStackFrame {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
let pos = reader.original_position();
if reader.read_u8()? != 0 {
bail!(pos, "invalid start byte for core dump stack frame");
}
let funcidx = reader.read_var_u32()?;
let codeoffset = reader.read_var_u32()?;
let mut locals = vec![];
for _ in 0..reader.read_var_u32()? {
locals.push(CoreDumpValue::from_reader(reader)?);
}
let mut stack = vec![];
for _ in 0..reader.read_var_u32()? {
stack.push(CoreDumpValue::from_reader(reader)?);
}

Ok(CoreDumpStackFrame {
funcidx,
codeoffset,
locals,
stack,
})
}
}

/// Local and stack values are encoded using one byte for the type (similar to
/// Wasm's Number Types) followed by bytes representing the actual value
/// See the tool-conventions repo for more details.
#[derive(Clone, Debug)]
pub enum CoreDumpValue {
/// A missing value (usually missing because it was optimized out)
Missing,
/// An i32 value
I32(i32),
/// An i64 value
I64(i64),
/// An f32 value
F32(f32),
/// An f64 value
F64(f64),
}

impl<'a> FromReader<'a> for CoreDumpValue {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
let pos = reader.original_position();
match reader.read_u8()? {
0x01 => Ok(CoreDumpValue::Missing),
0x7F => Ok(CoreDumpValue::I32(reader.read_var_i32()?)),
0x7E => Ok(CoreDumpValue::I64(reader.read_var_i64()?)),
0x7D => Ok(CoreDumpValue::F32(f32::from_bits(
reader.read_f32()?.bits(),
))),
0x7C => Ok(CoreDumpValue::F64(f64::from_bits(
reader.read_f64()?.bits(),
))),
_ => bail!(pos, "invalid CoreDumpValue type"),
}
}
}

0 comments on commit cf1d02d

Please sign in to comment.