rustfmt, then clippy -D warnings across the three new crates. Mechanical,
except three decisions that are stated rather than silently allowed:
* lzx.rs gets file-scoped needless_range_loop/explicit_counter_loop allows.
Index arithmetic IS the algorithm -- LZX is defined over symbol indices,
Huffman slots and window positions, and a decompressor that is merely
idiomatic is worth nothing if it is not bit-exact.
* sylpheed-xexdb gets crate-scoped allows for needless_range_loop (nine
sites index reg[r] where r is the PowerPC register number -- the index is
the meaning), too_many_arguments and type_complexity. This code arrived
whole from a retired repository; a refactor here would be an unreviewed
edit dressed as a lint fix.
* Everything else clippy asked for is FIXED, including all 14 doc-indent
sites, the let-else, and a Prepared type alias in the binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
186 lines
6.5 KiB
Rust
186 lines
6.5 KiB
Rust
//! `.tls` section parser for PE32 PowerPC.
|
|
//!
|
|
//! When MSVC links a binary that uses `__declspec(thread)` storage, it emits
|
|
//! a `.tls` section plus an IMAGE_TLS_DIRECTORY32 inside `.rdata`. The
|
|
//! directory points at:
|
|
//! - the raw initialised TLS data range (start, end VAs)
|
|
//! - the address of the index field (a u32 written at runtime by the
|
|
//! loader to identify which TLS slot was assigned)
|
|
//! - an array of TLS callback function pointers (NUL-terminated)
|
|
//! - the size of the zero-fill area appended after raw data
|
|
//!
|
|
//! Xbox 360 binaries follow the standard PE layout. Sylpheed has no `.tls`
|
|
//! section and no TLS directory — the parser simply returns `None` and
|
|
//! callers emit zero rows.
|
|
//!
|
|
//! Reference: Microsoft PE/COFF spec, IMAGE_TLS_DIRECTORY32 layout.
|
|
|
|
use crate::pe::PeSection;
|
|
|
|
/// One TLS callback function pointer extracted from the directory's
|
|
/// callback array.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct TlsCallback {
|
|
pub address: u32,
|
|
}
|
|
|
|
/// Parsed `.tls` directory information. All fields are absolute VAs.
|
|
#[derive(Debug, Clone)]
|
|
pub struct TlsInfo {
|
|
/// VA of the start of the initialised raw TLS data (template).
|
|
pub raw_data_start: u32,
|
|
/// VA of one-past-end of the raw TLS data.
|
|
pub raw_data_end: u32,
|
|
/// VA of the u32 the loader writes the assigned slot index into.
|
|
pub index_address: u32,
|
|
/// VA of the zero-terminated callback array; 0 when no callbacks.
|
|
pub callback_array: u32,
|
|
/// Bytes of zero-fill appended after the raw template at thread init.
|
|
pub zero_fill_size: u32,
|
|
/// Characteristics flags (alignment / etc).
|
|
pub characteristics: u32,
|
|
/// Resolved TLS callbacks (parsed from `callback_array`).
|
|
pub callbacks: Vec<TlsCallback>,
|
|
}
|
|
|
|
/// Parse the `.tls` section. Returns `None` if the binary has no `.tls`
|
|
/// section or the directory is malformed.
|
|
pub fn parse_tls(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Option<TlsInfo> {
|
|
// Find the `.tls` section. The IMAGE_TLS_DIRECTORY32 lives somewhere
|
|
// in `.rdata`; rather than hunt the IMAGE_DATA_DIRECTORY entry through
|
|
// the optional header, we accept any 24-byte struct at the start of
|
|
// `.tls` if the section's raw data looks like a valid directory.
|
|
//
|
|
// Per MS docs, IMAGE_TLS_DIRECTORY32 layout (24 bytes):
|
|
// +0x00 StartAddressOfRawData (VA, 4)
|
|
// +0x04 EndAddressOfRawData (VA, 4)
|
|
// +0x08 AddressOfIndex (VA, 4)
|
|
// +0x0C AddressOfCallBacks (VA, 4 — array of FN ptrs, NUL-terminated)
|
|
// +0x10 SizeOfZeroFill (4)
|
|
// +0x14 Characteristics (4)
|
|
let tls_section = sections.iter().find(|s| s.name == ".tls")?;
|
|
let off = tls_section.virtual_address as usize;
|
|
if off + 24 > pe.len() {
|
|
return None;
|
|
}
|
|
|
|
// Xbox 360 PE bodies are big-endian; this is consistent with how we
|
|
// parse the PE elsewhere (e.g. xref scanning reads BE u32 from PE).
|
|
let read_u32 = |start: usize| -> u32 {
|
|
u32::from_be_bytes([pe[start], pe[start + 1], pe[start + 2], pe[start + 3]])
|
|
};
|
|
|
|
let raw_data_start = read_u32(off);
|
|
let raw_data_end = read_u32(off + 4);
|
|
let index_address = read_u32(off + 8);
|
|
let callback_array = read_u32(off + 12);
|
|
let zero_fill_size = read_u32(off + 16);
|
|
let characteristics = read_u32(off + 20);
|
|
|
|
// Sanity: raw_data_start should land somewhere inside the image.
|
|
if raw_data_start == 0 && raw_data_end == 0 && index_address == 0 {
|
|
return None;
|
|
}
|
|
|
|
// Walk the callback array (zero-terminated array of u32 VAs).
|
|
let mut callbacks = Vec::new();
|
|
if callback_array != 0 {
|
|
let mut p = callback_array.wrapping_sub(image_base) as usize;
|
|
while p + 4 <= pe.len() {
|
|
let v = read_u32(p);
|
|
if v == 0 {
|
|
break;
|
|
}
|
|
callbacks.push(TlsCallback { address: v });
|
|
p += 4;
|
|
if callbacks.len() >= 64 {
|
|
break;
|
|
} // sanity cap
|
|
}
|
|
}
|
|
|
|
Some(TlsInfo {
|
|
raw_data_start,
|
|
raw_data_end,
|
|
index_address,
|
|
callback_array,
|
|
zero_fill_size,
|
|
characteristics,
|
|
callbacks,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::pe::PeSection;
|
|
|
|
fn mk_section(name: &str, va: u32, size: u32) -> PeSection {
|
|
PeSection {
|
|
name: name.into(),
|
|
virtual_address: va,
|
|
virtual_size: size,
|
|
raw_offset: va,
|
|
raw_size: size,
|
|
flags: 0x4000_0040,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn returns_none_when_no_tls_section() {
|
|
let pe = vec![0u8; 0x100];
|
|
let sections = vec![mk_section(".text", 0x10, 0x40)];
|
|
assert!(parse_tls(&pe, 0x82000000, §ions).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn parses_directory_and_callback_array() {
|
|
let image_base = 0x82000000u32;
|
|
let mut pe = vec![0u8; 0x4000];
|
|
|
|
// Place the .tls section at RVA 0x100 with the directory.
|
|
let tls_va: u32 = 0x100;
|
|
let cb_va: u32 = 0x200;
|
|
// Directory fields:
|
|
let raw_start = 0x800u32;
|
|
let raw_end = 0x900u32;
|
|
let idx = 0x1000u32;
|
|
let zero_fill = 0x40u32;
|
|
let chars = 0x0u32;
|
|
let cb_array = image_base + cb_va;
|
|
for (i, v) in [
|
|
image_base + raw_start,
|
|
image_base + raw_end,
|
|
image_base + idx,
|
|
cb_array,
|
|
zero_fill,
|
|
chars,
|
|
]
|
|
.iter()
|
|
.enumerate()
|
|
{
|
|
pe[tls_va as usize + i * 4..tls_va as usize + i * 4 + 4]
|
|
.copy_from_slice(&v.to_be_bytes());
|
|
}
|
|
|
|
// Two callbacks + NUL terminator at cb_va.
|
|
let cb1 = image_base + 0x500;
|
|
let cb2 = image_base + 0x600;
|
|
pe[cb_va as usize..cb_va as usize + 4].copy_from_slice(&cb1.to_be_bytes());
|
|
pe[cb_va as usize + 4..cb_va as usize + 8].copy_from_slice(&cb2.to_be_bytes());
|
|
// pe[cb_va + 8..cb_va + 12] already zero (terminator).
|
|
|
|
let sections = vec![mk_section(".tls", tls_va, 0x100)];
|
|
let info = parse_tls(&pe, image_base, §ions).expect("parses");
|
|
|
|
assert_eq!(info.raw_data_start, image_base + raw_start);
|
|
assert_eq!(info.raw_data_end, image_base + raw_end);
|
|
assert_eq!(info.index_address, image_base + idx);
|
|
assert_eq!(info.callback_array, cb_array);
|
|
assert_eq!(info.zero_fill_size, zero_fill);
|
|
assert_eq!(info.callbacks.len(), 2);
|
|
assert_eq!(info.callbacks[0].address, cb1);
|
|
assert_eq!(info.callbacks[1].address, cb2);
|
|
}
|
|
}
|