Skip to content

Commit

Permalink
read/archive: add thin archive support (#651)
Browse files Browse the repository at this point in the history
  • Loading branch information
philipc authored Mar 25, 2024
1 parent bd15e0c commit 9f11ebd
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 20 deletions.
10 changes: 8 additions & 2 deletions crates/examples/src/readobj/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,12 +315,18 @@ fn print_object_at(p: &mut Printer<'_>, data: &[u8], offset: u64) {

fn print_archive(p: &mut Printer<'_>, data: &[u8]) {
if let Some(archive) = ArchiveFile::parse(data).print_err(p) {
p.field("Format", format!("Archive ({:?})", archive.kind()));
write!(p.w(), "Format: Archive ({:?})", archive.kind()).unwrap();
if archive.is_thin() {
write!(p.w(), " (thin)").unwrap();
}
p.blank();
for member in archive.members() {
if let Some(member) = member.print_err(p) {
p.blank();
p.field("Member", String::from_utf8_lossy(member.name()));
if let Some(data) = member.data(data).print_err(p) {
if member.is_thin() {
p.field("Size", member.size());
} else if let Some(data) = member.data(data).print_err(p) {
print_object(p, data);
}
}
Expand Down
124 changes: 107 additions & 17 deletions src/read/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct ArchiveFile<'data, R: ReadRef<'data> = &'data [u8]> {
members: Members<'data>,
symbols: (u64, u64),
names: &'data [u8],
thin: bool,
}

impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
Expand All @@ -78,11 +79,15 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
.read_bytes(&mut tail, archive::MAGIC.len() as u64)
.read_error("Invalid archive size")?;

if magic == archive::AIX_BIG_MAGIC {
let thin = if magic == archive::AIX_BIG_MAGIC {
return Self::parse_aixbig(data);
} else if magic != archive::MAGIC {
} else if magic == archive::THIN_MAGIC {
true
} else if magic == archive::MAGIC {
false
} else {
return Err(Error("Unsupported archive identifier"));
}
};

let mut members_offset = tail;
let members_end_offset = len;
Expand All @@ -96,6 +101,7 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
},
symbols: (0, 0),
names: &[],
thin,
};

// The first few members may be special, so parse them.
Expand All @@ -113,23 +119,23 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
// BSD may use the extended name for the symbol table. This is handled
// by `ArchiveMember::parse`.
if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], thin)?;
if member.name == b"/" {
// GNU symbol table (unless we later determine this is COFF).
file.kind = ArchiveKind::Gnu;
file.symbols = member.file_range();
members_offset = tail;

if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], thin)?;
if member.name == b"/" {
// COFF linker member.
file.kind = ArchiveKind::Coff;
file.symbols = member.file_range();
members_offset = tail;

if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], thin)?;
if member.name == b"//" {
// COFF names table.
file.names = member.data(data)?;
Expand All @@ -149,7 +155,7 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
members_offset = tail;

if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], thin)?;
if member.name == b"//" {
// GNU names table.
file.names = member.data(data)?;
Expand Down Expand Up @@ -197,6 +203,7 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
members: Members::AixBig { index: &[] },
symbols: (0, 0),
names: &[],
thin: false,
};

// Read the span of symbol table.
Expand Down Expand Up @@ -254,6 +261,11 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
self.kind
}

/// Return true if the archive is a thin archive.
pub fn is_thin(&self) -> bool {
self.thin
}

/// Iterate over the members of the archive.
///
/// This does not return special members.
Expand All @@ -263,6 +275,7 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
data: self.data,
members: self.members,
names: self.names,
thin: self.thin,
}
}
}
Expand All @@ -273,6 +286,7 @@ pub struct ArchiveMemberIterator<'data, R: ReadRef<'data> = &'data [u8]> {
data: R,
members: Members<'data>,
names: &'data [u8],
thin: bool,
}

impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
Expand All @@ -287,7 +301,7 @@ impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
if *offset >= *end_offset {
return None;
}
let member = ArchiveMember::parse(self.data, offset, self.names);
let member = ArchiveMember::parse(self.data, offset, self.names, self.thin);
if member.is_err() {
*offset = *end_offset;
}
Expand Down Expand Up @@ -322,6 +336,7 @@ enum MemberHeader<'data> {
pub struct ArchiveMember<'data> {
header: MemberHeader<'data>,
name: &'data [u8],
// May be zero for thin members.
offset: u64,
size: u64,
}
Expand All @@ -334,6 +349,7 @@ impl<'data> ArchiveMember<'data> {
data: R,
offset: &mut u64,
names: &'data [u8],
thin: bool,
) -> read::Result<Self> {
let header = data
.read::<archive::Header>(offset)
Expand All @@ -342,16 +358,10 @@ impl<'data> ArchiveMember<'data> {
return Err(Error("Invalid archive terminator"));
}

let mut file_offset = *offset;
let mut file_size =
let header_file_size =
parse_u64_digits(&header.size, 10).read_error("Invalid archive member size")?;
*offset = offset
.checked_add(file_size)
.read_error("Archive member size is too large")?;
// Entries are padded to an even number of bytes.
if (file_size & 1) != 0 {
*offset = offset.saturating_add(1);
}
let mut file_offset = *offset;
let mut file_size = header_file_size;

let name = if header.name[0] == b'/' && (header.name[1] as char).is_ascii_digit() {
// Read file name from the names table.
Expand All @@ -371,6 +381,25 @@ impl<'data> ArchiveMember<'data> {
&header.name[..name_len]
};

// Members in thin archives don't have data unless they are special members.
if thin && name != b"/" && name != b"//" && name != b"/SYM64/" {
return Ok(ArchiveMember {
header: MemberHeader::Common(header),
name,
offset: 0,
size: file_size,
});
}

// Skip the file data.
*offset = offset
.checked_add(header_file_size)
.read_error("Archive member size is too large")?;
// Entries are padded to an even number of bytes.
if (header_file_size & 1) != 0 {
*offset = offset.saturating_add(1);
}

Ok(ArchiveMember {
header: MemberHeader::Common(header),
name,
Expand Down Expand Up @@ -493,14 +522,31 @@ impl<'data> ArchiveMember<'data> {
}
}

/// Return the size of the file data.
pub fn size(&self) -> u64 {
self.size
}

/// Return the offset and size of the file data.
pub fn file_range(&self) -> (u64, u64) {
(self.offset, self.size)
}

/// Return true if the member is a thin member.
///
/// Thin members have no file data.
pub fn is_thin(&self) -> bool {
self.offset == 0
}

/// Return the file data.
///
/// This is an empty slice for thin members.
#[inline]
pub fn data<R: ReadRef<'data>>(&self, data: R) -> read::Result<&'data [u8]> {
if self.is_thin() {
return Ok(&[]);
}
data.read_bytes_at(self.offset, self.size)
.read_error("Archive member size is too large")
}
Expand Down Expand Up @@ -667,6 +713,14 @@ mod tests {
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
let archive = ArchiveFile::parse(&data[..]).unwrap();
assert_eq!(archive.kind(), ArchiveKind::AixBig);

let data = b"\
!<thin>\n\
/ 4 `\n\
0000";
let archive = ArchiveFile::parse(&data[..]).unwrap();
assert_eq!(archive.kind(), ArchiveKind::Gnu);
assert!(archive.is_thin());
}

#[test]
Expand Down Expand Up @@ -701,6 +755,42 @@ mod tests {
assert!(members.next().is_none());
}

#[test]
fn thin_gnu_names() {
let data = b"\
!<thin>\n\
// 18 `\n\
0123456789abcdef/\n\
s p a c e/ 0 0 0 644 4 `\n\
0123456789abcde/0 0 0 644 3 `\n\
/0 0 0 0 644 4 `\n\
";
let data = &data[..];
let archive = ArchiveFile::parse(data).unwrap();
assert_eq!(archive.kind(), ArchiveKind::Gnu);
let mut members = archive.members();

let member = members.next().unwrap().unwrap();
assert_eq!(member.name(), b"s p a c e");
assert!(member.is_thin());
assert_eq!(member.size(), 4);
assert_eq!(member.data(data).unwrap(), &[]);

let member = members.next().unwrap().unwrap();
assert_eq!(member.name(), b"0123456789abcde");
assert!(member.is_thin());
assert_eq!(member.size(), 3);
assert_eq!(member.data(data).unwrap(), &[]);

let member = members.next().unwrap().unwrap();
assert_eq!(member.name(), b"0123456789abcdef");
assert!(member.is_thin());
assert_eq!(member.size(), 4);
assert_eq!(member.data(data).unwrap(), &[]);

assert!(members.next().is_none());
}

#[test]
fn bsd_names() {
let data = b"\
Expand Down
3 changes: 2 additions & 1 deletion src/read/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ impl FileKind {

let kind = match [magic[0], magic[1], magic[2], magic[3], magic[4], magic[5], magic[6], magic[7]] {
#[cfg(feature = "archive")]
[b'!', b'<', b'a', b'r', b'c', b'h', b'>', b'\n'] => FileKind::Archive,
[b'!', b'<', b'a', b'r', b'c', b'h', b'>', b'\n']
| [b'!', b'<', b't', b'h', b'i', b'n', b'>', b'\n'] => FileKind::Archive,
#[cfg(feature = "macho")]
[b'd', b'y', b'l', b'd', b'_', b'v', b'1', b' '] => FileKind::DyldCache,
#[cfg(feature = "elf")]
Expand Down

0 comments on commit 9f11ebd

Please sign in to comment.