Skip to content

Commit

Permalink
read/gnu_compression: extract and support in macho
Browse files Browse the repository at this point in the history
This commit extracts the GNU-style section compression logic from the
read::elf::section to a module underneath read, and then uses it also
in read::macho. This is the style of compression created by the go
compiler for Mach-O executables.
  • Loading branch information
ajwerner committed Jun 21, 2024
1 parent 211bfce commit 988a6d8
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 44 deletions.
45 changes: 8 additions & 37 deletions src/read/elf/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use crate::elf;
use crate::endian::{self, Endianness, U32Bytes};
use crate::pod::{self, Pod};
use crate::read::{
self, CompressedData, CompressedFileRange, CompressionFormat, Error, ObjectSection, ReadError,
ReadRef, RelocationMap, SectionFlags, SectionIndex, SectionKind, StringTable,
self, gnu_compression, CompressedData, CompressedFileRange, CompressionFormat, Error,
ObjectSection, ReadError, ReadRef, RelocationMap, SectionFlags, SectionIndex, SectionKind,
StringTable,
};

use super::{
Expand Down Expand Up @@ -508,46 +509,16 @@ impl<'data, 'file, Elf: FileHeader, R: ReadRef<'data>> ElfSection<'data, 'file,
}
}

/// Try GNU-style "ZLIB" header decompression.
// Try GNU-style "ZLIB" header decompression.
fn maybe_compressed_gnu(&self) -> read::Result<Option<CompressedFileRange>> {
let name = match self.name() {
Ok(name) => name,
// I think it's ok to ignore this error?
Err(_) => return Ok(None),
};
if !name.starts_with(".zdebug_") {
if !self.name().map_or(false, |name| name.starts_with(".zdebug_")) {
return Ok(None);
}
let (section_offset, section_size) = self
.section
.file_range(self.file.endian)
.file_range()
.read_error("Invalid ELF GNU compressed section type")?;
let mut offset = section_offset;
let data = self.file.data;
// Assume ZLIB-style uncompressed data is no more than 4GB to avoid accidentally
// huge allocations. This also reduces the chance of accidentally matching on a
// .debug_str that happens to start with "ZLIB".
if data
.read_bytes(&mut offset, 8)
.read_error("ELF GNU compressed section is too short")?
!= b"ZLIB\0\0\0\0"
{
return Err(Error("Invalid ELF GNU compressed section header"));
}
let uncompressed_size = data
.read::<U32Bytes<_>>(&mut offset)
.read_error("ELF GNU compressed section is too short")?
.get(endian::BigEndian)
.into();
let compressed_size = section_size
.checked_sub(offset - section_offset)
.read_error("ELF GNU compressed section is too short")?;
Ok(Some(CompressedFileRange {
format: CompressionFormat::Zlib,
offset,
compressed_size,
uncompressed_size,
}))
gnu_compression::compressed_file_range(self.file.data, section_offset, section_size)
.map(Some)
}
}

Expand Down
36 changes: 36 additions & 0 deletions src/read/gnu_compression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::read::{self, Error, ReadError as _};
use crate::{endian, CompressedFileRange, CompressionFormat, ReadRef, U32Bytes};

// Attempt to parse the the CompressedFileRange for a section using the GNU-style
// inline compression header format. This is used by the Go compiler in Mach-O files
// as well as by the GNU linker in some ELF files.
pub(super) fn compressed_file_range<'data, R: ReadRef<'data>>(
file_data: R,
section_offset: u64,
section_size: u64,
) -> read::Result<CompressedFileRange> {
let mut offset = section_offset;
// Assume ZLIB-style uncompressed data is no more than 4GB to avoid accidentally
// huge allocations. This also reduces the chance of accidentally matching on a
// .debug_str that happens to start with "ZLIB".
let header = file_data
.read_bytes(&mut offset, 8)
.read_error("GNU compressed section is too short")?;
if header != b"ZLIB\0\0\0\0" {
return Err(Error("Invalid GNU compressed section header"));
}
let uncompressed_size = file_data
.read::<U32Bytes<_>>(&mut offset)
.read_error("GNU compressed section is too short")?
.get(endian::BigEndian)
.into();
let compressed_size = section_size
.checked_sub(offset - section_offset)
.read_error("GNU compressed section is too short")?;
Ok(CompressedFileRange {
format: CompressionFormat::Zlib,
offset,
compressed_size,
uncompressed_size,
})
}
28 changes: 21 additions & 7 deletions src/read/macho/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::endian::{self, Endianness};
use crate::macho;
use crate::pod::Pod;
use crate::read::{
self, CompressedData, CompressedFileRange, ObjectSection, ReadError, ReadRef, RelocationMap,
Result, SectionFlags, SectionIndex, SectionKind,
self, gnu_compression, CompressedData, CompressedFileRange, ObjectSection, ReadError, ReadRef,
RelocationMap, Result, SectionFlags, SectionIndex, SectionKind,
};

use super::{MachHeader, MachOFile, MachORelocationIterator};
Expand Down Expand Up @@ -102,6 +102,18 @@ where
.data(self.file.endian, self.internal.data)
.read_error("Invalid Mach-O section size or offset")
}

// Try GNU-style "ZLIB" header decompression.
fn maybe_compressed_gnu(&self) -> Result<Option<CompressedFileRange>> {
if !self.name().map_or(false, |name| name.starts_with("__zdebug_")) {
return Ok(None);
}
let (section_offset, section_size) = self
.file_range()
.read_error("Invalid ELF GNU compressed section type")?;
gnu_compression::compressed_file_range(self.internal.data, section_offset, section_size)
.map(Some)
}
}

impl<'data, 'file, Mach, R> read::private::Sealed for MachOSection<'data, 'file, Mach, R>
Expand Down Expand Up @@ -162,14 +174,16 @@ where
))
}

#[inline]
fn compressed_file_range(&self) -> Result<CompressedFileRange> {
Ok(CompressedFileRange::none(self.file_range()))
Ok(if let Some(data) = self.maybe_compressed_gnu()? {
data
} else {
CompressedFileRange::none(self.file_range())
})
}

#[inline]
fn compressed_data(&self) -> Result<CompressedData<'data>> {
self.data().map(CompressedData::none)
fn compressed_data(&self) -> read::Result<CompressedData<'data>> {
self.compressed_file_range()?.data(self.file.data)
}

#[inline]
Expand Down
3 changes: 3 additions & 0 deletions src/read/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ pub use read_cache::*;
mod util;
pub use util::*;

#[cfg(any(feature = "elf", feature = "macho"))]
mod gnu_compression;

#[cfg(any(
feature = "coff",
feature = "elf",
Expand Down

0 comments on commit 988a6d8

Please sign in to comment.