3 Commits
5 changed files with 144 additions and 6 deletions
+49
View File
@@ -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::<Block>`(`patch()` 里 `PakBuilder` 读 `mods_P.pak`) | 读 pak 阶段 |
| `+0x54351` | `crossbeam_channel::Sender<tracing_appender::Msg>::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"
```
+34 -5
View File
@@ -17,7 +17,7 @@ Grab the tarball from the [latest release](https://gitea.mygoband.com/carrydela/
check `sha256sum`, extract, put `mint` on your PATH: check `sha256sum`, extract, put `mint` on your PATH:
```bash ```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 sha256sum -c mint-linux-x86_64.tar.gz.sha256 # download the .sha256 alongside
tar -xzf mint-linux-x86_64.tar.gz tar -xzf mint-linux-x86_64.tar.gz
install -m 0755 mint /usr/local/bin/ install -m 0755 mint /usr/local/bin/
@@ -33,14 +33,43 @@ mint --version
## Build notes ## Build notes
Built from `trumank/mint` main (v0.2.10) with: Built from `trumank/mint` main (v0.2.10) with the **default `hook` feature enabled**:
```bash ```bash
cargo +nightly-2026-04-24 build --package mint --no-default-features --release # build-time deps (NOT needed at runtime)
apt install -y mingw-w64
rustup target add x86_64-pc-windows-gnu --toolchain nightly-2026-04-24
cargo +nightly-2026-04-24 build --package mint --release
``` ```
The default `hook` feature is a Windows game-injection cdylib (`x86_64-pc-windows-gnu`) and is The Linux MINT executable is built with the default `hook` feature enabled.
intentionally excluded on Linux. Binary is stripped. The hook itself is cross-compiled for `x86_64-pc-windows-gnu` because
Deep Rock Galactic runs as a Windows application under Proton.
MinGW-w64 is therefore required **at build time only, not at runtime** —
the hook DLL is embedded into the Linux binary via Cargo artifact dependency
(`include_bytes!(env!("CARGO_CDYLIB_FILE_HOOK_hook"))`) and written by MINT
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 <hook.dll> 0x521f15 0x57bff 0x54351
# or directly:
x86_64-w64-mingw32-addr2line -Cfipe <hook.dll> 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 ## Files
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -44,7 +44,7 @@ fi
# GUI runtime libs (loaded at runtime by egui/winit). Warn if absent. # GUI runtime libs (loaded at runtime by egui/winit). Warn if absent.
missing=() missing=()
for probe in libxcb.so.1 libxkbcommon.so.0 libGL.so.1 libwayland-client.so.0 libx11.so.6; do for probe in libxcb.so.1 libxkbcommon.so.0 libGL.so.1 libwayland-client.so.0 libX11.so.6; do
ldconfig -p 2>/dev/null | grep -q "$probe" || missing+=("$probe") ldconfig -p 2>/dev/null | grep -q "$probe" || missing+=("$probe")
done done
if [ "${#missing[@]}" -gt 0 ]; then if [ "${#missing[@]}" -gt 0 ]; then
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3.12
"""Symbolize mint hook DLL crash RVAs.
Usage:
python3.12 symbolize_hook.py <hook.dll> <rva> [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()