Files
NX2navi/tests/crash_stack_check.py
chrg6 dd78132e6d Tell a crash on the RTSS thread as the game's own call of RivaTuner's library, and skip the restore there.
main.c foreign_hook matches "hook", so a crash in RTSSHooks64.dll got the line that the library is not part of the
game and that a recording, overlay or capture program put it there, also when the game's own thread that sets the
frame limit (rtss_limit.c's "rtss" worker) had loaded and called it. The one RTSS crash on record (issue #231)
faulted in ntdll on that thread, where no note applied. And the crash handler then called rtss_limit_restore from
that thread, whose own lock it holds (SRW locks are not recursive), so the restore's trylocks spent a second.

The platform layer keeps the name a thread was made with in the thread itself (plat_thread_name, copied at its
start: a detached thread's struct may go before it ends). main.c crash_module_note picks the note: on the "rtss"
thread, whatever the module, the line says the fault is in the game's calls of RTSS's library or around them and
that rtss = 0 keeps the game from calling it; on other threads RTSSHooks64.dll stays foreign, as it is RTSS's
overlay there. The restore is skipped on the rtss thread. rtss_limit.c is unchanged: the thread is known by the name
it is spawned with. Crash text only, besides the skipped restore.

Not reproduced before the change. tests/foreign_hook_check.py checks the note's choice (RTSSHooks64.dll and ntdll on
the RTSS thread: the RTSS note; RTSSHooks64.dll elsewhere: foreign; a driver: none), and tests/crash_stack_check.py
that the crash report reads the faulting thread's name. They, thread_end_check, fatal_exit_check and the compile
checks of every runtime file pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:43:23 -05:00

72 lines
3.1 KiB
Python

"""Build and run tests/crash_stack_check.c: a stack overflow on a thread and on a fiber made by the platform layer
(runtime/platform/win32.c thread_main, fiber_start, plat_stack_guarantee) still gets its whole crash report, a file
write and three 2 KiB log lines, and ends with the report's code 71. Without the stack kept back the report faulted
again and the process ended with an access violation and no line. No game files.
Usage: crash_stack_check.py
"""
import os
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
LAYOUT = '''// A stand-in for the generated layout.h (tests/crash_stack_check.py).
#pragma once
#define MOD_MAIN_BASE 0x7000000000ull
#define MOD_MAIN_SIZE 0x4ea0000ull
'''
def compiler():
if os.environ.get('CC'):
return os.environ['CC']
if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists():
return str(MSYS_BIN / 'clang.exe')
return shutil.which('clang') or 'clang'
def main():
if os.name != 'nt':
print('crash_stack_check: Windows only')
return 0
work = ROOT / 'build' / 'test' / 'crash_stack'
work.mkdir(parents=True, exist_ok=True)
(work / 'layout.h').write_text(LAYOUT)
exe = work / 'crash_stack_check.exe'
env = dict(os.environ)
if MSYS_BIN.exists():
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
rt = ROOT / 'runtime'
plat = [rt / 'platform' / 'win32.c', rt / 'platform' / 'common.c']
libs = ['-lbcrypt', '-lws2_32', '-lpsapi', '-lwinmm', '-ldbghelp', '-lshell32', '-lole32', '-luser32', '-ladvapi32']
subprocess.check_call([compiler(), '-O1', '-g', '-Wall', '-Wno-unused-function', '-fno-strict-aliasing',
'-march=x86-64-v3', '-I', str(rt), '-I', str(work), str(ROOT / 'tests' / 'crash_stack_check.c'),
*map(str, plat), '-o', str(exe), '-lm', *libs], env=env)
bad = 0
for mode in ('thread', 'fiber'):
note = work / f'note_{mode}.txt'
note.unlink(missing_ok=True)
p = subprocess.run([str(exe), mode, str(note)], env=env, capture_output=True, text=True, errors='replace',
timeout=60)
lines = [f'report line {i}' for i in (1, 2, 3)]
missing = [x for x in lines if x not in p.stdout]
# platform.h plat_thread_name, which the crash report reads: the spawned thread's name; the fiber runs on the
# main thread, which the platform layer did not make
named = ('on thread deep' if mode == 'thread' else 'on thread (unnamed)') in p.stdout
ok = p.returncode == 71 and not missing and 'stack overflow' in p.stdout and 'the file written: 1' in p.stdout and named
print(f'a stack overflow on a {mode}: exit code {p.returncode & 0xffffffff:#x} (want 0x47), '
f'{3 - len(missing)} of 3 report lines: {"ok" if ok else "FAIL"}', flush=True)
if not ok:
print(p.stdout[-2000:], p.stderr[-2000:])
bad += not ok
print(f'crash_stack_check: {"FAILED" if bad else "ok"}')
return 1 if bad else 0
if __name__ == '__main__':
sys.exit(main())