use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use goblin::mach;
use smallvec::SmallVec;
use thiserror::Error;
use symbolic_common::{Arch, AsSelf, CodeId, DebugId, Uuid};
use crate::base::*;
use crate::dwarf::{Dwarf, DwarfDebugSession, DwarfError, DwarfSection, Endian};
pub(crate) use mono_archive::{MonoArchive, MonoArchiveObjects};
mod bcsymbolmap;
pub mod compact;
mod mono_archive;
pub use bcsymbolmap::*;
pub use compact::*;
const SWIFT_HIDDEN_PREFIX: &str = "__hidden#";
#[derive(Debug, Error)]
#[error("invalid MachO file")]
pub struct MachError {
#[source]
source: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl MachError {
fn new<E>(source: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
let source = Some(source.into());
Self { source }
}
}
impl From<goblin::error::Error> for MachError {
fn from(e: goblin::error::Error) -> Self {
Self::new(e)
}
}
impl From<scroll::Error> for MachError {
fn from(e: scroll::Error) -> Self {
Self::new(e)
}
}
pub struct MachObject<'d> {
macho: mach::MachO<'d>,
data: &'d [u8],
bcsymbolmap: Option<Arc<BcSymbolMap<'d>>>,
}
impl<'d> MachObject<'d> {
pub fn test(data: &[u8]) -> bool {
matches!(MachArchive::is_fat(data), Some(false))
}
pub fn parse(data: &'d [u8]) -> Result<Self, MachError> {
mach::MachO::parse(data, 0)
.map(|macho| MachObject {
macho,
data,
bcsymbolmap: None,
})
.map_err(MachError::new)
}
pub fn load_symbolmap(&mut self, symbolmap: BcSymbolMap<'d>) {
self.bcsymbolmap = Some(Arc::new(symbolmap));
}
pub fn compact_unwind_info(&self) -> Result<Option<CompactUnwindInfoIter<'d>>, MachError> {
if let Some(section) = self.section("unwind_info") {
if let Cow::Borrowed(section) = section.data {
let arch = self.arch();
let is_little_endian = self.endianity() == Endian::Little;
return Ok(Some(CompactUnwindInfoIter::new(
section,
is_little_endian,
arch,
)?));
}
}
Ok(None)
}
pub fn file_format(&self) -> FileFormat {
FileFormat::MachO
}
fn find_uuid(&self) -> Option<Uuid> {
for cmd in &self.macho.load_commands {
if let mach::load_command::CommandVariant::Uuid(ref uuid_cmd) = cmd.command {
return Uuid::from_slice(&uuid_cmd.uuid).ok();
}
}
None
}
pub fn name(&self) -> Option<&'d str> {
self.macho.name
}
pub fn code_id(&self) -> Option<CodeId> {
let uuid = self.find_uuid()?;
Some(CodeId::from_binary(&uuid.as_bytes()[..]))
}
pub fn debug_id(&self) -> DebugId {
self.find_uuid().map(DebugId::from_uuid).unwrap_or_default()
}
pub fn arch(&self) -> Arch {
use goblin::mach::constants::cputype;
match (self.macho.header.cputype(), self.macho.header.cpusubtype()) {
(cputype::CPU_TYPE_I386, cputype::CPU_SUBTYPE_I386_ALL) => Arch::X86,
(cputype::CPU_TYPE_I386, _) => Arch::X86Unknown,
(cputype::CPU_TYPE_X86_64, cputype::CPU_SUBTYPE_X86_64_ALL) => Arch::Amd64,
(cputype::CPU_TYPE_X86_64, cputype::CPU_SUBTYPE_X86_64_H) => Arch::Amd64h,
(cputype::CPU_TYPE_X86_64, _) => Arch::Amd64Unknown,
(cputype::CPU_TYPE_ARM64, cputype::CPU_SUBTYPE_ARM64_ALL) => Arch::Arm64,
(cputype::CPU_TYPE_ARM64, cputype::CPU_SUBTYPE_ARM64_V8) => Arch::Arm64V8,
(cputype::CPU_TYPE_ARM64, cputype::CPU_SUBTYPE_ARM64_E) => Arch::Arm64e,
(cputype::CPU_TYPE_ARM64, _) => Arch::Arm64Unknown,
(cputype::CPU_TYPE_ARM64_32, cputype::CPU_SUBTYPE_ARM64_32_ALL) => Arch::Arm64_32,
(cputype::CPU_TYPE_ARM64_32, cputype::CPU_SUBTYPE_ARM64_32_V8) => Arch::Arm64_32V8,
(cputype::CPU_TYPE_ARM64_32, _) => Arch::Arm64_32Unknown,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_ALL) => Arch::Arm,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V5TEJ) => Arch::ArmV5,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V6) => Arch::ArmV6,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V6M) => Arch::ArmV6m,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V7) => Arch::ArmV7,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V7F) => Arch::ArmV7f,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V7S) => Arch::ArmV7s,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V7K) => Arch::ArmV7k,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V7M) => Arch::ArmV7m,
(cputype::CPU_TYPE_ARM, cputype::CPU_SUBTYPE_ARM_V7EM) => Arch::ArmV7em,
(cputype::CPU_TYPE_ARM, _) => Arch::ArmUnknown,
(cputype::CPU_TYPE_POWERPC, cputype::CPU_SUBTYPE_POWERPC_ALL) => Arch::Ppc,
(cputype::CPU_TYPE_POWERPC64, cputype::CPU_SUBTYPE_POWERPC_ALL) => Arch::Ppc64,
(_, _) => Arch::Unknown,
}
}
pub fn kind(&self) -> ObjectKind {
match self.macho.header.filetype {
goblin::mach::header::MH_OBJECT => ObjectKind::Relocatable,
goblin::mach::header::MH_EXECUTE => ObjectKind::Executable,
goblin::mach::header::MH_FVMLIB => ObjectKind::Library,
goblin::mach::header::MH_CORE => ObjectKind::Dump,
goblin::mach::header::MH_PRELOAD => ObjectKind::Executable,
goblin::mach::header::MH_DYLIB => ObjectKind::Library,
goblin::mach::header::MH_DYLINKER => ObjectKind::Executable,
goblin::mach::header::MH_BUNDLE => ObjectKind::Library,
goblin::mach::header::MH_DSYM => ObjectKind::Debug,
goblin::mach::header::MH_KEXT_BUNDLE => ObjectKind::Library,
_ => ObjectKind::Other,
}
}
pub fn load_address(&self) -> u64 {
for seg in &self.macho.segments {
if seg.name().map(|name| name == "__TEXT").unwrap_or(false) {
return seg.vmaddr;
}
}
0
}
pub fn has_symbols(&self) -> bool {
self.macho.symbols.is_some()
}
pub fn symbols(&self) -> MachOSymbolIterator<'d> {
let mut sections = SmallVec::new();
let mut section_index = 0;
'outer: for segment in &self.macho.segments {
if segment.name().ok() != Some("__TEXT") {
section_index += segment.nsects as usize;
continue;
}
for result in segment {
let section = match result {
Ok((section, _data)) => section,
Err(_) => break 'outer,
};
match section.name() {
Ok("__text") | Ok("__stubs") => sections.push(section_index),
_ => (),
}
section_index += 1;
}
}
MachOSymbolIterator {
symbols: self.macho.symbols(),
sections,
vmaddr: self.load_address(),
symbolmap: self.bcsymbolmap.clone(),
}
}
pub fn symbol_map(&self) -> SymbolMap<'d> {
self.symbols().collect()
}
pub fn has_debug_info(&self) -> bool {
self.has_section("debug_info")
}
pub fn debug_session(&self) -> Result<DwarfDebugSession<'d>, DwarfError> {
let symbols = self.symbol_map();
let mut session =
DwarfDebugSession::parse(self, symbols, self.load_address() as i64, self.kind())?;
session.load_symbolmap(self.bcsymbolmap.clone());
Ok(session)
}
pub fn has_unwind_info(&self) -> bool {
self.has_section("eh_frame")
|| self.has_section("debug_frame")
|| self.has_section("unwind_info")
}
pub fn has_sources(&self) -> bool {
false
}
pub fn is_malformed(&self) -> bool {
false
}
pub fn data(&self) -> &'d [u8] {
self.data
}
pub fn requires_symbolmap(&self) -> bool {
self.symbols().any(|s| {
s.name()
.map_or(false, |n| n.starts_with(SWIFT_HIDDEN_PREFIX))
})
}
}
impl fmt::Debug for MachObject<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MachObject")
.field("code_id", &self.code_id())
.field("debug_id", &self.debug_id())
.field("arch", &self.arch())
.field("kind", &self.kind())
.field("load_address", &format_args!("{:#x}", self.load_address()))
.field("has_symbols", &self.has_symbols())
.field("has_debug_info", &self.has_debug_info())
.field("has_unwind_info", &self.has_unwind_info())
.field("is_malformed", &self.is_malformed())
.finish()
}
}
impl<'slf, 'd: 'slf> AsSelf<'slf> for MachObject<'d> {
type Ref = MachObject<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
impl<'d> Parse<'d> for MachObject<'d> {
type Error = MachError;
fn test(data: &[u8]) -> bool {
Self::test(data)
}
fn parse(data: &'d [u8]) -> Result<Self, MachError> {
Self::parse(data)
}
}
impl<'data: 'object, 'object> ObjectLike<'data, 'object> for MachObject<'data> {
type Error = DwarfError;
type Session = DwarfDebugSession<'data>;
type SymbolIterator = MachOSymbolIterator<'data>;
fn file_format(&self) -> FileFormat {
self.file_format()
}
fn code_id(&self) -> Option<CodeId> {
self.code_id()
}
fn debug_id(&self) -> DebugId {
self.debug_id()
}
fn arch(&self) -> Arch {
self.arch()
}
fn kind(&self) -> ObjectKind {
self.kind()
}
fn load_address(&self) -> u64 {
self.load_address()
}
fn has_symbols(&self) -> bool {
self.has_symbols()
}
fn symbols(&self) -> Self::SymbolIterator {
self.symbols()
}
fn symbol_map(&self) -> SymbolMap<'data> {
self.symbol_map()
}
fn has_debug_info(&self) -> bool {
self.has_debug_info()
}
fn debug_session(&self) -> Result<Self::Session, Self::Error> {
self.debug_session()
}
fn has_unwind_info(&self) -> bool {
self.has_unwind_info()
}
fn has_sources(&self) -> bool {
self.has_sources()
}
fn is_malformed(&self) -> bool {
self.is_malformed()
}
}
impl<'data> Dwarf<'data> for MachObject<'data> {
fn endianity(&self) -> Endian {
if self.macho.little_endian {
Endian::Little
} else {
Endian::Big
}
}
fn raw_section(&self, section_name: &str) -> Option<DwarfSection<'data>> {
for segment in &self.macho.segments {
for section in segment.into_iter() {
let (header, data) = section.ok()?;
if let Ok(sec) = header.name() {
if sec.starts_with("__") && &sec[2..] == section_name {
if header.offset == 0 {
return None;
}
return Some(DwarfSection {
data: Cow::Borrowed(data),
address: header.addr,
offset: u64::from(header.offset),
align: u64::from(header.align),
});
}
}
}
}
None
}
}
pub struct MachOSymbolIterator<'data> {
symbols: mach::symbols::SymbolIterator<'data>,
sections: SmallVec<[usize; 2]>,
vmaddr: u64,
symbolmap: Option<Arc<BcSymbolMap<'data>>>,
}
impl<'data> Iterator for MachOSymbolIterator<'data> {
type Item = Symbol<'data>;
fn next(&mut self) -> Option<Self::Item> {
for next in &mut self.symbols {
let (mut name, nlist) = next.ok()?;
if nlist.n_value < self.vmaddr {
continue;
}
let in_valid_section = !nlist.is_stab()
&& nlist.get_type() == mach::symbols::N_SECT
&& nlist.n_sect != (mach::symbols::NO_SECT as usize)
&& self.sections.contains(&(nlist.n_sect - 1));
if !in_valid_section {
continue;
}
if let Some(symbolmap) = self.symbolmap.as_ref() {
name = symbolmap.resolve(name);
}
if let Some(tail) = name.strip_prefix('_') {
if !name.starts_with(SWIFT_HIDDEN_PREFIX) {
name = tail;
}
}
return Some(Symbol {
name: Some(Cow::Borrowed(name)),
address: nlist.n_value - self.vmaddr,
size: 0, });
}
None
}
}
pub struct FatMachObjectIterator<'d, 'a> {
iter: mach::FatArchIterator<'a>,
remaining: usize,
data: &'d [u8],
}
impl<'d, 'a> Iterator for FatMachObjectIterator<'d, 'a> {
type Item = Result<MachObject<'d>, MachError>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
self.remaining -= 1;
match self.iter.next() {
Some(Ok(arch)) => {
let start = (arch.offset as usize).min(self.data.len());
let end = (arch.offset as usize + arch.size as usize).min(self.data.len());
Some(MachObject::parse(&self.data[start..end]))
}
Some(Err(error)) => Some(Err(MachError::new(error))),
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl std::iter::FusedIterator for FatMachObjectIterator<'_, '_> {}
impl ExactSizeIterator for FatMachObjectIterator<'_, '_> {}
pub struct FatMachO<'d> {
fat: mach::MultiArch<'d>,
data: &'d [u8],
}
impl<'d> FatMachO<'d> {
pub fn test(data: &[u8]) -> bool {
matches!(MachArchive::is_fat(data), Some(true))
}
pub fn parse(data: &'d [u8]) -> Result<Self, MachError> {
mach::MultiArch::new(data)
.map(|fat| FatMachO { fat, data })
.map_err(MachError::new)
}
pub fn objects(&self) -> FatMachObjectIterator<'d, '_> {
FatMachObjectIterator {
iter: self.fat.iter_arches(),
remaining: self.fat.narches,
data: self.data,
}
}
pub fn object_count(&self) -> usize {
self.fat.narches
}
pub fn object_by_index(&self, index: usize) -> Result<Option<MachObject<'d>>, MachError> {
let arch = match self.fat.iter_arches().nth(index) {
Some(arch) => arch.map_err(MachError::new)?,
None => return Ok(None),
};
let start = (arch.offset as usize).min(self.data.len());
let end = (arch.offset as usize + arch.size as usize).min(self.data.len());
MachObject::parse(&self.data[start..end]).map(Some)
}
}
impl fmt::Debug for FatMachO<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FatMachO").field("fat", &self.fat).finish()
}
}
impl<'slf, 'd: 'slf> AsSelf<'slf> for FatMachO<'d> {
type Ref = FatMachO<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
#[allow(clippy::large_enum_variant)]
enum MachObjectIteratorInner<'d, 'a> {
Single(MonoArchiveObjects<'d, MachObject<'d>>),
Archive(FatMachObjectIterator<'d, 'a>),
}
pub struct MachObjectIterator<'d, 'a>(MachObjectIteratorInner<'d, 'a>);
impl<'d, 'a> Iterator for MachObjectIterator<'d, 'a> {
type Item = Result<MachObject<'d>, MachError>;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
MachObjectIteratorInner::Single(ref mut iter) => iter.next(),
MachObjectIteratorInner::Archive(ref mut iter) => iter.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0 {
MachObjectIteratorInner::Single(ref iter) => iter.size_hint(),
MachObjectIteratorInner::Archive(ref iter) => iter.size_hint(),
}
}
}
impl std::iter::FusedIterator for MachObjectIterator<'_, '_> {}
impl ExactSizeIterator for MachObjectIterator<'_, '_> {}
#[derive(Debug)]
enum MachArchiveInner<'d> {
Single(MonoArchive<'d, MachObject<'d>>),
Archive(FatMachO<'d>),
}
#[derive(Debug)]
pub struct MachArchive<'d>(MachArchiveInner<'d>);
impl<'d> MachArchive<'d> {
pub fn test(data: &[u8]) -> bool {
Self::is_fat(data).is_some()
}
fn is_fat(data: &[u8]) -> Option<bool> {
let (magic, _maybe_ctx) = goblin::mach::parse_magic_and_ctx(data, 0).ok()?;
match magic {
goblin::mach::fat::FAT_MAGIC => {
use scroll::Pread;
let narches = data.pread_with::<u32>(4, scroll::BE).ok()?;
if narches < 45 {
Some(true)
} else {
None
}
}
goblin::mach::header::MH_CIGAM_64
| goblin::mach::header::MH_CIGAM
| goblin::mach::header::MH_MAGIC_64
| goblin::mach::header::MH_MAGIC => Some(false),
_ => None,
}
}
pub fn parse(data: &'d [u8]) -> Result<Self, MachError> {
Ok(Self(match Self::is_fat(data) {
Some(true) => MachArchiveInner::Archive(FatMachO::parse(data)?),
_ => MachArchiveInner::Single(MonoArchive::new(data)),
}))
}
pub fn objects(&self) -> MachObjectIterator<'d, '_> {
MachObjectIterator(match self.0 {
MachArchiveInner::Single(ref inner) => MachObjectIteratorInner::Single(inner.objects()),
MachArchiveInner::Archive(ref inner) => {
MachObjectIteratorInner::Archive(inner.objects())
}
})
}
pub fn object_count(&self) -> usize {
match self.0 {
MachArchiveInner::Single(ref inner) => inner.object_count(),
MachArchiveInner::Archive(ref inner) => inner.object_count(),
}
}
pub fn object_by_index(&self, index: usize) -> Result<Option<MachObject<'d>>, MachError> {
match self.0 {
MachArchiveInner::Single(ref inner) => inner.object_by_index(index),
MachArchiveInner::Archive(ref inner) => inner.object_by_index(index),
}
}
pub fn is_multi(&self) -> bool {
match self.0 {
MachArchiveInner::Archive(_) => true,
MachArchiveInner::Single(_) => false,
}
}
}
impl<'slf, 'd: 'slf> AsSelf<'slf> for MachArchive<'d> {
type Ref = MachArchive<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bcsymbolmap() {
let object_data =
std::fs::read("tests/fixtures/2d10c42f-591d-3265-b147-78ba0868073f.dwarf-hidden")
.unwrap();
let mut object = MachObject::parse(&object_data).unwrap();
let mut symbols = object.symbols();
let symbol = symbols.next().unwrap();
assert_eq!(symbol.name.unwrap(), "__hidden#0_");
let session = object.debug_session().unwrap();
let mut files = session.files();
let file = files.next().unwrap().unwrap();
assert_eq!(&file.path_str(), "__hidden#41_/__hidden#42_");
assert_eq!(
&file.abs_path_str(),
"__hidden#41_/__hidden#41_/__hidden#42_"
);
let mut functions = session.functions();
let function = functions.next().unwrap().unwrap();
assert_eq!(&function.name, "__hidden#0_");
assert_eq!(&function.compilation_dir, b"__hidden#41_");
assert_eq!(
&function.lines[0].file.path_str(),
"__hidden#41_/__hidden#42_"
);
let fn_with_inlinees = functions
.filter_map(|f| f.ok())
.find(|f| !f.inlinees.is_empty())
.unwrap();
let inlinee = fn_with_inlinees.inlinees.first().unwrap();
assert_eq!(&inlinee.name, "__hidden#146_");
let bc_symbol_map_data =
std::fs::read("tests/fixtures/c8374b6d-6e96-34d8-ae38-efaa5fec424f.bcsymbolmap")
.unwrap();
let bc_symbol_map = BcSymbolMap::parse(&bc_symbol_map_data).unwrap();
object.load_symbolmap(bc_symbol_map);
let mut symbols = object.symbols();
let symbol = symbols.next().unwrap();
assert_eq!(symbol.name.unwrap(), "-[SentryMessage initWithFormatted:]");
let symbol = symbols.next().unwrap();
assert_eq!(symbol.name.unwrap(), "-[SentryMessage setMessage:]");
let session = object.debug_session().unwrap();
let mut files = session.files();
let file = files.next().unwrap().unwrap();
assert_eq!(
&file.path_str(),
"/Users/philipphofmann/git-repos/sentry-cocoa/Sources/Sentry/SentryMessage.m"
);
assert_eq!(
&file.abs_path_str(),
"/Users/philipphofmann/git-repos/sentry-cocoa/Sources/Sentry/SentryMessage.m"
);
let mut functions = session.functions();
let function = functions.next().unwrap().unwrap();
assert_eq!(&function.name, "-[SentryMessage initWithFormatted:]");
assert_eq!(
&function.compilation_dir,
b"/Users/philipphofmann/git-repos/sentry-cocoa"
);
assert_eq!(
&function.lines[0].file.path_str(),
"/Users/philipphofmann/git-repos/sentry-cocoa/Sources/Sentry/SentryMessage.m"
);
let fn_with_inlinees = functions
.filter_map(|f| f.ok())
.find(|f| !f.inlinees.is_empty())
.unwrap();
let inlinee = fn_with_inlinees.inlinees.first().unwrap();
assert_eq!(&inlinee.name, "prepareReportWriter");
}
#[test]
fn test_overflow_multiarch() {
let data = [
0xbe, 0xba, 0xfe, 0xca, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, ];
let fat = FatMachO::parse(&data).unwrap();
let obj = fat.object_by_index(0);
assert!(obj.is_err());
let mut iter = fat.objects();
assert!(iter.next().unwrap().is_err());
}
#[test]
fn test_section_access() {
let data = [
0xfe, 0xed, 0xfa, 0xcf, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0xed, 0xfa, 0xce, 0x6f, 0x73,
0x6f, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x40, 0x20, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x4d, 0xc2, 0xc2, 0xc2, 0xc2,
0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xca, 0x7a, 0xfe, 0xba, 0xbe, 0x0, 0x0, 0x0, 0x20,
0x43, 0x2f, 0x0, 0x32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x4d, 0x4f,
0x44, 0x55, 0x4c, 0x40, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x2a, 0x78, 0x6e, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2,
0xc2, 0xc2, 0xc2, 0xc2, 0xc6, 0xd5, 0xc2, 0xc2, 0x1f, 0x1f,
];
let obj = MachObject::parse(&data).unwrap();
assert!(!obj.has_debug_info());
}
#[test]
fn test_invalid_symbols() {
let data = std::fs::read("tests/fixtures/invalid-symbols.fuzzed").unwrap();
let obj = MachObject::parse(&data).unwrap();
let _ = obj.symbol_map();
}
}