initial commit
This commit is contained in:
commit
a2922b8bad
59 changed files with 2684583 additions and 0 deletions
95
src/locate.rs
Normal file
95
src/locate.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//! Locating primitives: where a library file lives ON DISK, and where the function entries live
|
||||
//! INSIDE an image.
|
||||
//!
|
||||
//! On disk: `find_file` resolves a lib by name under a build tree (nearest-depth-wins, so a Metamod
|
||||
//! shim can't shadow the real engine lib) and `load_lib` turns a build dir *or* a bare `.so` into a
|
||||
//! loaded [`CodeImage`]. Both are leaf primitives (they touch only `elf` + the filesystem), so the
|
||||
//! low-level readers — `schema` especially — depend on THIS module rather than up on the engine.
|
||||
//!
|
||||
//! In an image: [`candidate_entries`] enumerates plausible function starts without symbols — relocation
|
||||
//! values that point into code (vtable slots + function pointers — covers virtual functions) unioned
|
||||
//! with the targets of direct near `call`s found by a linear sweep. `xref` unions this with `.eh_frame`
|
||||
//! starts to index the whole binary.
|
||||
|
||||
use crate::elf::CodeImage;
|
||||
use anyhow::{Context, Result};
|
||||
use iced_x86::{Decoder, DecoderOptions, FlowControl, OpKind};
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Every plausible function entry address in `img`: relocation values that point into code, plus
|
||||
/// the targets of direct near `call`s found by a linear sweep. Sorted, de-duplicated.
|
||||
pub fn candidate_entries(img: &CodeImage) -> Vec<u64> {
|
||||
let mut set: BTreeSet<u64> = img.code_pointer_targets().into_iter().collect();
|
||||
for (va, code) in img.exec_blocks() {
|
||||
let mut dec = Decoder::with_ip(64, code, va, DecoderOptions::NONE);
|
||||
while dec.can_decode() {
|
||||
let insn = dec.decode(); // iced advances one byte on invalid, so the sweep self-resyncs
|
||||
if insn.flow_control() == FlowControl::Call
|
||||
&& matches!(
|
||||
insn.op0_kind(),
|
||||
OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64
|
||||
)
|
||||
{
|
||||
let t = insn.near_branch_target();
|
||||
if img.is_code(t) {
|
||||
set.insert(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Shallowest file named `name` under `dir` (bounded depth), ties broken by sorted path.
|
||||
///
|
||||
/// NEAREST-DEPTH-WINS, not first-`read_dir`-hit: a game install legitimately holds several files of the same
|
||||
/// basename, and the shallowest is the real one. A CS2 tree has the engine's own
|
||||
/// `csgo/bin/linuxsteamrt64/libserver.so` at depth 3 and Metamod's ~300 KB loader shim of the SAME name at
|
||||
/// `csgo/addons/metamod/bin/linuxsteamrt64/libserver.so` (depth 5, plus any `bin.*.bak` siblings). Depth-first
|
||||
/// order made which one you derive from a property of directory-entry order — deriving against the shim would
|
||||
/// yield garbage — and plain sorting is WORSE, since `addons` sorts before `bin`. Sorting is only the tie-break
|
||||
/// among equally-shallow candidates, so the result never depends on filesystem enumeration order.
|
||||
pub(crate) fn find_file(dir: &Path, name: &str, depth: usize) -> Option<PathBuf> {
|
||||
let mut level = vec![dir.to_path_buf()];
|
||||
for _ in 0..depth {
|
||||
let (mut hits, mut next) = (Vec::new(), Vec::new());
|
||||
for d in &level {
|
||||
let Ok(rd) = std::fs::read_dir(d) else {
|
||||
continue;
|
||||
};
|
||||
for e in rd.flatten() {
|
||||
let p = e.path();
|
||||
if p.is_dir() {
|
||||
next.push(p);
|
||||
} else if p.file_name().and_then(|s| s.to_str()) == Some(name) {
|
||||
hits.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hits.is_empty() {
|
||||
hits.sort();
|
||||
return hits.into_iter().next();
|
||||
}
|
||||
if next.is_empty() {
|
||||
return None;
|
||||
}
|
||||
next.sort();
|
||||
level = next;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Locate `lib` under `dir` (depth 8) and load it as a `CodeImage` — the `find_file` + load pattern the
|
||||
/// command entry points share.
|
||||
pub(crate) fn load_lib(path: &Path, lib: &str) -> Result<CodeImage> {
|
||||
// Accept a build DIR (find `lib` within, depth 8) or a direct `.so` FILE (load as-is), so callers can
|
||||
// pass `path/to/build_dir` or `path/to/libserver.so` interchangeably (e.g. `classify-change --prev`).
|
||||
let file = if path.is_file() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
find_file(path, lib, 8)
|
||||
.with_context(|| format!("{lib} not found under {}", path.display()))?
|
||||
};
|
||||
CodeImage::load(&file)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue