mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
page: ?pins=path@base+span places shared objects at fixed bases; ?fjitlibs= applies to every translation and takes name@lo-hi ranges
The function JIT's heap spans come from the same arena cursor as the guest's mappings, so with --fjit HK's UnityPlayer.so landed at 0x11752000 and without it at 0x10752000 (hp6): a unit built for one base missed in the other run. ?pins= feeds the existing closure pins (gex_pin_object), so the unit's base holds either way. ?fjitlibs= was checked only on tier-1 dispatch misses; gex.lookup misses from JIT code (calls through pointers and PLT slots) translated whatever they reached, so the HK black-screen bisection (hb2, hb4) still JIT-compiled objects outside the rules. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
1 parent
945fe792b2
commit
841b380b8f
4 files changed
+54
-20
No files matched your search
+29
-11
@@ -136,7 +136,7 @@ fn page_gen(page: u64, fresh: &mut Vec<(u64, u32)>) -> u32 {
|
||||
/// Translate the function at `addr` and publish it (`gex.lookup`'s miss):
|
||||
/// its index in the function table, or `None` (the fallback runs it).
|
||||
pub(crate) fn translate(addr: u64) -> Option<u32> {
|
||||
if !on() || is_tier1_only(addr) {
|
||||
if !on() || is_tier1_only(addr) || !lib_wanted(addr) {
|
||||
return None;
|
||||
}
|
||||
let only = ONLY.load(Ordering::Relaxed);
|
||||
@@ -243,8 +243,19 @@ pub(crate) fn serve(pc: u64) -> bool {
|
||||
}
|
||||
|
||||
/// `+name,-name` (`?fjitlibs=`): the objects the JIT is limited to (any `+`)
|
||||
/// and kept from (`-`), by file name.
|
||||
static LIBS: Mutex<Vec<(bool, String)>> = Mutex::new(Vec::new());
|
||||
/// and kept from (`-`), by file name. `name@lo-hi` (hex) narrows a rule to
|
||||
/// the functions whose entry lies at those offsets into their mapping (the
|
||||
/// `name +0x..` a trap prints), for bisecting within an object. Checked at
|
||||
/// every translation, the table lookups' (`gex.lookup`) as well as tier-1's
|
||||
/// misses: JIT code calling through a pointer must not pull in objects the
|
||||
/// rules keep out.
|
||||
struct Rule {
|
||||
include: bool,
|
||||
name: String,
|
||||
range: Option<(u64, u64)>,
|
||||
}
|
||||
|
||||
static LIBS: Mutex<Vec<Rule>> = Mutex::new(Vec::new());
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_fjit_libs(ptr: u32, len: u32) {
|
||||
@@ -252,23 +263,30 @@ pub extern "C" fn gex_fjit_libs(ptr: u32, len: u32) {
|
||||
let mut v = lock(&LIBS);
|
||||
v.clear();
|
||||
for w in spec.split(',').map(str::trim).filter(|w| !w.is_empty()) {
|
||||
match w.strip_prefix('-') {
|
||||
Some(n) => v.push((false, n.to_string())),
|
||||
None => v.push((true, w.trim_start_matches('+').to_string())),
|
||||
}
|
||||
let (include, w) = match w.strip_prefix('-') {
|
||||
Some(n) => (false, n),
|
||||
None => (true, w.trim_start_matches('+')),
|
||||
};
|
||||
let hex = |x: &str| u64::from_str_radix(x.trim_start_matches("0x"), 16).ok();
|
||||
let (name, range) = match w.split_once('@') {
|
||||
Some((n, r)) => (n, r.split_once('-').and_then(|(lo, hi)| Some((hex(lo)?, hex(hi)?)))),
|
||||
None => (w, None),
|
||||
};
|
||||
v.push(Rule { include, name: name.to_string(), range });
|
||||
}
|
||||
}
|
||||
|
||||
fn lib_wanted(pc: u64) -> bool {
|
||||
let rules = lock(&LIBS).clone();
|
||||
let rules = lock(&LIBS);
|
||||
if rules.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let name = crate::object_file_at(pc);
|
||||
if rules.iter().any(|(inc, n)| !inc && *n == name) {
|
||||
let (name, off) = crate::object_file_off(pc);
|
||||
let hit = |r: &Rule| r.name == name && r.range.is_none_or(|(lo, hi)| off >= lo && off < hi);
|
||||
if rules.iter().any(|r| !r.include && hit(r)) {
|
||||
return false;
|
||||
}
|
||||
!rules.iter().any(|(inc, _)| *inc) || rules.iter().any(|(inc, n)| *inc && *n == name)
|
||||
!rules.iter().any(|r| r.include) || rules.iter().any(|r| r.include && hit(r))
|
||||
}
|
||||
|
||||
/// JIT module `n`'s bytes.
|
||||
|
||||
@@ -12390,24 +12390,24 @@ fn note_blocks(
|
||||
e.2 = e.2.max(a);
|
||||
}
|
||||
|
||||
/// The short name of whatever object holds this address, for a counter. Takes
|
||||
/// the host it already has, since the caller is inside `with_host` and a second
|
||||
/// `RefCell` borrow would panic.
|
||||
/// Like `object_name_at` but with the `+offset` into the object, so a return
|
||||
/// address on a crash stack can be run through objdump to name the function.
|
||||
/// The file name of the object mapped at `a` ("" if none).
|
||||
pub(crate) fn object_file_at(a: u64) -> String {
|
||||
/// The object file at `a` (by file name) and `a`'s offset into its mapping.
|
||||
pub(crate) fn object_file_off(a: u64) -> (String, u64) {
|
||||
with_host(|h| {
|
||||
h.kernel
|
||||
.mem
|
||||
.vmas()
|
||||
.iter()
|
||||
.find(|v| a >= v.start && a < v.end)
|
||||
.and_then(|v| v.code.as_ref().map(|(p, _)| p.rsplit('/').next().unwrap_or(p).to_string()))
|
||||
.map(|v| (v.code.as_ref().map(|(p, _)| p.rsplit('/').next().unwrap_or(p).to_string()).unwrap_or_default(), a - v.start))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// The short name of whatever object holds this address, for a counter. Takes
|
||||
/// the host it already has, since the caller is inside `with_host` and a second
|
||||
/// `RefCell` borrow would panic.
|
||||
/// Like `object_name_at` but with the `+offset` into the object, so a return
|
||||
/// address on a crash stack can be run through objdump to name the function.
|
||||
fn object_off_at(h: &Host, a: u64) -> String {
|
||||
h.kernel
|
||||
.mem
|
||||
|
||||
@@ -121,6 +121,10 @@ async function main() {
|
||||
if (q.get('elfunits')) globalThis.GEX_ELF_UNITS = q.get('elfunits');
|
||||
// `?fjitlibs=+name,-name`: the function JIT only for (+) or not for (-) those objects (a bisection aid).
|
||||
if (q.get('fjitlibs')) globalThis.GEX_FJIT_LIBS = q.get('fjitlibs');
|
||||
// `?pins=path@base+span,...` (hex): place those shared objects at fixed bases, as a
|
||||
// closure's pins do, so a unit built for one base (`?elfunits=`) is used whatever
|
||||
// else (the function JIT's heap, say) moved the arena first.
|
||||
if (q.get('pins')) globalThis.GEX_PINS = q.get('pins');
|
||||
if (q.get('dense') === '0') globalThis.GEX_DENSE = 0;
|
||||
if (q.get('dense') === '2') globalThis.GEX_DENSE = 2;
|
||||
// cgen=1|2 (prefer|require units), in the unguarded flavor of i386 units
|
||||
|
||||
+13
-1
@@ -801,7 +801,7 @@ export async function runProgram(
|
||||
// out an address inside a pinned object and the image lands somewhere
|
||||
// its own translated code does not expect.
|
||||
opts: {
|
||||
cwd, tty, uid, recompileObjects, heartbeat, link, dirs, modes, hard, env, pins: closure?.pins,
|
||||
cwd, tty, uid, recompileObjects, heartbeat, link, dirs, modes, hard, env, pins: pinsFrom(closure?.pins, globalThis.GEX_PINS),
|
||||
// `?strace=1`, and every child inherits it: see `#opts` in gex.mjs.
|
||||
strace: globalThis.GEX_STRACE ? 1 : undefined,
|
||||
// `?stracepid=N`: narrate that one process, which is affordable.
|
||||
@@ -843,3 +843,15 @@ export async function runProgram(
|
||||
|
||||
export { TerminalEnv };
|
||||
export { loadKernel as globalKernel };
|
||||
|
||||
/** A closure's pins plus the page's `?pins=path@base+span,...` (hex; the query wins). */
|
||||
function pinsFrom(closurePins, spec) {
|
||||
if (!spec) return closurePins;
|
||||
const out = { ...(closurePins ?? {}) };
|
||||
for (const w of spec.split(',').map((x) => x.trim()).filter(Boolean)) {
|
||||
const m = /^(.+)@(?:0x)?([0-9a-f]+)(?:\+(?:0x)?([0-9a-f]+))?$/i.exec(w);
|
||||
if (!m) { console.error(`[gex] ?pins: cannot read ${w}`); continue; }
|
||||
out[m[1]] = { base: Number.parseInt(m[2], 16), span: m[3] ? Number.parseInt(m[3], 16) : 0 };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in new issue
Block a user