Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read/archive: add thin archive support #651

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading