mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
browser: a wasm32 program's fork child installs the module at the parent's table base
A wasm32 program exec'd by another (bash running make, apt running dpkg) gets a table base past its predecessor's range; its fork child published the module fresh at the first base, so every function pointer in the data it copied was off and the child trapped (function signature mismatch in __gex_fork_drive). The fork record carries the base (gex_wasmprog_base) and the child publishes there (FuncMap::publish_at, gex_wasmprog_adopt_at). 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
6853931bbd
commit
bdd34eb3f0
3 files changed
+60
-9
No files matched your search
@@ -134,6 +134,20 @@ impl FuncMap {
|
||||
n
|
||||
}
|
||||
|
||||
/// [`FuncMap::publish`] at `base` when nothing has taken it yet: a fork's
|
||||
/// child of a wasm32 program installs the parent's module where the
|
||||
/// parent had it, because the function pointers in the data it copied
|
||||
/// are `__table_base + i` for the parent's base (an exec'd program's base
|
||||
/// is past its predecessor's range, which is never reused). Returns
|
||||
/// whether the install got that base.
|
||||
pub fn publish_at(&mut self, base: u32, len: u32, module: u32, tier: Tier, funcs: &[(u64, u32)]) -> (u32, bool) {
|
||||
if base >= self.next {
|
||||
self.next = base;
|
||||
}
|
||||
let n = self.publish(len, module, tier, funcs);
|
||||
(n, self.installs[n as usize].base == base)
|
||||
}
|
||||
|
||||
/// Map more addresses to slots of install `install`: one module serving
|
||||
/// several images (Wine's PE side as one module, docs/wine-native.md
|
||||
/// §3.14, bound DLL by DLL as each maps). Nothing changes in any table
|
||||
|
||||
@@ -2017,7 +2017,10 @@ export class Gex {
|
||||
child.brk ?? 0n,
|
||||
);
|
||||
if (child.compat) e.gex_set_compat?.(1);
|
||||
if (child.wasm32) e.gex_wasmprog_adopt?.(BigInt(child.wasm32));
|
||||
if (child.wasm32) {
|
||||
if (child.wasm32Base && e.gex_wasmprog_adopt_at) e.gex_wasmprog_adopt_at(BigInt(child.wasm32), child.wasm32Base);
|
||||
else e.gex_wasmprog_adopt?.(BigInt(child.wasm32));
|
||||
}
|
||||
// The parent's block numbering carries on in the child (its low region),
|
||||
// over slot maps of its own (`dense::child`).
|
||||
this.#dense = !!(child.opts ?? {}).dense && !!this.#guest.exports.blocks && !!e.gex_set_dense;
|
||||
@@ -2285,6 +2288,9 @@ export class Gex {
|
||||
compat: e.gex_compat?.() ?? 0,
|
||||
// A wasm32 program's entry: the child installs the same module.
|
||||
wasm32: e.gex_wasmprog_entry?.() ?? 0n,
|
||||
// And where its module sits in the function table: the child's copy
|
||||
// of the data holds function pointers for that base.
|
||||
wasm32Base: e.gex_wasmprog_base?.() ?? 0,
|
||||
// Where the parent's heap had reached. Without the number the child's
|
||||
// kernel thinks it has no heap and grows one from zero, over its contexts.
|
||||
brk: e.gex_fork_brk?.() ?? 0n,
|
||||
|
||||
@@ -29,10 +29,13 @@ struct Prog {
|
||||
}
|
||||
|
||||
static PROGS: Mutex<Vec<Prog>> = Mutex::new(Vec::new());
|
||||
/// The program to publish (entry, whether its data is fresh).
|
||||
static STAGED: Mutex<Option<(u64, bool)>> = Mutex::new(None);
|
||||
/// The program to publish (entry, whether its data is fresh, the table base
|
||||
/// it must have: a fork's child's, the parent's).
|
||||
static STAGED: Mutex<Option<(u64, bool, Option<u32>)>> = Mutex::new(None);
|
||||
/// This process's program's entry while it is a wasm32 one, for a fork.
|
||||
static CURRENT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
/// And its module's `__table_base`.
|
||||
static CURRENT_BASE: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
|
||||
pub(crate) const MAGIC: &[u8; 8] = b"GEXWASM\0";
|
||||
|
||||
@@ -52,7 +55,7 @@ pub(crate) fn is_wasm32(program: &gex_ld::Program) -> bool {
|
||||
/// it runs if it is a wasm32 one.
|
||||
pub(crate) fn stage(program: Option<&gex_ld::Program>) {
|
||||
CURRENT.store(0, Ordering::Relaxed);
|
||||
*lock(&STAGED) = program.filter(|p| is_wasm32(p)).map(|p| (p.exe.entry, true));
|
||||
*lock(&STAGED) = program.filter(|p| is_wasm32(p)).map(|p| (p.exe.entry, true, None));
|
||||
}
|
||||
|
||||
/// A fork's child of a wasm32 program at `entry`: the same module, over the
|
||||
@@ -60,10 +63,26 @@ pub(crate) fn stage(program: Option<&gex_ld::Program>) {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_wasmprog_adopt(entry: u64) {
|
||||
if entry != 0 {
|
||||
*lock(&STAGED) = Some((entry, false));
|
||||
*lock(&STAGED) = Some((entry, false, None));
|
||||
}
|
||||
}
|
||||
|
||||
/// [`gex_wasmprog_adopt`] with the parent's table base
|
||||
/// ([`gex_wasmprog_base`]): the data the child copied holds function
|
||||
/// pointers for that base, so the module goes there.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_wasmprog_adopt_at(entry: u64, base: u32) {
|
||||
if entry != 0 {
|
||||
*lock(&STAGED) = Some((entry, false, (base != 0).then_some(base)));
|
||||
}
|
||||
}
|
||||
|
||||
/// The running wasm32 program's `__table_base` (the fork record's).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_wasmprog_base() -> u32 {
|
||||
if CURRENT.load(Ordering::Relaxed) == 0 { 0 } else { CURRENT_BASE.load(Ordering::Relaxed) }
|
||||
}
|
||||
|
||||
/// The running program's entry if it is a wasm32 one (the fork record's).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_wasmprog_entry() -> u64 {
|
||||
@@ -73,8 +92,8 @@ pub extern "C" fn gex_wasmprog_entry() -> u64 {
|
||||
/// Publish the staged program: its provider module number, or -1.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_wasmprog_serve() -> i32 {
|
||||
let Some((entry, relocate)) = lock(&STAGED).take() else { return -1 };
|
||||
match publish(entry, relocate) {
|
||||
let Some((entry, relocate, at)) = lock(&STAGED).take() else { return -1 };
|
||||
match publish(entry, relocate, at) {
|
||||
Ok(n) => n as i32,
|
||||
Err(e) => {
|
||||
crate::warn_line(&format!("gex: wasm32 program: {e}\n"));
|
||||
@@ -84,7 +103,7 @@ pub extern "C" fn gex_wasmprog_serve() -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
fn publish(entry: u64, relocate: bool) -> Result<u32, String> {
|
||||
fn publish(entry: u64, relocate: bool, want_base: Option<u32>) -> Result<u32, String> {
|
||||
let mem = crate::guest_bytes();
|
||||
let at = entry.wrapping_sub(0x40) as usize;
|
||||
let h = mem.get(at..at + 36).ok_or("header outside memory")?;
|
||||
@@ -101,7 +120,19 @@ fn publish(entry: u64, relocate: bool) -> Result<u32, String> {
|
||||
let slot = |n: &str| names.iter().position(|s| s.as_deref() == Some(n)).map(|k| k as u32).ok_or(format!("{n} is not in its table"));
|
||||
let pairs = vec![(entry, slot("__gex_start")?), (thread_entry, slot("__gex_thread_start")?)];
|
||||
let number = lock(&PROVIDERS).len() as u32;
|
||||
ftab(|m| m.publish(names.len() as u32, PROVIDER | number, Tier::Unit, &pairs));
|
||||
let install = ftab(|m| match want_base {
|
||||
Some(base) => {
|
||||
let (n, ok) = m.publish_at(base, names.len() as u32, PROVIDER | number, Tier::Unit, &pairs);
|
||||
if !ok {
|
||||
crate::warn_line(&format!(
|
||||
"gex: wasm32 fork child: table base {base} is taken, its function pointers will be wrong\n"
|
||||
));
|
||||
}
|
||||
n
|
||||
}
|
||||
None => m.publish(names.len() as u32, PROVIDER | number, Tier::Unit, &pairs),
|
||||
});
|
||||
CURRENT_BASE.store(ftab(|m| m.install(install).base), Ordering::Relaxed);
|
||||
let addrs: Vec<u64> = pairs.iter().map(|&(a, _)| a).collect();
|
||||
for &a in &addrs {
|
||||
gex_kernel::ftab::fcache_clear(crate::guest_bytes(), a);
|
||||
|
||||
Reference in new issue
Block a user