Skip to content

Commit

Permalink
elf: add ELFCOMPRESS_ZSTD (#532)
Browse files Browse the repository at this point in the history
Also add readobj support for compression headers.
  • Loading branch information
philipc committed Apr 6, 2023
1 parent e0f7ccd commit 463bab4
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ indexmap = { version = "1.6", optional = true }
wasmparser = { version = "0.102.0", optional = true }
memchr = { version = "2.4.1", default-features = false }
hashbrown = { version = "0.13.1", features = ["ahash"], default-features = false, optional = true }
ruzstd = { version = "0.3.1", optional = true }

# Internal feature, only used when building as part of libstd, not part of the
# stable interface of this crate.
Expand Down Expand Up @@ -48,7 +49,7 @@ write = ["write_std", "coff", "elf", "macho", "pe", "xcoff"]
std = ["memchr/std"]
# Enable decompression of compressed sections.
# This feature is not required if you want to do your own decompression.
compression = ["flate2", "std"]
compression = ["flate2", "ruzstd", "std"]
# Treat all types as unaligned.
# Normally types use the alignment required by the specifications, but
# sometimes files do not strictly follow the specifications.
Expand Down
10 changes: 10 additions & 0 deletions crates/examples/src/readobj/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ fn print_section_headers<Elf: FileHeader>(
p.field_hex("AddressAlign", section.sh_addralign(endian).into());
p.field_hex("EntrySize", section.sh_entsize(endian).into());

if let Some(Some((compression, _, _))) = section.compression(endian, data).print_err(p)
{
p.group("CompressionHeader", |p| {
p.field_enum("Type", compression.ch_type(endian), FLAGS_ELFCOMPRESS);
p.field_hex("Size", compression.ch_size(endian).into());
p.field_hex("AddressAlign", compression.ch_addralign(endian).into());
});
}

match section.sh_type(endian) {
SHT_SYMTAB | SHT_DYNSYM => {
print_section_symbols(p, endian, data, elf, sections, index, section)
Expand Down Expand Up @@ -1315,6 +1324,7 @@ static FLAGS_SHF_PARISC: &[Flag<u32>] = &flags!(SHF_PARISC_SHORT, SHF_PARISC_HUG
static FLAGS_SHF_ALPHA: &[Flag<u32>] = &flags!(SHF_ALPHA_GPREL);
static FLAGS_SHF_ARM: &[Flag<u32>] = &flags!(SHF_ARM_ENTRYSECT, SHF_ARM_COMDEF);
static FLAGS_SHF_IA_64: &[Flag<u32>] = &flags!(SHF_IA_64_SHORT, SHF_IA_64_NORECOV);
static FLAGS_ELFCOMPRESS: &[Flag<u32>] = &flags!(ELFCOMPRESS_ZLIB, ELFCOMPRESS_ZSTD);
static FLAGS_STT: &[Flag<u8>] = &flags!(
STT_NOTYPE,
STT_OBJECT,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
SectionHeader {
Index: 6
Name: ".debug_info" (0x3E)
Type: SHT_PROGBITS (0x1)
Flags: 0x800
SHF_COMPRESSED (0x800)
Address: 0x0
Offset: 0x70
Size: 0x74
Link: 0
Info: 0
AddressAlign: 0x8
EntrySize: 0x0
CompressionHeader {
Type: ELFCOMPRESS_ZLIB (0x1)
Size: 0xAF
AddressAlign: 0x1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
SectionHeader {
Index: 6
Name: ".debug_info" (0x3E)
Type: SHT_PROGBITS (0x1)
Flags: 0x800
SHF_COMPRESSED (0x800)
Address: 0x0
Offset: 0x70
Size: 0x84
Link: 0
Info: 0
AddressAlign: 0x8
EntrySize: 0x0
CompressionHeader {
Type: ELFCOMPRESS_ZSTD (0x2)
Size: 0xAF
AddressAlign: 0x1
}
}
2 changes: 2 additions & 0 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,8 @@ pub struct CompressionHeader64<E: Endian> {

/// ZLIB/DEFLATE algorithm.
pub const ELFCOMPRESS_ZLIB: u32 = 1;
/// Zstandard algorithm.
pub const ELFCOMPRESS_ZSTD: u32 = 2;
/// Start of OS-specific compression types.
pub const ELFCOMPRESS_LOOS: u32 = 0x6000_0000;
/// End of OS-specific compression types.
Expand Down
76 changes: 51 additions & 25 deletions src/read/elf/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,32 +381,24 @@ impl<'data, 'file, Elf: FileHeader, R: ReadRef<'data>> ElfSection<'data, 'file,

fn maybe_compressed(&self) -> read::Result<Option<CompressedFileRange>> {
let endian = self.file.endian;
if (self.section.sh_flags(endian).into() & u64::from(elf::SHF_COMPRESSED)) == 0 {
return Ok(None);
}
let (section_offset, section_size) = self
.section
.file_range(endian)
.read_error("Invalid ELF compressed section type")?;
let mut offset = section_offset;
let header = self
.file
.data
.read::<Elf::CompressionHeader>(&mut offset)
.read_error("Invalid ELF compressed section offset")?;
if header.ch_type(endian) != elf::ELFCOMPRESS_ZLIB {
return Err(Error("Unsupported ELF compression type"));
if let Some((header, offset, compressed_size)) =
self.section.compression(endian, self.file.data)?
{
let format = match header.ch_type(endian) {
elf::ELFCOMPRESS_ZLIB => CompressionFormat::Zlib,
elf::ELFCOMPRESS_ZSTD => CompressionFormat::Zstandard,
_ => return Err(Error("Unsupported ELF compression type")),
};
let uncompressed_size = header.ch_size(endian).into();
Ok(Some(CompressedFileRange {
format,
offset,
compressed_size,
uncompressed_size,
}))
} else {
Ok(None)
}
let uncompressed_size = header.ch_size(endian).into();
let compressed_size = section_size
.checked_sub(offset - section_offset)
.read_error("Invalid ELF compressed section size")?;
Ok(Some(CompressedFileRange {
format: CompressionFormat::Zlib,
offset,
compressed_size,
uncompressed_size,
}))
}

/// Try GNU-style "ZLIB" header decompression.
Expand Down Expand Up @@ -1006,6 +998,40 @@ pub trait SectionHeader: Debug + Pod {
let data = self.data(endian, data)?;
AttributesSection::new(endian, data)
}

/// Parse the compression header if present.
///
/// Returns the header, and the offset and size of the compressed section data
/// in the file.
///
/// Returns `Ok(None)` if the section flags do not have `SHF_COMPRESSED`.
/// Returns `Err` for invalid values.
fn compression<'data, R: ReadRef<'data>>(
&self,
endian: Self::Endian,
data: R,
) -> read::Result<
Option<(
&'data <Self::Elf as FileHeader>::CompressionHeader,
u64,
u64,
)>,
> {
if (self.sh_flags(endian).into() & u64::from(elf::SHF_COMPRESSED)) == 0 {
return Ok(None);
}
let (section_offset, section_size) = self
.file_range(endian)
.read_error("Invalid ELF compressed section type")?;
let mut offset = section_offset;
let header = data
.read::<<Self::Elf as FileHeader>::CompressionHeader>(&mut offset)
.read_error("Invalid ELF compressed section offset")?;
let compressed_size = section_size
.checked_sub(offset - section_offset)
.read_error("Invalid ELF compressed section size")?;
Ok(Some((header, offset, compressed_size)))
}
}

impl<Endian: endian::Endian> SectionHeader for elf::SectionHeader32<Endian> {
Expand Down
23 changes: 23 additions & 0 deletions src/read/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,10 @@ pub enum CompressionFormat {
///
/// Used for ELF compression and GNU compressed debug information.
Zlib,
/// Zstandard.
///
/// Used for ELF compression.
Zstandard,
}

/// A range in a file that may be compressed.
Expand Down Expand Up @@ -731,6 +735,25 @@ impl<'data> CompressedData<'data> {
.read_error("Invalid zlib compressed data")?;
Ok(Cow::Owned(decompressed))
}
#[cfg(feature = "compression")]
CompressionFormat::Zstandard => {
use core::convert::TryInto;
use std::io::Read;
let size = self
.uncompressed_size
.try_into()
.ok()
.read_error("Uncompressed data size is too large.")?;
let mut decompressed = Vec::with_capacity(size);
let mut decoder = ruzstd::StreamingDecoder::new(self.data)
.ok()
.read_error("Invalid zstd compressed data")?;
decoder
.read_to_end(&mut decompressed)
.ok()
.read_error("Invalid zstd compressed data")?;
Ok(Cow::Owned(decompressed))
}
_ => Err(Error("Unsupported compressed data.")),
}
}
Expand Down
2 changes: 1 addition & 1 deletion testfiles

0 comments on commit 463bab4

Please sign in to comment.