Open H-M-H opened 3 years ago
Interesting! Would you be able to implement this?
There's also a request for better debug info. So maybe it'd be worth adding elf parser in Rust to handle all of this properly, without needing to spawn shell tools.
Alright, I have had a look at xmas-elf
and unfortunately the documentation is all but existent. Nonetheless I could get it working after some guess work. The following function reads a binary file and returns all directly required libraries:
fn extract_needed_libs<P: AsRef<std::path::Path>>(
binary_path: P,
) -> Result<std::collections::HashSet<String>, Box<dyn std::error::Error>> {
use xmas_elf::{
dynamic::Tag,
header,
sections::{sanity_check, SectionData, ShType},
ElfFile,
};
let mut libs = std::collections::HashSet::new();
// read binary to memory
use std::fs::File;
use std::io::Read;
let mut f = File::open(binary_path)?;
let mut buf = Vec::new();
assert!(f.read_to_end(&mut buf)? > 0);
let elf_file = ElfFile::new(&buf)?;
header::sanity_check(&elf_file)?;
let mut sect_iter = elf_file.section_iter();
// Skip the first (dummy) section
sect_iter.next();
// iterate over sections
for sect in sect_iter {
// skip things we do not care about
if sect.get_type() != Ok(ShType::Dynamic) {
continue;
}
sanity_check(sect, &elf_file)?;
// find entries for needed dynamic libs
if let Ok(SectionData::Dynamic64(dyns)) = sect.get_data(&elf_file) {
for d in dyns.iter() {
if let Ok(Tag::Needed) = d.get_tag() {
// lookup the string containing the name of the library
libs.insert(elf_file.get_dyn_string(d.get_val()? as u32)?.to_string());
}
}
}
// same procedure for 32bit
else if let Ok(SectionData::Dynamic32(dyns)) = sect.get_data(&elf_file) {
for d in dyns.iter() {
if let Ok(Tag::Needed) = d.get_tag() {
libs.insert(elf_file.get_dyn_string(d.get_val()? as u32)?.to_string());
}
}
}
}
Ok(libs)
}
Something to note is that the libraries returned are just their names and not the resolved paths pointing to the actual location of these libraries on the system. So calling ldd
on the binary or ldconfig -p
to get the file paths is still required.
I just found an even better solution, there is: https://manpages.debian.org/testing/dpkg-dev/dpkg-shlibdeps.1.en.html which handles this correctly.
Hey @H-M-H - would you mind elaborating on how to use the dpkg-shlibdeps
program to generate this correct dependency information? Thanks!
dpkg-shlibdeps
unfortunately assumes a debian package path structure, which actually is not required. Assuming you have your binary my_program
located in some folder, you have to create the subfolder debian
and add the file control
to it (may be empty, still works). Then run dpkg-shlibdeps my_program -O
, which will print the dependency information to stdout.
Indeed, that Debian package path structure was confusing me, and I didn't have the time to read loads of documentation or source code to find out. Thanks.
As of now not only direct dependencies are added but also dependencies of dependencies and so on. Consider the following example:
One of the direct dependencies is
libgobject-2.0.so.0
, which in turn depends directly on eitherlibffi.so.7
orlibffi.so.6
, depending on the system I build my binary with. Butlibffi
is not a direct dependency of my binary. That means my binary runs fine no matter iflibffi.so.7
orlibffi.so.6
is installed (as long aslibgobject
is available). So cargo-deb places some unnecessary constraints that reduce compatibility across distributions/versions. In my specific case a program that I built on Debian Stretch can not be installed on Ubuntu Focal because Ubuntu switched fromlibffi.so.6
tolibffi.so.7
and the former is not available anymore (and not needed aslibgobject
points to the latter).The cause is that
ldd
is used to gather information on dependencies: https://github.com/mmstick/cargo-deb/blob/ac4b338be0cbae5222e5f2598eb3928f518eefc2/src/dependencies.rs#L11 Butldd
always resolves direct and indirect dependencies, that's why I propose to replaceldd
with something likeobjdump -p
orreadelf -d
.