use std::borrow::Cow;
use std::mem::size_of;
use std::path::Path;
use crate::elf;
use crate::elf::types::ElfN_Nhdr;
use crate::elf::ElfParser;
use crate::util::align_up_u32;
use crate::util::ReadRaw as _;
use crate::IntoError as _;
use crate::Mmap;
use crate::Result;
pub type BuildId<'src> = Cow<'src, [u8]>;
pub(crate) fn read_build_id(parser: &ElfParser) -> Result<Option<BuildId<'_>>> {
let shdrs = parser.section_headers()?;
for (idx, shdr) in shdrs.iter(0).enumerate() {
if shdr.type_() == elf::types::SHT_NOTE {
let mut bytes = parser.section_data(idx).unwrap();
while bytes.len() >= size_of::<ElfN_Nhdr>() {
let nhdr = bytes
.read_pod_ref::<ElfN_Nhdr>()
.ok_or_invalid_data(|| "failed to read build ID section header")?;
let () = bytes
.advance(align_up_u32(nhdr.n_namesz, 4) as _)
.ok_or_invalid_data(|| "failed to skip over ELF note name")?;
if nhdr.n_type == elf::types::NT_GNU_BUILD_ID {
let build_id = bytes
.read_slice(nhdr.n_descsz as _)
.ok_or_invalid_data(|| "failed to read build ID section contents")?;
return Ok(Some(Cow::Borrowed(build_id)))
} else {
let () = bytes
.advance(align_up_u32(nhdr.n_descsz, 4) as _)
.ok_or_invalid_data(|| "failed to skip over ELF note descriptor")?;
}
}
}
}
Ok(None)
}
#[inline]
pub fn read_elf_build_id<P>(path: &P) -> Result<Option<BuildId<'static>>>
where
P: AsRef<Path> + ?Sized,
{
let parser = ElfParser::open(path.as_ref())?;
let buildid = read_build_id(&parser)?.map(|buildid| Cow::Owned(buildid.to_vec()));
Ok(buildid)
}
#[inline]
pub fn read_elf_build_id_from_mmap(mmap: &Mmap) -> Result<Option<BuildId<'static>>> {
let parser = ElfParser::from_mmap(mmap.clone(), None);
let buildid = read_build_id(&parser)?.map(|buildid| Cow::Owned(buildid.to_vec()));
Ok(buildid)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use test_log::test;
use test_tag::tag;
use crate::ErrorKind;
use crate::Result;
#[tag(other_os)]
#[test]
fn build_id_reading_from_name_and_notes() {
fn test(file: &str, f: fn(&ElfParser) -> Result<Option<BuildId>>) {
let elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join(file);
let parser = ElfParser::open(elf.as_path()).unwrap();
let build_id = f(&parser).unwrap().unwrap();
assert_eq!(build_id.len(), 20, "'{build_id:?}'");
}
test("libtest-so.so", read_build_id);
test("libtest-so-32.so", read_build_id);
}
#[tag(other_os)]
#[test]
fn build_id_reading() {
let elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("libtest-so.so");
let build_id = read_elf_build_id(&elf).unwrap().unwrap();
assert_eq!(build_id.len(), 20, "'{build_id:?}'");
let file = File::open(&elf).unwrap();
let mmap = Mmap::map(&file).unwrap();
let build_id = read_elf_build_id_from_mmap(&mmap).unwrap().unwrap();
assert_eq!(build_id.len(), 20, "'{build_id:?}'");
let elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("libtest-so-no-separate-code.so");
let build_id = read_elf_build_id(&elf).unwrap().unwrap();
assert_eq!(build_id.len(), 16, "'{build_id:?}'");
let elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-no-debug.bin");
let build_id = read_elf_build_id(&elf).unwrap();
assert_eq!(build_id, None);
let elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("does-not-exist");
let err = read_elf_build_id(&elf).unwrap_err();
assert_eq!(err.kind(), ErrorKind::NotFound);
}
}