diff --git a/CRASH-NOTES.md b/CRASH-NOTES.md new file mode 100644 index 0000000..f39dd7a --- /dev/null +++ b/CRASH-NOTES.md @@ -0,0 +1,49 @@ +# MINT HOOK FAILED — Proton 崩溃分析 + +## 结论 +hook DLL 成功被 Proton 加载,但在**初始化阶段**(`DllMain` → `init` → `patch()`)发生 +`EXCEPTION_ACCESS_VIOLATION (0xc0000005)`,crash 落在 native `X3DAudio1_7.dll` 内部。 +不是缺 DLL、不是 WINEDLLOVERRIDES、不是 MinGW runtime 问题。 + +## 符号化结果(debug 构建 `hook-debug-symbolized.dll`,image base 0x180000000) + +| RVA | 包含函数 | 含义 | +|---|---|---| +| `+0x521f15` | `std::thread::spawnhook::run_spawn_hooks` (+0x165) | **崩溃点**:首个线程 spawn 时的 std spawn-hooks 钩子,`usize::saturating_add` 内联叶子帧 | +| `+0x57bff` | `repak::ext::ReadExt::read_array::`(`patch()` 里 `PakBuilder` 读 `mods_P.pak`) | 读 pak 阶段 | +| `+0x54351` | `crossbeam_channel::Sender::drop` | tracing_appender **non_blocking 日志 worker** 的 channel 析构 | +| `+0x15966` | `D3DPERF_QueryRepeatFrame`(proxy_dll 导出 thunk,hook/src/lib.rs:12) | DLL 导出面 | +| `+0x2c221` | `tracing_subscriber::fmt::Timings` extension `get` | 日志 subscriber 初始化 | + +## 指向 +5 个点全部落在**初始化序列**:`proxy_dll!([x3daudio1_7, d3d9], init)` 的 `init` → +`patch()` → `mint_lib::setup_logging(bin_dir/mint_hook.log)`(`tracing_appender::non_blocking`) +→ 读 `Content/Paks/mods_P.pak` → patternsleuth 解析 → `hooks::initialize()`。 + +崩溃点 `run_spawn_hooks` 说明:**hook 在 DllMain(LoadLibrary,持 loader lock)期间 +spawn 了线程**(tracing_appender 日志 worker,或 tokio/tokio-console 相关)。 +Windows/Proton 下 loader lock 内 spawn 线程是经典 AV/死锁源 —— 游戏主进程首次 +`LoadLibrary("x3daudio1_7")` 时触发 `init`,此时 spawn 线程在 Proton Experimental 下崩。 + +## 修复方向(按优先级) +1. **延迟日志/线程初始化**:`patch()` 里不要在 `init`(DllMain 上下文)里 + `setup_logging`/spawn 任何线程;把 non_blocking guard 的创建挪到 + `hooks::initialize` 里真正进入游戏线程之后(如 `FEngineLoop::Init`/tick 钩子首次触发时)。 +2. 若 tokio 相关线程也在 init 期创建,同样挪出 DllMain 上下文。 +3. 上游 issue:https://github.com/trumank/mint/issues/307 (Linux 使用场景)。 + +## 复现符号化 +```bash +# 带符号 DLL(本 release 附件 hook-debug-symbolized.dll,debug=2 构建,代码布局与发布版一致) +x86_64-w64-mingw32-addr2line -Cfipe hook-debug-symbolized.dll \ + 0x180521f15 0x180057bff 0x180054351 0x180015966 0x18002c221 +# 或用仓库里的通用脚本 +python3.12 symbolize_hook.py hook-debug-symbolized.dll 0x521f15 0x57bff 0x54351 0x15966 0x2c221 +``` + +## 临时恢复游戏 +```bash +mv "/data/SteamLibrary/steamapps/common/Deep Rock Galactic/FSD/Binaries/Win64/x3daudio1_7.dll" \ + "/data/SteamLibrary/steamapps/common/Deep Rock Galactic/FSD/Binaries/Win64/x3daudio1_7.dll.disabled" +# Steam 启动参数恢复 -disablemodding,去掉 WINEDLLOVERRIDES="x3daudio1_7=n,b" +``` diff --git a/README.md b/README.md index 51ec04a..b78ba09 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Grab the tarball from the [latest release](https://gitea.mygoband.com/carrydela/ check `sha256sum`, extract, put `mint` on your PATH: ```bash -curl -fLO https://gitea.mygoband.com/carrydela/mint-linux/releases/download/v0.1.0/mint-linux-x86_64.tar.gz +curl -fLO https://gitea.mygoband.com/carrydela/mint-linux/releases/download/v0.1.1/mint-linux-x86_64.tar.gz sha256sum -c mint-linux-x86_64.tar.gz.sha256 # download the .sha256 alongside tar -xzf mint-linux-x86_64.tar.gz install -m 0755 mint /usr/local/bin/ @@ -52,6 +52,25 @@ the hook DLL is embedded into the Linux binary via Cargo artifact dependency itself to `FSD/Binaries/Win64/x3daudio1_7.dll` when installing mods. Binary is stripped. +## Symbol build (crash triage) + +To resolve crash offsets (e.g. `X3DAudio1_7.dll + 0x521F15` from a Proton crash log) +into Rust symbols, build the hook with debug info and symbolize: + +```bash +# one-off profile override (see [profile.release.package.hook] in Cargo.toml) +cargo +nightly-2026-04-24 build --package mint --release + +# symbolize RVAs against the debug hook.dll (image base 0x180000000) +python3.12 symbolize_hook.py 0x521f15 0x57bff 0x54351 +# or directly: +x86_64-w64-mingw32-addr2line -Cfipe 0x180521f15 0x180057bff 0x180054351 +``` + +A debug-symbolized DLL matching the shipped code layout is attached to the +release as `hook-debug-symbolized.dll`. See `CRASH-NOTES.md` for the +MINT HOOK FAILED analysis. + ## Files - `install.sh` — one-click installer (see above) diff --git a/hook-debug-symbolized.dll b/hook-debug-symbolized.dll new file mode 100755 index 0000000..61b6831 Binary files /dev/null and b/hook-debug-symbolized.dll differ diff --git a/symbolize_hook.py b/symbolize_hook.py new file mode 100644 index 0000000..3a03a6e --- /dev/null +++ b/symbolize_hook.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3.12 +"""Symbolize mint hook DLL crash RVAs. + +Usage: + python3.12 symbolize_hook.py [rva ...] + +RVAs are relative to the DLL's image base (e.g. from a wine/proton crash +line "X3DAudio1_7.dll + 0x521F15"). The script prints the containing symbol +for each RVA. Run it against a debug-symbol hook build (see README "Symbol +build"), NOT the stripped release artifact. + +Optional env: + HOOK_IMAGE_BASE override base (default 0x180000000) +""" +import bisect, os, re, subprocess, sys + +def main(): + if len(sys.argv) < 3: + print(__doc__); sys.exit(1) + dll = sys.argv[1] + base = int(os.environ.get("HOOK_IMAGE_BASE", "0x180000000"), 16) + rv = [int(x, 16) for x in sys.argv[2:]] + + sym_out = subprocess.run( + ["x86_64-w64-mingw32-nm", "-n", "-C", dll], + capture_output=True, text=True) + syms = [] + for line in sym_out.stdout.splitlines(): + m = re.match(r"^([0-9a-f]+) [TtDdWw] (.+)$", line) + if m: + a = int(m.group(1), 16) + if a >= base: + syms.append((a, m.group(2).strip())) + if not syms: + print("no symbols found — is this a debug build? (see README)"); sys.exit(2) + addrs = [a for a, _ in syms] + syms.sort() + + for r in rv: + va = base + r + i = bisect.bisect_right(addrs, va) - 1 + if i < 0: + print(f"+{r:06x} NOT FOUND"); continue + fa, name = syms[i] + leaf = subprocess.run( + ["x86_64-w64-mingw32-addr2line", "-Cfie", dll, hex(va)], + capture_output=True, text=True).stdout + leaf_frames = [l for l in leaf.splitlines() if "at " in l] + print(f"=== +{r:06x} (va {va:#x}) ===") + print(f" container: {name[:160]}{'…' if len(name) > 160 else ''}") + print(f" +{va - fa:#x} into symbol") + if leaf_frames: + print(f" leaf: {leaf_frames[0][:160]}") + # outermost non-inlined frame + if leaf_frames: + print(f" outer: {leaf_frames[-1][:160]}") + print() + +if __name__ == "__main__": + main()