use std::cell::OnceCell;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fmt::Debug;
use std::fmt::Write as _;
use std::mem;
use std::ops::BitOr;
use std::ops::Shl;
use std::str;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_while;
use nom::character::complete::multispace0;
use nom::character::complete::space1;
use nom::combinator::cut;
use nom::combinator::map;
use nom::combinator::map_res;
use nom::combinator::opt;
use nom::error::ErrorKind;
use nom::error::ParseError;
use nom::error::VerboseError;
use nom::error::VerboseErrorKind;
use nom::multi::separated_list1;
use nom::sequence::preceded;
use nom::sequence::terminated;
use nom::sequence::tuple;
use nom::Err;
use nom::IResult;
use nom::Needed;
use nom::Offset as _;
use super::types::Function;
use super::types::Inlinee;
use super::types::PublicSymbol;
use super::types::SourceLine;
use super::types::SymbolFile;
use crate::error::IntoCowStr;
use crate::util::bytes_to_os_str;
use crate::Error;
use crate::ErrorExt;
use crate::Result;
fn stringify_error(input: &[u8], e: VerboseError<&[u8]>) -> String {
let mut result = String::new();
for (i, (substring, kind)) in e.errors.iter().enumerate() {
let offset = input.offset(substring);
if input.is_empty() {
match kind {
VerboseErrorKind::Char(c) => {
write!(&mut result, "{i}: expected '{c}', got empty input\n\n")
}
VerboseErrorKind::Context(s) => {
write!(&mut result, "{i}: in {s}, got empty input\n\n")
}
VerboseErrorKind::Nom(e) => {
write!(&mut result, "{i}: in {e:?}, got empty input\n\n")
}
}
} else {
let prefix = &input[..offset];
let line_number = prefix.iter().filter(|&&b| b == b'\n').count() + 1;
let line_begin = prefix
.iter()
.rev()
.position(|&b| b == b'\n')
.map(|pos| offset - pos)
.unwrap_or(0);
let line = input[line_begin..]
.split(|b| *b == b'\n')
.next()
.unwrap_or(&input[line_begin..]);
let column_number = line.offset(substring) + 1;
let line = String::from_utf8_lossy(line);
let line = line.trim_end();
match kind {
VerboseErrorKind::Char(c) => {
if let Some(actual) = String::from_utf8_lossy(substring).chars().next() {
write!(
&mut result,
"{i}: at line {line_number}:\n\
{line}\n\
{caret:>column$}\n\
expected '{expected}', found {actual}\n\n",
i = i,
line_number = line_number,
line = line,
caret = '^',
column = column_number,
expected = c,
actual = actual,
)
} else {
write!(
&mut result,
"{i}: at line {line_number}:\n\
{line}\n\
{caret:>column$}\n\
expected '{expected}', got end of input\n\n",
i = i,
line_number = line_number,
line = line,
caret = '^',
column = column_number,
expected = c,
)
}
}
VerboseErrorKind::Context(s) => write!(
&mut result,
"{i}: at line {line_number}, in {context}:\n\
{line}\n\
{caret:>column$}\n\n",
i = i,
line_number = line_number,
context = s,
line = line,
caret = '^',
column = column_number,
),
VerboseErrorKind::Nom(e) => write!(
&mut result,
"{i}: at line {line_number}, in {nom_err:?}:\n\
{line}\n\
{caret:>column$}\n\n",
i = i,
line_number = line_number,
nom_err = e,
line = line,
caret = '^',
column = column_number,
),
}
}
.unwrap();
}
result
}
fn convert_nom_err_to_error((input, err): (&[u8], Err<VerboseError<&[u8]>>)) -> Error {
match err {
Err::Incomplete(needed) => match needed {
Needed::Unknown => Error::with_invalid_input(
"got incomplete input, additional bytes are necessary to parse",
),
Needed::Size(num) => Error::with_invalid_input(format!(
"got incomplete input, {num} additional bytes are necessary to parse"
)),
},
Err::Error(err) | Err::Failure(err) => {
Error::with_invalid_input(stringify_error(input, err))
}
}
}
impl ErrorExt for (&[u8], Err<VerboseError<&[u8]>>) {
type Output = Error;
fn context<C>(self, context: C) -> Self::Output
where
C: IntoCowStr,
{
convert_nom_err_to_error(self).context(context)
}
fn with_context<C, F>(self, f: F) -> Self::Output
where
C: IntoCowStr,
F: FnOnce() -> C,
{
convert_nom_err_to_error(self).with_context(f)
}
}
#[derive(Debug)]
enum Line {
Module(OsString),
Info(()),
File(u32, String),
InlineOrigin(u32, String),
Public(PublicSymbol),
Function(Function),
StackWin(()),
StackCfi(()),
}
fn hex_str<T: Shl<T, Output = T> + BitOr<T, Output = T> + From<u8>>(
input: &[u8],
) -> IResult<&[u8], T, VerboseError<&[u8]>> {
let max_len = mem::size_of::<T>() * 2;
let mut res: T = T::from(0);
let mut k = 0;
for v in input.iter().take(max_len) {
let digit = match (*v as char).to_digit(16) {
Some(v) => v,
None => break,
};
res = res << T::from(4);
res = res | T::from(digit as u8);
k += 1;
}
if k == 0 {
return Err(Err::Error(VerboseError::from_error_kind(
input,
ErrorKind::HexDigit,
)))
}
let remaining = &input[k..];
Ok((remaining, res))
}
fn decimal_u32(input: &[u8]) -> IResult<&[u8], u32, VerboseError<&[u8]>> {
const MAX_LEN: usize = 10; let mut res: u64 = 0;
let mut k = 0;
for v in input.iter().take(MAX_LEN) {
let digit = *v as char;
let digit_value = match digit.to_digit(10) {
Some(v) => v,
None => break,
};
res = res * 10 + digit_value as u64;
k += 1;
}
if k == 0 {
return Err(Err::Error(VerboseError::from_error_kind(
input,
ErrorKind::Digit,
)))
}
let res = u32::try_from(res)
.map_err(|_| Err::Error(VerboseError::from_error_kind(input, ErrorKind::TooLarge)))?;
let remaining = &input[k..];
Ok((remaining, res))
}
fn non_space(input: &[u8]) -> IResult<&[u8], &[u8], VerboseError<&[u8]>> {
take_while(|c: u8| c != b' ')(input)
}
fn my_eol(input: &[u8]) -> IResult<&[u8], &[u8], VerboseError<&[u8]>> {
preceded(take_while(|b| b == b'\r'), tag(b"\n"))(input)
}
fn not_my_eol(input: &[u8]) -> IResult<&[u8], &[u8], VerboseError<&[u8]>> {
take_while(|b| b != b'\r' && b != b'\n')(input)
}
fn module_line(input: &[u8]) -> IResult<&[u8], &OsStr, VerboseError<&[u8]>> {
let (input, _) = terminated(tag("MODULE"), space1)(input)?;
let (input, (_, _, _, module)) = cut(tuple((
terminated(non_space, space1), terminated(non_space, space1), terminated(non_space, space1), terminated(map_res(not_my_eol, bytes_to_os_str), my_eol), )))(input)?;
Ok((input, module))
}
fn info_url(input: &[u8]) -> IResult<&[u8], (), VerboseError<&[u8]>> {
let (input, _) = terminated(tag("INFO URL"), space1)(input)?;
let (input, _) = cut(terminated(not_my_eol, my_eol))(input)?;
Ok((input, ()))
}
fn info_line(input: &[u8]) -> IResult<&[u8], &[u8], VerboseError<&[u8]>> {
let (input, _) = terminated(tag("INFO"), space1)(input)?;
cut(terminated(not_my_eol, my_eol))(input)
}
fn file_line(input: &[u8]) -> IResult<&[u8], (u32, String), VerboseError<&[u8]>> {
let (input, _) = terminated(tag("FILE"), space1)(input)?;
let (input, (id, filename)) = cut(tuple((
terminated(decimal_u32, space1),
terminated(map_res(not_my_eol, str::from_utf8), my_eol),
)))(input)?;
Ok((input, (id, filename.to_string())))
}
fn inline_origin_line(input: &[u8]) -> IResult<&[u8], (u32, String), VerboseError<&[u8]>> {
let (input, _) = terminated(tag("INLINE_ORIGIN"), space1)(input)?;
let (input, (id, function)) = cut(tuple((
terminated(decimal_u32, space1),
terminated(map_res(not_my_eol, str::from_utf8), my_eol),
)))(input)?;
Ok((input, (id, function.to_string())))
}
fn public_line(input: &[u8]) -> IResult<&[u8], PublicSymbol, VerboseError<&[u8]>> {
let (input, _) = terminated(tag("PUBLIC"), space1)(input)?;
let (input, (_multiple, addr, parameter_size, name)) = cut(tuple((
opt(terminated(tag("m"), space1)),
terminated(hex_str::<u64>, space1),
terminated(hex_str::<u32>, space1),
terminated(map_res(not_my_eol, str::from_utf8), my_eol),
)))(input)?;
Ok((
input,
PublicSymbol {
addr,
parameter_size,
name: name.to_string(),
},
))
}
fn func_line_data(input: &[u8]) -> IResult<&[u8], SourceLine, VerboseError<&[u8]>> {
let (input, (addr, size, line, file)) = tuple((
terminated(hex_str::<u64>, space1),
terminated(hex_str::<u32>, space1),
terminated(decimal_u32, space1),
terminated(decimal_u32, my_eol),
))(input)?;
Ok((
input,
SourceLine {
addr,
size,
file,
line,
},
))
}
fn func_line(input: &[u8]) -> IResult<&[u8], Function, VerboseError<&[u8]>> {
let (input, _) = terminated(tag("FUNC"), space1)(input)?;
let (input, (_multiple, addr, size, parameter_size, name)) = cut(tuple((
opt(terminated(tag("m"), space1)),
terminated(hex_str::<u64>, space1),
terminated(hex_str::<u32>, space1),
terminated(hex_str::<u32>, space1),
terminated(map_res(not_my_eol, str::from_utf8), my_eol),
)))(input)?;
Ok((
input,
Function {
addr,
size,
parameter_size,
name: name.to_string(),
lines: Vec::new(),
inlinees: Vec::new(),
},
))
}
fn inline_address_range(input: &[u8]) -> IResult<&[u8], (u64, u32), VerboseError<&[u8]>> {
tuple((terminated(hex_str::<u64>, space1), hex_str::<u32>))(input)
}
fn inline_line(input: &[u8]) -> IResult<&[u8], impl Iterator<Item = Inlinee>, VerboseError<&[u8]>> {
let (input, _) = terminated(tag("INLINE"), space1)(input)?;
let (input, (depth, call_line, call_file, origin_id)) = cut(tuple((
terminated(decimal_u32, space1),
terminated(decimal_u32, space1),
terminated(decimal_u32, space1),
terminated(decimal_u32, space1),
)))(input)?;
let (input, address_ranges) = cut(terminated(
separated_list1(space1, inline_address_range),
my_eol,
))(input)?;
Ok((
input,
address_ranges.into_iter().map(move |(addr, size)| Inlinee {
addr,
size,
call_file,
call_line,
depth,
origin_id,
}),
))
}
fn stack_win_line(input: &[u8]) -> IResult<&[u8], (), VerboseError<&[u8]>> {
let (input, _) = terminated(tag("STACK WIN"), space1)(input)?;
let (input, _) = cut(terminated(not_my_eol, my_eol))(input)?;
Ok((input, ()))
}
fn stack_cfi(input: &[u8]) -> IResult<&[u8], (), VerboseError<&[u8]>> {
let (input, _) = terminated(tag("STACK CFI"), space1)(input)?;
let (input, _) = cut(terminated(not_my_eol, my_eol))(input)?;
Ok((input, ()))
}
fn stack_cfi_init(input: &[u8]) -> IResult<&[u8], (), VerboseError<&[u8]>> {
let (input, _) = terminated(tag("STACK CFI INIT"), space1)(input)?;
let (input, _) = cut(terminated(not_my_eol, my_eol))(input)?;
Ok((input, ()))
}
fn line(input: &[u8]) -> IResult<&[u8], Line, VerboseError<&[u8]>> {
terminated(
alt((
map(info_url, Line::Info),
map(info_line, |_| Line::Info(())),
map(file_line, |(i, f)| Line::File(i, f)),
map(inline_origin_line, |(i, f)| Line::InlineOrigin(i, f)),
map(public_line, Line::Public),
map(func_line, Line::Function),
map(stack_win_line, Line::StackWin),
map(stack_cfi_init, Line::StackCfi),
map(module_line, |module| Line::Module(module.to_os_string())),
)),
multispace0,
)(input)
}
#[derive(Debug, Default)]
pub struct SymbolParser {
module: Option<OsString>,
files: HashMap<u32, String>,
inline_origins: HashMap<u32, String>,
publics: Vec<PublicSymbol>,
functions: Vec<Function>,
pub lines: u64,
cur_item: Option<Line>,
}
impl SymbolParser {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn parse(&mut self, mut input: &[u8]) -> Result<usize> {
input = if let Some(idx) = input.iter().rposition(|&x| x == b'\n') {
&input[..idx + 1]
} else {
return Ok(0)
};
let orig_input = input;
loop {
if input.is_empty() {
return Ok(orig_input.len())
}
match self.cur_item.take() {
Some(Line::Function(mut cur)) => {
match self.parse_func_subline(input, &mut cur.lines, &mut cur.inlinees) {
Ok((new_input, ())) => {
input = new_input;
self.cur_item = Some(Line::Function(cur));
self.lines += 1;
continue
}
Err(_) => {
self.finish_item(Line::Function(cur));
continue
}
}
}
Some(Line::StackCfi(cur)) => match stack_cfi(input) {
Ok((new_input, _line)) => {
input = new_input;
self.cur_item = Some(Line::StackCfi(cur));
self.lines += 1;
continue
}
Err(_) => {
self.finish_item(Line::StackCfi(cur));
continue
}
},
_ => {
}
}
let line = match line(input) {
Ok((new_input, line)) => {
input = new_input;
line
}
Err(err) => {
return Err((input, err)).context("failed to parse input")
}
};
match line {
Line::Module(module) => {
if self.lines != 0 {
return Err(Error::with_invalid_input(
"MODULE line found after the start of the file",
))
}
debug_assert_eq!(self.module, None);
self.module = Some(module);
}
Line::Info(()) => {}
Line::File(id, filename) => {
self.files.insert(id, filename.to_string());
}
Line::InlineOrigin(id, function) => {
self.inline_origins.insert(id, function.to_string());
}
Line::Public(p) => {
self.publics.push(p);
}
Line::StackWin(..) => {}
item @ Line::Function(..) => {
self.cur_item = Some(item);
}
item @ Line::StackCfi(..) => {
self.cur_item = Some(item);
}
}
self.lines += 1;
}
}
fn parse_func_subline<'a>(
&mut self,
input: &'a [u8],
lines: &mut Vec<SourceLine>,
inlinees: &mut Vec<Inlinee>,
) -> IResult<&'a [u8], (), VerboseError<&'a [u8]>> {
if input.starts_with(b"INLINE_ORIGIN ") {
let (input, (id, function)) = inline_origin_line(input)?;
self.inline_origins.insert(id, function);
return Ok((input, ()))
}
if input.starts_with(b"INLINE ") {
let (input, new_inlinees) = inline_line(input)?;
inlinees.extend(new_inlinees);
return Ok((input, ()))
}
let (input, line) = func_line_data(input)?;
lines.push(line);
Ok((input, ()))
}
fn finish_item(&mut self, item: Line) {
match item {
Line::Function(mut cur) => {
let () = cur.lines.sort_by_key(|x| (x.addr, x.size));
let () = cur.inlinees.sort_by_key(|x| (x.depth, x.addr));
let () = self.functions.push(cur);
}
Line::StackCfi(..) => {}
_ => {
unreachable!()
}
}
}
pub(crate) fn finish(mut self) -> SymbolFile {
if let Some(item) = self.cur_item.take() {
self.finish_item(item);
}
let () = self
.functions
.sort_by(|x, y| x.addr.cmp(&y.addr).then_with(|| x.size.cmp(&y.size)));
SymbolFile {
module: self.module,
files: self.files,
functions: self.functions,
by_name_idx: OnceCell::new(),
inline_origins: self.inline_origins,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nom::character::complete::char;
use nom::sequence::pair;
use nom::Parser as _;
#[test]
fn nom_err_conversion() {
let input = b"a";
let err = pair(char('a'), char('b'))
.parse(input.as_slice())
.unwrap_err();
let err = convert_nom_err_to_error((input, err));
assert_eq!(
err.to_string(),
"0: at line 1:\na\n ^\nexpected \'b\', got end of input\n\n"
);
let input = b"foo bar baz";
let err = VerboseError {
errors: vec![
(&input[1..], VerboseErrorKind::Context("context")),
(&input[2..], VerboseErrorKind::Char('y')),
(&input[3..], VerboseErrorKind::Nom(ErrorKind::Tag)),
],
};
let s = stringify_error(input, err.clone());
let expected = "\
0: at line 1, in context:
foo bar baz
^
1: at line 1:
foo bar baz
^
expected 'y', found o
2: at line 1, in Tag:
foo bar baz
^";
assert_eq!(s.trim(), expected);
let empty = &input[0..0];
let s = stringify_error(empty, err);
let expected = "\
0: in context, got empty input
1: expected 'y', got empty input
2: in Tag, got empty input";
assert_eq!(s.trim(), expected);
}
#[test]
fn parse_module_line() {
let line = b"MODULE Linux x86 D3096ED481217FD4C16B29CD9BC208BA0 firefox-bin\n";
let rest = &b""[..];
assert_eq!(module_line(line), Ok((rest, OsStr::new("firefox-bin"))));
}
#[test]
fn parse_module_line_filename_spaces() {
let line = b"MODULE Windows x86_64 D3096ED481217FD4C16B29CD9BC208BA0 firefox x y z\n";
let rest = &b""[..];
assert_eq!(module_line(line), Ok((rest, OsStr::new("firefox x y z"))));
}
#[test]
fn parse_module_line_crcrlf() {
let line =
b"MODULE Windows x86_64 D3096ED481217FD4C16B29CD9BC208BA0 /a/proper/fire/fox\r\r\n";
let rest = &b""[..];
assert_eq!(
module_line(line),
Ok((rest, OsStr::new("/a/proper/fire/fox")))
);
}
#[test]
fn parse_info_line() {
let line = b"INFO blah blah blah\n";
let bits = &b"blah blah blah"[..];
let rest = &b""[..];
assert_eq!(info_line(line), Ok((rest, bits)));
}
#[test]
fn parse_info_line2() {
let line = b"INFO CODE_ID abc xyz\n";
let bits = &b"CODE_ID abc xyz"[..];
let rest = &b""[..];
assert_eq!(info_line(line), Ok((rest, bits)));
}
#[test]
fn parse_info_url() {
let line = b"INFO URL https://www.example.com\n";
let rest = &b""[..];
assert_eq!(info_url(line), Ok((rest, ())));
}
#[test]
fn parse_file_line() {
let line = b"FILE 1 foo.c\n";
let rest = &b""[..];
assert_eq!(file_line(line), Ok((rest, (1, String::from("foo.c")))));
}
#[test]
fn parse_file_line_spaces() {
let line = b"FILE 1234 foo bar.xyz\n";
let rest = &b""[..];
assert_eq!(
file_line(line),
Ok((rest, (1234, String::from("foo bar.xyz"))))
);
}
#[test]
fn parse_public_line() {
let line = b"PUBLIC f00d d00d some func\n";
let rest = &b""[..];
assert_eq!(
public_line(line),
Ok((
rest,
PublicSymbol {
addr: 0xf00d,
parameter_size: 0xd00d,
name: "some func".to_string(),
}
))
);
}
#[test]
fn parse_public_with_m() {
let line = b"PUBLIC m f00d d00d some func\n";
let rest = &b""[..];
assert_eq!(
public_line(line),
Ok((
rest,
PublicSymbol {
addr: 0xf00d,
parameter_size: 0xd00d,
name: "some func".to_string(),
}
))
);
}
#[test]
fn parse_func_lines_no_lines() {
let line =
b"FUNC c184 30 0 nsQueryInterfaceWithError::operator()(nsID const&, void**) const\n";
let rest = &b""[..];
assert_eq!(
func_line(line),
Ok((
rest,
Function {
addr: 0xc184,
size: 0x30,
parameter_size: 0,
name: "nsQueryInterfaceWithError::operator()(nsID const&, void**) const"
.to_string(),
lines: Vec::new(),
inlinees: Vec::new(),
}
))
);
}
#[test]
fn parse_truncated_func() {
let line = b"FUNC 1000\n1000 10 42 7\n";
assert_eq!(
func_line(line),
Err(Err::Failure(VerboseError {
errors: vec![(&line[9..], VerboseErrorKind::Nom(ErrorKind::Space))],
}))
);
}
#[test]
fn parse_inline_line_single_range() {
let line = b"INLINE 0 3082 52 1410 49200 10\n";
assert_eq!(
inline_line(line).unwrap().1.collect::<Vec<_>>(),
vec![Inlinee {
depth: 0,
addr: 0x49200,
size: 0x10,
call_file: 52,
call_line: 3082,
origin_id: 1410
}]
)
}
#[test]
fn parse_inline_line_multiple_ranges() {
let line = b"INLINE 6 642 8 207 8b110 18 8b154 18\n";
assert_eq!(
inline_line(line).unwrap().1.collect::<Vec<_>>(),
vec![
Inlinee {
depth: 6,
addr: 0x8b110,
size: 0x18,
call_file: 8,
call_line: 642,
origin_id: 207
},
Inlinee {
depth: 6,
addr: 0x8b154,
size: 0x18,
call_file: 8,
call_line: 642,
origin_id: 207
}
]
)
}
#[test]
fn parse_func_lines_and_lines() {
let data = b"FUNC 1000 30 10 some func
1000 10 42 7
INLINE_ORIGIN 16 inlined_function_name()
1010 10 52 8
INLINE 0 23 9 16 1020 10
1020 10 62 15
";
let file = SymbolFile::from_bytes(data).expect("failed to parse!");
let f = file.functions.first().unwrap();
assert_eq!(f.addr, 0x1000);
assert_eq!(f.size, 0x30);
assert_eq!(f.parameter_size, 0x10);
assert_eq!(f.name, "some func".to_string());
assert_eq!(
f.find_line(0x1000).unwrap(),
&SourceLine {
addr: 0x1000,
size: 0x10,
file: 7,
line: 42,
}
);
assert_eq!(
f.lines,
vec![
SourceLine {
addr: 0x1000,
size: 0x10,
file: 7,
line: 42,
},
SourceLine {
addr: 0x1010,
size: 0x10,
file: 8,
line: 52,
},
SourceLine {
addr: 0x1020,
size: 0x10,
file: 15,
line: 62,
},
]
);
assert_eq!(
f.inlinees,
vec![Inlinee {
depth: 0,
addr: 0x1020,
size: 0x10,
call_file: 9,
call_line: 23,
origin_id: 16
}]
);
}
#[test]
fn parse_nested_inlines() {
let data = b"FUNC 1000 30 10 outer_func()
INLINE_ORIGIN 1 inner_func_2()
INLINE_ORIGIN 2 mid_func()
INLINE_ORIGIN 3 inner_func_1()
INLINE 0 60 15 2 1000 20
INLINE 1 12 4 3 1000 10
INLINE 1 17 4 1 1010 10
1000 10 42 7
1010 10 52 8
1020 10 62 15
";
let file = SymbolFile::from_bytes(data).expect("failed to parse!");
let f = file.functions.first().unwrap();
assert_eq!(f.addr, 0x1000);
assert_eq!(f.size, 0x30);
assert_eq!(f.parameter_size, 0x10);
assert_eq!(f.name, "outer_func()".to_string());
assert_eq!(f.get_inlinee_at_depth(0, 0x0fff), None);
assert_eq!(f.get_inlinee_at_depth(0, 0x1000), Some((15, 60, 0x1000, 2)));
assert_eq!(f.get_inlinee_at_depth(0, 0x100f), Some((15, 60, 0x1000, 2)));
assert_eq!(f.get_inlinee_at_depth(0, 0x1010), Some((15, 60, 0x1000, 2)));
assert_eq!(f.get_inlinee_at_depth(0, 0x101f), Some((15, 60, 0x1000, 2)));
assert_eq!(f.get_inlinee_at_depth(0, 0x1020), None);
assert_eq!(f.get_inlinee_at_depth(0, 0x102f), None);
assert_eq!(f.get_inlinee_at_depth(0, 0x1030), None);
assert_eq!(f.get_inlinee_at_depth(1, 0x0fff), None);
assert_eq!(f.get_inlinee_at_depth(1, 0x1000), Some((4, 12, 0x1000, 3)));
assert_eq!(f.get_inlinee_at_depth(1, 0x100f), Some((4, 12, 0x1000, 3)));
assert_eq!(f.get_inlinee_at_depth(1, 0x1010), Some((4, 17, 0x1010, 1)));
assert_eq!(f.get_inlinee_at_depth(1, 0x101f), Some((4, 17, 0x1010, 1)));
assert_eq!(f.get_inlinee_at_depth(1, 0x1020), None);
assert_eq!(f.get_inlinee_at_depth(1, 0x102f), None);
assert_eq!(f.get_inlinee_at_depth(1, 0x1030), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x0fff), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x1000), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x100f), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x1010), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x101f), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x1020), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x102f), None);
assert_eq!(f.get_inlinee_at_depth(2, 0x1030), None);
}
#[test]
fn parse_func_with_m() {
let data = b"FUNC m 1000 30 10 some func
1000 10 42 7
1010 10 52 8
1020 10 62 15
";
let file = SymbolFile::from_bytes(data).expect("failed to parse!");
let _f = file.functions.first().unwrap();
}
#[test]
fn parse_stack_win_line_program_string() {
let line =
b"STACK WIN 4 2170 14 a1 b2 c3 d4 e5 f6 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =\n";
let (rest, ()) = stack_win_line(line).unwrap();
assert_eq!(rest, &[] as &[u8]);
}
#[test]
fn parse_stack_win_line_frame_data() {
let line = b"STACK WIN 0 1000 30 a1 b2 c3 d4 e5 f6 0 1\n";
let (rest, ()) = stack_win_line(line).unwrap();
assert_eq!(rest, &[] as &[u8]);
}
#[test]
fn parse_stack_cfi() {
let line = b"STACK CFI deadf00d some rules\n";
let (rest, ()) = stack_cfi(line).unwrap();
assert_eq!(rest, &[] as &[u8]);
}
#[test]
fn parse_stack_cfi_init() {
let line = b"STACK CFI INIT badf00d abc init rules\n";
let (rest, ()) = stack_cfi_init(line).unwrap();
assert_eq!(rest, &[] as &[u8]);
}
#[test]
fn parse_stack_cfi_lines() {
let data = b"STACK CFI INIT badf00d abc init rules
STACK CFI deadf00d some rules
STACK CFI deadbeef more rules
";
let _file = SymbolFile::from_bytes(data).expect("failed to parse!");
}
#[test]
fn parse_symbol_bytes() {
let bytes = &b"MODULE Linux x86 D3096ED481217FD4C16B29CD9BC208BA0 firefox-bin
INFO blah blah blah
FILE 0 foo.c
FILE 100 bar.c
PUBLIC abcd 10 func 1
PUBLIC ff00 3 func 2
FUNC 900 30 10 some other func
FUNC 1000 30 10 some func
1000 10 42 7
1010 10 52 8
1020 10 62 15
FUNC 1100 30 10 a third func
STACK WIN 4 900 30 a1 b2 c3 d4 e5 f6 1 prog string
STACK WIN 0 1000 30 a1 b2 c3 d4 e5 f6 0 1
STACK CFI INIT badf00d abc init rules
STACK CFI deadf00d some rules
STACK CFI deadbeef more rules
STACK CFI INIT f00f f0 more init rules
"[..];
let sym = SymbolFile::from_bytes(bytes).unwrap();
assert_eq!(sym.files.len(), 2);
assert_eq!(sym.files.get(&0).unwrap(), "foo.c");
assert_eq!(sym.files.get(&100).unwrap(), "bar.c");
assert_eq!(sym.functions.len(), 3);
let funcs = &sym.functions;
{
let f = &funcs[0];
assert_eq!(f.addr, 0x900);
assert_eq!(f.size, 0x30);
assert_eq!(f.parameter_size, 0x10);
assert_eq!(f.name, "some other func".to_string());
assert_eq!(f.lines.len(), 0);
}
{
let f = &funcs[1];
assert_eq!(f.addr, 0x1000);
assert_eq!(f.size, 0x30);
assert_eq!(f.parameter_size, 0x10);
assert_eq!(f.name, "some func".to_string());
assert_eq!(
f.lines,
vec![
SourceLine {
addr: 0x1000,
size: 0x10,
file: 7,
line: 42,
},
SourceLine {
addr: 0x1010,
size: 0x10,
file: 8,
line: 52,
},
SourceLine {
addr: 0x1020,
size: 0x10,
file: 15,
line: 62,
},
]
);
}
{
let f = &funcs[2];
assert_eq!(f.addr, 0x1100);
assert_eq!(f.size, 0x30);
assert_eq!(f.parameter_size, 0x10);
assert_eq!(f.name, "a third func".to_string());
assert_eq!(f.lines.len(), 0);
}
}
#[test]
fn parse_with_overlap() {
let bytes = b"MODULE Linux x86 D3096ED481217FD4C16B29CD9BC208BA0 firefox-bin
FILE 0 foo.c
PUBLIC abcd 10 func 1
PUBLIC ff00 3 func 2
FUNC 1000 30 10 some func
1000 10 42 0
1000 10 43 0
1001 10 44 0
1001 5 45 0
1010 10 52 0
FUNC 1000 30 10 some func overlap exact
FUNC 1001 30 10 some func overlap end
FUNC 1001 10 10 some func overlap contained
";
let sym = SymbolFile::from_bytes(&bytes[..]).unwrap();
assert_eq!(sym.functions.len(), 4);
}
#[test]
fn parse_symbol_bytes_malformed() {
assert!(SymbolFile::from_bytes(&b"this is not a symbol file\n"[..]).is_err(),);
assert!(SymbolFile::from_bytes(
&b"MODULE Linux x86 xxxxxx
FILE 0 foo.c
"[..]
)
.is_ok(),);
assert!(SymbolFile::from_bytes(
&b"MODULE Linux x86 abcd1234 foo
FILE x foo.c
"[..]
)
.is_err(),);
assert!(SymbolFile::from_bytes(
&b"MODULE Linux x86 abcd1234 foo
FUNC xx 1 2 foo
"[..]
)
.is_err(),);
assert!(SymbolFile::from_bytes(
&b"MODULE Linux x86 abcd1234 foo
this is some junk
"[..]
)
.is_err(),);
assert!(SymbolFile::from_bytes(
&b"MODULE Linux x86 abcd1234 foo
FILE 0 foo.c
FILE"[..]
)
.is_err(),);
assert!(SymbolFile::from_bytes(&b""[..]).is_err(),);
}
#[test]
fn parse_stack_win_inconsistent() {
let bytes = b"MODULE Windows x86 D3096ED481217FD4C16B29CD9BC208BA0 firefox-bin
FILE 0 foo.c
STACK WIN 0 1d93e 4 4 0 0 10 0 0 1 1
STACK WIN 0 2d93e 4 4 0 0 10 0 0 1 0
STACK WIN 0 3d93e 4 4 0 0 10 0 0 1 prog string
STACK WIN 0 4d93e 4 4 0 0 10 0 0 0 1
STACK WIN 4 5d93e 4 4 0 0 10 0 0 0 1
STACK WIN 4 6d93e 4 4 0 0 10 0 0 0 0
STACK WIN 4 7d93e 4 4 0 0 10 0 0 0 prog string
STACK WIN 4 8d93e 4 4 0 0 10 0 0 1 prog string
";
let _sym = SymbolFile::from_bytes(&bytes[..]).unwrap();
}
#[test]
fn function_overlap() {
let bytes = b"FUNC 1 2 3 x
FUNC 1 3 3 y
";
let sym = SymbolFile::from_bytes(bytes.as_slice()).unwrap();
for addr in 1..=3 {
assert!(sym.find_function(addr).is_some());
}
assert_eq!(sym.find_function(4), None);
}
#[test]
fn address_size_overflow() {
let bytes = b"FUNC 1 2 3 x
ffffffffffffffff 2 0 0
";
let sym = SymbolFile::from_bytes(bytes.as_slice()).unwrap();
let fun = sym.find_function(1).unwrap();
assert_eq!(fun.lines.len(), 1);
assert!(fun.name == "x");
}
#[test]
fn invalid_input() {
let input = [31, 139, 8, 8, 85, 135, 48, 102, 2, 255, 108, 105, 98, b'\n'];
let _err = SymbolFile::from_bytes(input.as_slice());
}
}