Files
Sylpheed/crates/sylpheed-xex/src/tls.rs
MechaCat02 9c48c340bd fix(xexdb): clear the lint gate on the imported crates
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>
2026-09-13 20:25:44 +02:00

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, &sections).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, &sections).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);
}
}