323 lines
10 KiB
Rust
323 lines
10 KiB
Rust
use std::io::{Result, Write};
|
|
|
|
use object::{Endianness, Object, ObjectComdat, ObjectSection, ObjectSymbol};
|
|
use object::read::archive::ArchiveFile;
|
|
use object::read::macho::{DyldCache, FatArch, FatHeader};
|
|
|
|
use std::{env, fs, io, process};
|
|
|
|
fn main() {
|
|
let mut args = env::args();
|
|
let cmd = args.next().unwrap();
|
|
if args.len() == 0 {
|
|
eprintln!("Usage: {} <file> [<member>...]", cmd);
|
|
process::exit(1);
|
|
}
|
|
let file_path = args.next().unwrap();
|
|
let member_names: Vec<_> = args.collect();
|
|
|
|
let file = match fs::File::open(&file_path) {
|
|
Ok(file) => file,
|
|
Err(err) => {
|
|
eprintln!("Failed to open file '{}': {}", file_path, err,);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
let extra_files = open_subcaches_if_exist(&file_path);
|
|
let file = match unsafe { memmap2::Mmap::map(&file) } {
|
|
Ok(mmap) => mmap,
|
|
Err(err) => {
|
|
eprintln!("Failed to map file '{}': {}", file_path, err,);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
let extra_files: Vec<_> = extra_files
|
|
.into_iter()
|
|
.map(
|
|
|subcache_file| match unsafe { memmap2::Mmap::map(&subcache_file) } {
|
|
Ok(mmap) => mmap,
|
|
Err(err) => {
|
|
eprintln!("Failed to map file '{}': {}", file_path, err,);
|
|
process::exit(1);
|
|
}
|
|
},
|
|
)
|
|
.collect();
|
|
let extra_file_data: Vec<&[u8]> = extra_files.iter().map(|f| &**f).collect();
|
|
|
|
let stdout = io::stdout();
|
|
let stderr = io::stderr();
|
|
print(
|
|
&mut stdout.lock(),
|
|
&mut stderr.lock(),
|
|
&*file,
|
|
&extra_file_data,
|
|
member_names,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// If the file is a dyld shared cache, and we're on macOS 12 or later,
|
|
// then there will be one or more "subcache" files next to this file,
|
|
// with the names filename.1, filename.2 etc.
|
|
// Read those files now, if they exist, even if we don't know that
|
|
// we're dealing with a dyld shared cache. By the time we know what
|
|
// we're dealing with, it's too late to read more files.
|
|
fn open_subcaches_if_exist(path: &str) -> Vec<fs::File> {
|
|
let mut files = Vec::new();
|
|
for i in 1.. {
|
|
let subcache_path = format!("{}.{}", path, i);
|
|
match fs::File::open(&subcache_path) {
|
|
Ok(subcache_file) => files.push(subcache_file),
|
|
Err(_) => break,
|
|
};
|
|
}
|
|
let symbols_subcache_path = format!("{}.symbols", path);
|
|
if let Ok(subcache_file) = fs::File::open(&symbols_subcache_path) {
|
|
files.push(subcache_file);
|
|
};
|
|
files
|
|
}
|
|
|
|
pub fn print<W: Write, E: Write>(
|
|
w: &mut W,
|
|
e: &mut E,
|
|
file: &[u8],
|
|
extra_files: &[&[u8]],
|
|
member_names: Vec<String>,
|
|
) -> Result<()> {
|
|
let mut member_names: Vec<_> = member_names.into_iter().map(|name| (name, false)).collect();
|
|
|
|
if let Ok(archive) = ArchiveFile::parse(file) {
|
|
writeln!(w, "Format: Archive (kind: {:?})", archive.kind())?;
|
|
for member in archive.members() {
|
|
match member {
|
|
Ok(member) => {
|
|
if find_member(&mut member_names, member.name()) {
|
|
writeln!(w)?;
|
|
writeln!(w, "{}:", String::from_utf8_lossy(member.name()))?;
|
|
if let Ok(data) = member.data(file) {
|
|
dump_object(w, e, data)?;
|
|
}
|
|
}
|
|
}
|
|
Err(err) => writeln!(e, "Failed to parse archive member: {}", err)?,
|
|
}
|
|
}
|
|
} else if let Ok(arches) = FatHeader::parse_arch32(file) {
|
|
writeln!(w, "Format: Mach-O Fat 32")?;
|
|
for arch in arches {
|
|
writeln!(w)?;
|
|
writeln!(w, "Fat Arch: {:?}", arch.architecture())?;
|
|
match arch.data(file) {
|
|
Ok(data) => dump_object(w, e, data)?,
|
|
Err(err) => writeln!(e, "Failed to parse Fat 32 data: {}", err)?,
|
|
}
|
|
}
|
|
} else if let Ok(arches) = FatHeader::parse_arch64(file) {
|
|
writeln!(w, "Format: Mach-O Fat 64")?;
|
|
for arch in arches {
|
|
writeln!(w)?;
|
|
writeln!(w, "Fat Arch: {:?}", arch.architecture())?;
|
|
match arch.data(file) {
|
|
Ok(data) => dump_object(w, e, data)?,
|
|
Err(err) => writeln!(e, "Failed to parse Fat 64 data: {}", err)?,
|
|
}
|
|
}
|
|
} else if let Ok(cache) = DyldCache::<Endianness>::parse(file, extra_files) {
|
|
writeln!(w, "Format: dyld cache {:?}-endian", cache.endianness())?;
|
|
writeln!(w, "Architecture: {:?}", cache.architecture())?;
|
|
for image in cache.images() {
|
|
let path = match image.path() {
|
|
Ok(path) => path,
|
|
Err(err) => {
|
|
writeln!(e, "Failed to parse dydld image name: {}", err)?;
|
|
continue;
|
|
}
|
|
};
|
|
if !find_member(&mut member_names, path.as_bytes()) {
|
|
continue;
|
|
}
|
|
writeln!(w)?;
|
|
writeln!(w, "{}:", path)?;
|
|
let file = match image.parse_object() {
|
|
Ok(file) => file,
|
|
Err(err) => {
|
|
writeln!(e, "Failed to parse file: {}", err)?;
|
|
continue;
|
|
}
|
|
};
|
|
dump_parsed_object(w, e, &file)?;
|
|
}
|
|
} else {
|
|
dump_object(w, e, file)?;
|
|
}
|
|
|
|
for (name, found) in member_names {
|
|
if !found {
|
|
writeln!(e, "Failed to find member '{}", name)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn find_member(member_names: &mut [(String, bool)], name: &[u8]) -> bool {
|
|
if member_names.is_empty() {
|
|
return true;
|
|
}
|
|
match member_names.iter().position(|x| x.0.as_bytes() == name) {
|
|
Some(i) => {
|
|
member_names[i].1 = true;
|
|
true
|
|
}
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
fn dump_object<W: Write, E: Write>(w: &mut W, e: &mut E, data: &[u8]) -> Result<()> {
|
|
match object::File::parse(data) {
|
|
Ok(file) => {
|
|
dump_parsed_object(w, e, &file)?;
|
|
}
|
|
Err(err) => {
|
|
writeln!(e, "Failed to parse file: {}", err)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn dump_parsed_object<W: Write, E: Write>(w: &mut W, e: &mut E, file: &object::File) -> Result<()> {
|
|
writeln!(
|
|
w,
|
|
"Format: {:?} {:?}-endian {}-bit",
|
|
file.format(),
|
|
file.endianness(),
|
|
if file.is_64() { "64" } else { "32" }
|
|
)?;
|
|
writeln!(w, "Kind: {:?}", file.kind())?;
|
|
writeln!(w, "Architecture: {:?}", file.architecture())?;
|
|
writeln!(w, "Flags: {:x?}", file.flags())?;
|
|
writeln!(
|
|
w,
|
|
"Relative Address Base: {:x?}",
|
|
file.relative_address_base()
|
|
)?;
|
|
writeln!(w, "Entry Address: {:x?}", file.entry())?;
|
|
|
|
match file.mach_uuid() {
|
|
Ok(Some(uuid)) => writeln!(w, "Mach UUID: {:x?}", uuid)?,
|
|
Ok(None) => {}
|
|
Err(err) => writeln!(e, "Failed to parse Mach UUID: {}", err)?,
|
|
}
|
|
match file.build_id() {
|
|
Ok(Some(build_id)) => writeln!(w, "Build ID: {:x?}", build_id)?,
|
|
Ok(None) => {}
|
|
Err(err) => writeln!(e, "Failed to parse build ID: {}", err)?,
|
|
}
|
|
match file.gnu_debuglink() {
|
|
Ok(Some((filename, crc))) => writeln!(
|
|
w,
|
|
"GNU debug link: {} CRC: {:08x}",
|
|
String::from_utf8_lossy(filename),
|
|
crc,
|
|
)?,
|
|
Ok(None) => {}
|
|
Err(err) => writeln!(e, "Failed to parse GNU debug link: {}", err)?,
|
|
}
|
|
match file.gnu_debugaltlink() {
|
|
Ok(Some((filename, build_id))) => writeln!(
|
|
w,
|
|
"GNU debug alt link: {}, build ID: {:x?}",
|
|
String::from_utf8_lossy(filename),
|
|
build_id,
|
|
)?,
|
|
Ok(None) => {}
|
|
Err(err) => writeln!(e, "Failed to parse GNU debug alt link: {}", err)?,
|
|
}
|
|
match file.pdb_info() {
|
|
Ok(Some(info)) => writeln!(
|
|
w,
|
|
"PDB file: {}, GUID: {:x?}, Age: {}",
|
|
String::from_utf8_lossy(info.path()),
|
|
info.guid(),
|
|
info.age()
|
|
)?,
|
|
Ok(None) => {}
|
|
Err(err) => writeln!(e, "Failed to parse PE CodeView info: {}", err)?,
|
|
}
|
|
|
|
for segment in file.segments() {
|
|
writeln!(w, "{:x?}", segment)?;
|
|
}
|
|
|
|
for section in file.sections() {
|
|
writeln!(w, "{}: {:x?}", section.index().0, section)?;
|
|
}
|
|
|
|
for comdat in file.comdats() {
|
|
write!(w, "{:?} Sections:", comdat)?;
|
|
for section in comdat.sections() {
|
|
write!(w, " {}", section.0)?;
|
|
}
|
|
writeln!(w)?;
|
|
}
|
|
|
|
writeln!(w)?;
|
|
writeln!(w, "Symbols")?;
|
|
for symbol in file.symbols() {
|
|
writeln!(w, "{}: {:x?}", symbol.index().0, symbol)?;
|
|
}
|
|
|
|
for section in file.sections() {
|
|
if section.relocations().next().is_some() {
|
|
writeln!(
|
|
w,
|
|
"\n{} relocations",
|
|
section.name().unwrap_or("<invalid name>")
|
|
)?;
|
|
for relocation in section.relocations() {
|
|
writeln!(w, "{:x?}", relocation)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
writeln!(w)?;
|
|
writeln!(w, "Dynamic symbols")?;
|
|
for symbol in file.dynamic_symbols() {
|
|
writeln!(w, "{}: {:x?}", symbol.index().0, symbol)?;
|
|
}
|
|
|
|
if let Some(relocations) = file.dynamic_relocations() {
|
|
writeln!(w)?;
|
|
writeln!(w, "Dynamic relocations")?;
|
|
for relocation in relocations {
|
|
writeln!(w, "{:x?}", relocation)?;
|
|
}
|
|
}
|
|
|
|
match file.imports() {
|
|
Ok(imports) => {
|
|
if !imports.is_empty() {
|
|
writeln!(w)?;
|
|
for import in imports {
|
|
writeln!(w, "{:x?}", import)?;
|
|
}
|
|
}
|
|
}
|
|
Err(err) => writeln!(e, "Failed to parse imports: {}", err)?,
|
|
}
|
|
|
|
match file.exports() {
|
|
Ok(exports) => {
|
|
if !exports.is_empty() {
|
|
writeln!(w)?;
|
|
for export in exports {
|
|
writeln!(w, "{:x?}", export)?;
|
|
}
|
|
}
|
|
}
|
|
Err(err) => writeln!(e, "Failed to parse exports: {}", err)?,
|
|
}
|
|
|
|
Ok(())
|
|
} |