From e4638717fc289973f2b4942da0196cb7e1780775 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 1 Oct 2022 18:03:24 +0800 Subject: [PATCH 1/8] Add macho support --- src/elf.rs | 21 ++++++++++++ src/lib.rs | 85 ++++++++++++++++++++--------------------------- src/macho.rs | 17 ++++++++++ tests/test.macho | Bin 0 -> 16833 bytes 4 files changed, 74 insertions(+), 49 deletions(-) create mode 100644 src/elf.rs create mode 100644 src/macho.rs create mode 100755 tests/test.macho diff --git a/src/elf.rs b/src/elf.rs new file mode 100644 index 0000000..6e7ef3e --- /dev/null +++ b/src/elf.rs @@ -0,0 +1,21 @@ +use goblin::elf::Elf; + +use crate::InspectDylib; + +impl InspectDylib for Elf<'_> { + fn rpaths(&self) -> &[&str] { + if !self.runpaths.is_empty() { + &self.runpaths + } else { + &self.rpaths + } + } + + fn libraries(&self) -> &[&str] { + &self.libraries + } + + fn interpreter(&self) -> Option<&str> { + self.interpreter.clone() + } +} diff --git a/src/lib.rs b/src/lib.rs index 91e14ff..28531c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,10 @@ use goblin::elf::{ Elf, }; +mod elf; mod errors; pub mod ld_so_conf; +mod macho; pub use errors::Error; use ld_so_conf::parse_ld_so_conf; @@ -29,10 +31,8 @@ pub struct Library { pub realpath: Option, /// The dependencies of this library. pub needed: Vec, - /// Runtime library search paths. (deprecated) - pub rpath: Vec, /// Runtime library search paths. - pub runpath: Vec, + pub rpath: Vec, } impl Library { @@ -51,10 +51,17 @@ pub struct DependencyTree { pub needed: Vec, /// All of this binary’s dynamic libraries it uses in detail. pub libraries: HashMap, - /// Runtime library search paths. (deprecated) + /// Runtime library search paths. pub rpath: Vec, +} + +trait InspectDylib { /// Runtime library search paths. - pub runpath: Vec, + fn rpaths(&self) -> &[&str]; + /// A list of this binary’s dynamic libraries it depends on directly. + fn libraries(&self) -> &[&str]; + /// The binary’s program interpreter (e.g., dynamic linker). + fn interpreter(&self) -> Option<&str>; } /// Library dependency analyzer @@ -63,7 +70,7 @@ pub struct DependencyAnalyzer { env_ld_paths: Vec, conf_ld_paths: Vec, additional_ld_paths: Vec, - runpaths: Vec, + rpaths: Vec, root: PathBuf, } @@ -80,7 +87,7 @@ impl DependencyAnalyzer { env_ld_paths: Vec::new(), conf_ld_paths: Vec::new(), additional_ld_paths: Vec::new(), - runpaths: Vec::new(), + rpaths: Vec::new(), root, } } @@ -103,24 +110,14 @@ impl DependencyAnalyzer { self } - fn read_rpath_runpath( - &self, - elf: &Elf, - path: &Path, - ) -> Result<(Vec, Vec), Error> { + fn read_rpath(&self, lib: &impl InspectDylib, path: &Path) -> Result, Error> { let mut rpaths = Vec::new(); - let mut runpaths = Vec::new(); - for runpath in &elf.runpaths { - if let Ok(ld_paths) = self.parse_ld_paths(runpath, path) { - runpaths = ld_paths; - } - } - for rpath in &elf.rpaths { + for rpath in lib.rpaths() { if let Ok(ld_paths) = self.parse_ld_paths(rpath, path) { rpaths = ld_paths; } } - Ok((rpaths, runpaths)) + Ok(rpaths) } /// Analyze the given binary. @@ -131,15 +128,9 @@ impl DependencyAnalyzer { let bytes = fs::read(path)?; let elf = Elf::parse(&bytes)?; - let (mut rpaths, runpaths) = self.read_rpath_runpath(&elf, path)?; - if !runpaths.is_empty() { - // If both RPATH and RUNPATH are set, only the latter is used. - rpaths = Vec::new(); - } - self.runpaths = runpaths.clone(); - self.runpaths.extend(rpaths.clone()); + let rpaths = self.read_rpath(&elf, path)?; - let needed: Vec = elf.libraries.iter().map(ToString::to_string).collect(); + let needed: Vec = elf.libraries().iter().map(ToString::to_string).collect(); let mut libraries = HashMap::new(); let mut stack = needed.clone(); @@ -152,7 +143,7 @@ impl DependencyAnalyzer { stack.extend(library.needed); } - let interpreter = elf.interpreter.map(|interp| interp.to_string()); + let interpreter = elf.interpreter().map(|interp| interp.to_string()); if let Some(ref interp) = interpreter { if !libraries.contains_key(interp) { let interp_path = self.root.join(interp.strip_prefix('/').unwrap_or(interp)); @@ -169,7 +160,6 @@ impl DependencyAnalyzer { realpath: fs::canonicalize(PathBuf::from(interp)).ok(), needed: Vec::new(), rpath: Vec::new(), - runpath: Vec::new(), }, ); } @@ -179,22 +169,21 @@ impl DependencyAnalyzer { needed, libraries, rpath: rpaths, - runpath: runpaths, }; Ok(dep_tree) } /// Parse the colon-delimited list of paths and apply ldso rules - fn parse_ld_paths(&self, ld_path: &str, elf_path: &Path) -> Result, Error> { + fn parse_ld_paths(&self, ld_path: &str, dylib_path: &Path) -> Result, Error> { let mut paths = Vec::new(); for path in ld_path.split(':') { let normpath = if path.is_empty() { // The ldso treats empty paths as the current directory env::current_dir() } else if path.contains("$ORIGIN") || path.contains("${ORIGIN}") { - let elf_path = fs::canonicalize(elf_path)?; - let elf_dir = elf_path.parent().expect("no parent"); - let replacement = elf_dir.to_str().unwrap(); + let dylib_path = fs::canonicalize(dylib_path)?; + let dylib_dir = dylib_path.parent().expect("no parent"); + let replacement = dylib_dir.to_str().unwrap(); let path = path .replace("${ORIGIN}", replacement) .replace("$ORIGIN", replacement); @@ -209,11 +198,11 @@ impl DependencyAnalyzer { Ok(paths) } - fn load_ld_paths(&mut self, elf_path: &Path) -> Result<(), Error> { + fn load_ld_paths(&mut self, dylib_path: &Path) -> Result<(), Error> { #[cfg(unix)] if let Ok(env_ld_path) = env::var("LD_LIBRARY_PATH") { if self.root == Path::new("/") { - self.env_ld_paths = self.parse_ld_paths(&env_ld_path, elf_path)?; + self.env_ld_paths = self.parse_ld_paths(&env_ld_path, dylib_path)?; } } // Load all the paths from a ldso config file @@ -261,50 +250,48 @@ impl DependencyAnalyzer { Ok(()) } - /// Try to locate a `lib` that is compatible to `elf` - fn find_library(&self, elf: &Elf, lib: &str) -> Result { + /// Try to locate a `lib_name` that is compatible to `dylib` + fn find_library(&self, dylib: &Elf, lib_name: &str) -> Result { for lib_path in self - .runpaths + .rpaths .iter() .chain(self.env_ld_paths.iter()) .chain(self.conf_ld_paths.iter()) .map(|ld_path| { self.root .join(ld_path.strip_prefix('/').unwrap_or(ld_path)) - .join(lib) + .join(lib_name) }) .chain( self.additional_ld_paths .iter() - .map(|ld_path| ld_path.join(lib)), + .map(|ld_path| ld_path.join(lib_name)), ) { // FIXME: readlink to get real path if lib_path.exists() { let bytes = fs::read(&lib_path)?; if let Ok(lib_elf) = Elf::parse(&bytes) { - if compatible_elfs(elf, &lib_elf) { + if compatible_elfs(dylib, &lib_elf) { let needed = lib_elf.libraries.iter().map(ToString::to_string).collect(); - let (rpath, runpath) = self.read_rpath_runpath(&lib_elf, &lib_path)?; + let rpath = self.read_rpath(&lib_elf, &lib_path)?; return Ok(Library { - name: lib.to_string(), + name: lib_name.to_string(), path: lib_path.to_path_buf(), realpath: fs::canonicalize(lib_path).ok(), needed, rpath, - runpath, }); } } } } Ok(Library { - name: lib.to_string(), - path: PathBuf::from(lib), + name: lib_name.to_string(), + path: PathBuf::from(lib_name), realpath: None, needed: Vec::new(), rpath: Vec::new(), - runpath: Vec::new(), }) } } diff --git a/src/macho.rs b/src/macho.rs new file mode 100644 index 0000000..4614301 --- /dev/null +++ b/src/macho.rs @@ -0,0 +1,17 @@ +use goblin::mach::MachO; + +use crate::InspectDylib; + +impl InspectDylib for MachO<'_> { + fn rpaths(&self) -> &[&str] { + &self.rpaths + } + + fn libraries(&self) -> &[&str] { + &self.libs + } + + fn interpreter(&self) -> Option<&str> { + None + } +} diff --git a/tests/test.macho b/tests/test.macho new file mode 100755 index 0000000000000000000000000000000000000000..5ce5eac0727fa6aed6902871c2a35d8e88a81ba4 GIT binary patch literal 16833 zcmeI3O-NKx6vyw(XckIp4pEsj6h)u&N|S8m_%Z6Bq?yQ2I=9hzIuD&UF(3Jnr5i~J zq87D^YGJe}OC&+VU@9mADWoV2%7_+9M5{IfW#_#2M&DB?h?eKTf9^f!-gED{zj@p9 zD*o+P8e=8~W(H+}#x0EXGaJkp+X$)vab8nY?mX_St;VR)lw@OQg@@ki>-wu8wkD;CPc#_pKt0{|19m7$YJwHqUjRz%XDkK06vKY0IVhjD=Pu^J1|`q0&pTBNiC39$ ztScN5145P56!M0;gtCw~Aa(>pZDFA#7!u2ZQQ7B>NI}`&l2{Sy#ZVX`VWDW*9_=1| zeHPyq_`HqpQLnTY>kHfgxyZ}!U~DyvyEGsFf3zGBu8%kv%T%C)Lq-IM01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>@ue&zvG*FgurriSY;ZEvws0FvvBw&Prf(ls`nA=t@z=!J z8l|b+%maQdc8bl>h{*k-*C&Qpa=s%u0*58u{a|Be80Vh~Wr3 znmsJLygWYDI$%B-iCur1b?s2e;o^8h<+<0E{Q2+0y?005!DsU=v7~p?({1Kzck)8d zgP!jG_m3`A4DP-Cw{?%_%H+t4O($mW>}#&y^TyNqX`OGYb!_zWRKMruOlhsdI(KGU g_Pw@7?z?3jczEN<`HF_B4_{^-Lo@d?e+ Date: Sat, 1 Oct 2022 18:49:04 +0800 Subject: [PATCH 2/8] Parse single arch macho works --- src/elf.rs | 42 ++++++++++++++++++++++- src/errors.rs | 3 ++ src/lib.rs | 94 ++++++++++++++++++++++++++++----------------------- src/macho.rs | 9 ++++- 4 files changed, 103 insertions(+), 45 deletions(-) diff --git a/src/elf.rs b/src/elf.rs index 6e7ef3e..b635626 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -1,4 +1,10 @@ -use goblin::elf::Elf; +use goblin::{ + elf::{ + header::{EI_OSABI, ELFOSABI_GNU, ELFOSABI_NONE}, + Elf, + }, + Object, +}; use crate::InspectDylib; @@ -18,4 +24,38 @@ impl InspectDylib for Elf<'_> { fn interpreter(&self) -> Option<&str> { self.interpreter.clone() } + + /// See if two ELFs are compatible + /// + /// This compares the aspects of the ELF to see if they're compatible: + /// bit size, endianness, machine type, and operating system. + fn compatible(&self, other: &Object) -> bool { + match other { + Object::Elf(other) => { + if self.is_64 != other.is_64 { + return false; + } + if self.little_endian != other.little_endian { + return false; + } + if self.header.e_machine != other.header.e_machine { + return false; + } + let compatible_osabis = &[ + ELFOSABI_NONE, // ELFOSABI_NONE / ELFOSABI_SYSV + ELFOSABI_GNU, // ELFOSABI_GNU / ELFOSABI_LINUX + ]; + let osabi1 = self.header.e_ident[EI_OSABI]; + let osabi2 = other.header.e_ident[EI_OSABI]; + if osabi1 != osabi2 + && !compatible_osabis.contains(&osabi1) + && !compatible_osabis.contains(&osabi2) + { + return false; + } + true + } + _ => false, + } + } } diff --git a/src/errors.rs b/src/errors.rs index c239660..f1cc7d3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -9,6 +9,7 @@ pub enum Error { Io(io::Error), Goblin(goblin::error::Error), LdSoConf(LdSoConfError), + UnsupportedBinary, } impl fmt::Display for Error { @@ -17,6 +18,7 @@ impl fmt::Display for Error { Error::Io(e) => e.fmt(f), Error::Goblin(e) => e.fmt(f), Error::LdSoConf(e) => e.fmt(f), + Error::UnsupportedBinary => write!(f, "Unsupported binary format"), } } } @@ -27,6 +29,7 @@ impl error::Error for Error { Error::Io(e) => e.source(), Error::Goblin(e) => e.source(), Error::LdSoConf(e) => e.source(), + Error::UnsupportedBinary => None, } } } diff --git a/src/lib.rs b/src/lib.rs index 28531c5..37a817c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,8 @@ use std::env; use std::path::{Path, PathBuf}; use fs_err as fs; -use goblin::elf::{ - header::{EI_OSABI, ELFOSABI_GNU, ELFOSABI_NONE}, - Elf, -}; +use goblin::mach::Mach; +use goblin::Object; mod elf; mod errors; @@ -62,6 +60,8 @@ trait InspectDylib { fn libraries(&self) -> &[&str]; /// The binary’s program interpreter (e.g., dynamic linker). fn interpreter(&self) -> Option<&str>; + /// See if two dynamic libraries are compatible. + fn compatible(&self, other: &Object) -> bool; } /// Library dependency analyzer @@ -126,11 +126,25 @@ impl DependencyAnalyzer { self.load_ld_paths(path)?; let bytes = fs::read(path)?; - let elf = Elf::parse(&bytes)?; + let dep_tree = match Object::parse(&bytes)? { + Object::Elf(elf) => self.analyze_dylib(path, elf)?, + Object::Mach(mach) => match mach { + Mach::Fat(_) => todo!(), + Mach::Binary(macho) => self.analyze_dylib(path, macho)?, + }, + _ => return Err(Error::UnsupportedBinary), + }; + Ok(dep_tree) + } - let rpaths = self.read_rpath(&elf, path)?; + fn analyze_dylib( + &mut self, + path: &Path, + dylib: impl InspectDylib, + ) -> Result { + let rpaths = self.read_rpath(&dylib, path)?; - let needed: Vec = elf.libraries().iter().map(ToString::to_string).collect(); + let needed: Vec = dylib.libraries().iter().map(ToString::to_string).collect(); let mut libraries = HashMap::new(); let mut stack = needed.clone(); @@ -138,12 +152,12 @@ impl DependencyAnalyzer { if libraries.contains_key(&lib_name) { continue; } - let library = self.find_library(&elf, &lib_name)?; + let library = self.find_library(&dylib, &lib_name)?; libraries.insert(lib_name, library.clone()); stack.extend(library.needed); } - let interpreter = elf.interpreter().map(|interp| interp.to_string()); + let interpreter = dylib.interpreter().map(|interp| interp.to_string()); if let Some(ref interp) = interpreter { if !libraries.contains_key(interp) { let interp_path = self.root.join(interp.strip_prefix('/').unwrap_or(interp)); @@ -251,7 +265,7 @@ impl DependencyAnalyzer { } /// Try to locate a `lib_name` that is compatible to `dylib` - fn find_library(&self, dylib: &Elf, lib_name: &str) -> Result { + fn find_library(&self, dylib: &impl InspectDylib, lib_name: &str) -> Result { for lib_path in self .rpaths .iter() @@ -271,10 +285,33 @@ impl DependencyAnalyzer { // FIXME: readlink to get real path if lib_path.exists() { let bytes = fs::read(&lib_path)?; - if let Ok(lib_elf) = Elf::parse(&bytes) { - if compatible_elfs(dylib, &lib_elf) { - let needed = lib_elf.libraries.iter().map(ToString::to_string).collect(); - let rpath = self.read_rpath(&lib_elf, &lib_path)?; + if let Ok(obj) = Object::parse(&bytes) { + if let Some((rpath, needed)) = match obj { + Object::Elf(ref elf) => { + if dylib.compatible(&obj) { + Some(( + self.read_rpath(elf, &lib_path)?, + elf.libraries().iter().map(ToString::to_string).collect(), + )) + } else { + None + } + } + Object::Mach(ref mach) => match mach { + Mach::Fat(_) => None, + Mach::Binary(ref macho) => { + if dylib.compatible(&obj) { + Some(( + self.read_rpath(macho, &lib_path)?, + macho.libraries().iter().map(ToString::to_string).collect(), + )) + } else { + None + } + } + }, + _ => None, + } { return Ok(Library { name: lib_name.to_string(), path: lib_path.to_path_buf(), @@ -306,32 +343,3 @@ fn find_musl_libc() -> Result, Error> { _ => Ok(None), } } - -/// See if two ELFs are compatible -/// -/// This compares the aspects of the ELF to see if they're compatible: -/// bit size, endianness, machine type, and operating system. -fn compatible_elfs(elf1: &Elf, elf2: &Elf) -> bool { - if elf1.is_64 != elf2.is_64 { - return false; - } - if elf1.little_endian != elf2.little_endian { - return false; - } - if elf1.header.e_machine != elf2.header.e_machine { - return false; - } - let compatible_osabis = &[ - ELFOSABI_NONE, // ELFOSABI_NONE / ELFOSABI_SYSV - ELFOSABI_GNU, // ELFOSABI_GNU / ELFOSABI_LINUX - ]; - let osabi1 = elf1.header.e_ident[EI_OSABI]; - let osabi2 = elf2.header.e_ident[EI_OSABI]; - if osabi1 != osabi2 - && !compatible_osabis.contains(&osabi1) - && !compatible_osabis.contains(&osabi2) - { - return false; - } - true -} diff --git a/src/macho.rs b/src/macho.rs index 4614301..9d4895a 100644 --- a/src/macho.rs +++ b/src/macho.rs @@ -1,4 +1,4 @@ -use goblin::mach::MachO; +use goblin::{mach::MachO, Object}; use crate::InspectDylib; @@ -14,4 +14,11 @@ impl InspectDylib for MachO<'_> { fn interpreter(&self) -> Option<&str> { None } + + fn compatible(&self, other: &Object) -> bool { + match other { + Object::Mach(_) => true, + _ => false, + } + } } From dbcb16107b3591f860c89afbef17c47f9f0d594e Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 1 Oct 2022 18:58:48 +0800 Subject: [PATCH 3/8] Remove `self`/dylib id from libraries for macho --- src/elf.rs | 4 ++-- src/lib.rs | 2 +- src/macho.rs | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/elf.rs b/src/elf.rs index b635626..9be9868 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -17,8 +17,8 @@ impl InspectDylib for Elf<'_> { } } - fn libraries(&self) -> &[&str] { - &self.libraries + fn libraries(&self) -> Vec<&str> { + self.libraries.clone() } fn interpreter(&self) -> Option<&str> { diff --git a/src/lib.rs b/src/lib.rs index 37a817c..6cd4da0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ trait InspectDylib { /// Runtime library search paths. fn rpaths(&self) -> &[&str]; /// A list of this binary’s dynamic libraries it depends on directly. - fn libraries(&self) -> &[&str]; + fn libraries(&self) -> Vec<&str>; /// The binary’s program interpreter (e.g., dynamic linker). fn interpreter(&self) -> Option<&str>; /// See if two dynamic libraries are compatible. diff --git a/src/macho.rs b/src/macho.rs index 9d4895a..b151159 100644 --- a/src/macho.rs +++ b/src/macho.rs @@ -7,8 +7,11 @@ impl InspectDylib for MachO<'_> { &self.rpaths } - fn libraries(&self) -> &[&str] { - &self.libs + fn libraries(&self) -> Vec<&str> { + // goblin always add `self` or dylib id as a needed library, so we need to remove it, see + // https://github.com/m4b/goblin/blob/6fdaffdc411bacd5dd7095dc93cec66302ca2575/src/mach/mod.rs#L174 + // https://github.com/m4b/goblin/blob/6fdaffdc411bacd5dd7095dc93cec66302ca2575/src/mach/mod.rs#L231-L235 + self.libs[1..].to_vec() } fn interpreter(&self) -> Option<&str> { From ff4881af0e3b63af853dca4a6deefbdfc809c760 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 1 Oct 2022 20:25:07 +0800 Subject: [PATCH 4/8] Add PE support --- src/lib.rs | 12 ++++++++++++ src/pe.rs | 24 ++++++++++++++++++++++++ tests/test.pe | Bin 0 -> 10752 bytes 3 files changed, 36 insertions(+) create mode 100644 src/pe.rs create mode 100644 tests/test.pe diff --git a/src/lib.rs b/src/lib.rs index 6cd4da0..0af7e55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod elf; mod errors; pub mod ld_so_conf; mod macho; +mod pe; pub use errors::Error; use ld_so_conf::parse_ld_so_conf; @@ -132,6 +133,7 @@ impl DependencyAnalyzer { Mach::Fat(_) => todo!(), Mach::Binary(macho) => self.analyze_dylib(path, macho)?, }, + Object::PE(pe) => self.analyze_dylib(path, pe)?, _ => return Err(Error::UnsupportedBinary), }; Ok(dep_tree) @@ -310,6 +312,16 @@ impl DependencyAnalyzer { } } }, + Object::PE(ref pe) => { + if dylib.compatible(&obj) { + Some(( + self.read_rpath(pe, &lib_path)?, + pe.libraries().iter().map(ToString::to_string).collect(), + )) + } else { + None + } + } _ => None, } { return Ok(Library { diff --git a/src/pe.rs b/src/pe.rs new file mode 100644 index 0000000..3719f83 --- /dev/null +++ b/src/pe.rs @@ -0,0 +1,24 @@ +use goblin::{pe::PE, Object}; + +use crate::InspectDylib; + +impl InspectDylib for PE<'_> { + fn rpaths(&self) -> &[&str] { + &[] + } + + fn libraries(&self) -> Vec<&str> { + self.libraries.clone() + } + + fn interpreter(&self) -> Option<&str> { + None + } + + fn compatible(&self, other: &Object) -> bool { + match other { + Object::PE(_) => true, + _ => false, + } + } +} diff --git a/tests/test.pe b/tests/test.pe new file mode 100644 index 0000000000000000000000000000000000000000..5d0f29d6669efae90d7a05df572d98957531e178 GIT binary patch literal 10752 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 literal 0 HcmV?d00001 From 574a9175ab5ec2f4392005ef3afeb9d746445ae1 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 1 Oct 2022 20:27:43 +0800 Subject: [PATCH 5/8] Add test cases for macho and pe --- tests/test_lddtree.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/test_lddtree.rs b/tests/test_lddtree.rs index 20860a0..4139bf7 100644 --- a/tests/test_lddtree.rs +++ b/tests/test_lddtree.rs @@ -1,7 +1,7 @@ use lddtree::DependencyAnalyzer; #[test] -fn test_lddtree() { +fn test_elf() { let analyzer = DependencyAnalyzer::default(); let deps = analyzer.analyze("tests/test.elf").unwrap(); assert_eq!( @@ -20,3 +20,37 @@ fn test_lddtree() { ); assert_eq!(deps.libraries.len(), 6); } + +#[test] +fn test_macho() { + let analyzer = DependencyAnalyzer::default(); + let deps = analyzer.analyze("tests/test.macho").unwrap(); + assert!(deps.interpreter.is_none()); + assert_eq!( + deps.needed, + &[ + "/usr/lib/libz.1.dylib", + "/usr/lib/libiconv.2.dylib", + "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", + "/usr/lib/libSystem.B.dylib" + ] + ); + assert_eq!(deps.libraries.len(), 4); +} + +#[test] +fn test_pe() { + let analyzer = DependencyAnalyzer::default(); + let deps = analyzer.analyze("tests/test.pe").unwrap(); + assert!(deps.interpreter.is_none()); + assert_eq!( + deps.needed, + &[ + "KERNEL32.dll", + "VCRUNTIME140.dll", + "api-ms-win-crt-runtime-l1-1-0.dll", + "api-ms-win-crt-stdio-l1-1-0.dll" + ] + ); + assert_eq!(deps.libraries.len(), 4); +} From b72ba31da3415f5fb796fe14ac86b0eedd134e3d Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 1 Oct 2022 20:29:53 +0800 Subject: [PATCH 6/8] Only run tests in main branch --- .github/workflows/CI.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2d5cc4f..e68a534 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,8 @@ -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: name: CI From 010b00f79197e52a9bedc50fd4c13c7bfab815c1 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 1 Oct 2022 23:57:24 +0800 Subject: [PATCH 7/8] Refine macho `compatible` function --- src/lib.rs | 2 +- src/macho.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0af7e55..48ede0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,7 @@ impl DependencyAnalyzer { let dep_tree = match Object::parse(&bytes)? { Object::Elf(elf) => self.analyze_dylib(path, elf)?, Object::Mach(mach) => match mach { - Mach::Fat(_) => todo!(), + Mach::Fat(_) => return Err(Error::UnsupportedBinary), Mach::Binary(macho) => self.analyze_dylib(path, macho)?, }, Object::PE(pe) => self.analyze_dylib(path, pe)?, diff --git a/src/macho.rs b/src/macho.rs index b151159..f125ce5 100644 --- a/src/macho.rs +++ b/src/macho.rs @@ -1,4 +1,7 @@ -use goblin::{mach::MachO, Object}; +use goblin::{ + mach::{Mach, MachO}, + Object, +}; use crate::InspectDylib; @@ -20,7 +23,30 @@ impl InspectDylib for MachO<'_> { fn compatible(&self, other: &Object) -> bool { match other { - Object::Mach(_) => true, + Object::Mach(mach) => match mach { + Mach::Fat(fat) => { + for macho in fat { + if let Ok(goblin::mach::SingleArch::MachO(macho)) = macho { + if self.compatible(&Object::Mach(Mach::Binary(macho))) { + return true; + } + } + } + false + } + Mach::Binary(macho) => { + if self.is_64 != macho.is_64 { + return false; + } + if self.little_endian != macho.little_endian { + return false; + } + if self.header.cputype != macho.header.cputype { + return false; + } + true + } + }, _ => false, } } From f334ab1c262f02fd0ef4eae15c2d234dac3cd418 Mon Sep 17 00:00:00 2001 From: messense Date: Sun, 2 Oct 2022 00:01:23 +0800 Subject: [PATCH 8/8] Refine PE `compatible` function --- README.md | 2 +- src/pe.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3f62a88..18e71fa 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Add it to your ``Cargo.toml``: ```toml [dependencies] -lddtree = "0.2" +lddtree = "0.3" ``` ## Command line utility diff --git a/src/pe.rs b/src/pe.rs index 3719f83..031ddc0 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -17,7 +17,15 @@ impl InspectDylib for PE<'_> { fn compatible(&self, other: &Object) -> bool { match other { - Object::PE(_) => true, + Object::PE(pe) => { + if self.is_64 != pe.is_64 { + return false; + } + if self.header.coff_header.machine != pe.header.coff_header.machine { + return false; + } + true + } _ => false, } }