Skip to content

Commit 0142da7

Browse files
cjpaisclaude
andcommitted
diag(B): self-contained int-÷0 backtrace + Release-codegen diag workflow
The Windows/MSVC integer divide-by-zero (0xC0000094) faults ANY whisper run, not just a cancelled one (the alphabetically-first cancel test merely masked that it crashes before any run completes). None of our source integer divisions can be zero (all config-derived or guarded), so the ÷0 is in ggml, reached by our graph on MSVC Release codegen — needs a faulting backtrace to localize. - whisper_e2e_smoke.cpp: TRANSCRIBE_DIAG_FAULT_TRACE installs a vectored exception handler that symbolizes the faulting IP + full stack via DbgHelp. Self-contained: needs no cdb on the runner, only the Release+/Z7 PDB. - diag-b.yml: push-triggered, path-scoped. Builds the e2e test in the SAME codegen as the failing cargo build (CMAKE_BUILD_TYPE=Release, matching the -sys build.rs cfg.profile("Release")) plus /Z7 + linker /DEBUG for symbols, then runs a PLAIN transcription with the tracer (and under cdb if present). Diagnostic scaffolding — remove (with the dbghelp link) once "B" is fixed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 82413af commit 0142da7

3 files changed

Lines changed: 184 additions & 0 deletions

File tree

‎.github/workflows/diag-b.yml‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: diag-b
2+
3+
# One-off diagnostic for known-issues.md "B": the Windows/MSVC integer
4+
# divide-by-zero (STATUS_INTEGER_DIVIDE_BY_ZERO, 0xC0000094) that faults ANY
5+
# whisper run on Windows (not just a cancelled one). Builds the C++ whisper
6+
# e2e test in the SAME codegen as the failing cargo build — CMAKE_BUILD_TYPE=
7+
# Release (the -sys build.rs pins cfg.profile("Release")) — plus /Z7 embedded
8+
# CodeView and a linker /DEBUG so the exe carries a PDB, then runs a PLAIN
9+
# transcription (no cancel) two ways:
10+
# 1. in-process: TRANSCRIBE_DIAG_FAULT_TRACE=1 installs a vectored exception
11+
# handler that symbolizes the faulting IP + stack via DbgHelp. Needs no
12+
# external debugger on the runner.
13+
# 2. under cdb if the Windows SDK debugger is present (belt-and-suspenders).
14+
#
15+
# Push-triggered (workflow_dispatch won't fire from a non-default branch) and
16+
# path-scoped so it only runs when the diagnostic itself changes.
17+
#
18+
# DELETE this workflow, the dbghelp link, and the TRANSCRIBE_DIAG_FAULT_TRACE
19+
# block in tests/whisper_e2e_smoke.cpp once "B" is fixed.
20+
21+
on:
22+
push:
23+
branches: [rust-bindings]
24+
paths:
25+
- ".github/workflows/diag-b.yml"
26+
- "tests/whisper_e2e_smoke.cpp"
27+
- "tests/CMakeLists.txt"
28+
- "src/arch/whisper/**"
29+
- "src/transcribe-mel.cpp"
30+
- "include/transcribe.h"
31+
32+
concurrency:
33+
group: diag-b-${{ github.ref }}
34+
cancel-in-progress: true
35+
36+
jobs:
37+
diag:
38+
name: diag-b (windows)
39+
runs-on: blacksmith-2vcpu-windows-2025
40+
timeout-minutes: 40
41+
env:
42+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
43+
steps:
44+
- uses: actions/checkout@v6
45+
- uses: astral-sh/setup-uv@v8.2.0
46+
- name: Set up MSVC
47+
uses: ilammy/msvc-dev-cmd@v1
48+
with:
49+
arch: x64
50+
- name: Install build deps
51+
shell: pwsh
52+
run: |
53+
choco install ninja --no-progress -y
54+
vcpkg install zlib:x64-windows-static-md
55+
$zlibPrefix = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-static-md" -replace '\\','/'
56+
Add-Content $env:GITHUB_ENV "CMAKE_PREFIX_PATH=$zlibPrefix"
57+
- uses: ./.github/actions/fetch-canary
58+
with:
59+
hf-token: ${{ secrets.HF_TOKEN }}
60+
- name: Configure + build the e2e test (Release codegen + /Z7 symbols)
61+
shell: bash
62+
run: |
63+
# Release == the failing cargo build's native codegen. /Z7 embeds
64+
# CodeView in each .obj (Ninja-safe, no /Zi PDB contention); /DEBUG
65+
# makes the linker emit a merged exe PDB so file:line resolves for
66+
# both transcribe and ggml frames. advapi32 for ggml-cpu's registry
67+
# CPU probe (the static consumer must pull it; F is fixed so the
68+
# header no longer forces dllimport).
69+
cmake -B build-diag -G Ninja \
70+
-DCMAKE_BUILD_TYPE=Release \
71+
-DCMAKE_C_FLAGS=/Z7 -DCMAKE_CXX_FLAGS=/Z7 \
72+
-DCMAKE_EXE_LINKER_FLAGS="/DEBUG advapi32.lib" \
73+
-DTRANSCRIBE_BUILD_REAL_MODEL_TESTS=ON \
74+
-DTRANSCRIBE_USE_OPENMP=OFF
75+
cmake --build build-diag --target transcribe_whisper_e2e_smoke -j
76+
- name: Reproduce + symbolize in-process (vectored handler)
77+
shell: pwsh
78+
run: |
79+
$env:TRANSCRIBE_WHISPER_MODEL = $env:TRANSCRIBE_SMOKE_MODEL
80+
$env:TRANSCRIBE_DIAG_FAULT_TRACE = "1"
81+
$exe = (Get-ChildItem -Recurse -Filter transcribe_whisper_e2e_smoke.exe build-diag | Select-Object -First 1).FullName
82+
Write-Host "exe: $exe"
83+
Write-Host "model: $env:TRANSCRIBE_WHISPER_MODEL"
84+
Write-Host "===== in-process fault trace ====="
85+
& $exe
86+
Write-Host "exit code: $LASTEXITCODE (94 == fault captured by the handler)"
87+
exit 0
88+
- name: Reproduce under cdb (if the SDK debugger is present)
89+
shell: pwsh
90+
run: |
91+
$env:TRANSCRIBE_WHISPER_MODEL = $env:TRANSCRIBE_SMOKE_MODEL
92+
Remove-Item Env:\TRANSCRIBE_DIAG_FAULT_TRACE -ErrorAction SilentlyContinue
93+
$exe = (Get-ChildItem -Recurse -Filter transcribe_whisper_e2e_smoke.exe build-diag | Select-Object -First 1).FullName
94+
$cdb = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
95+
if (!(Test-Path $cdb)) {
96+
$cdb = (Get-ChildItem -Recurse -Filter cdb.exe "C:\Program Files (x86)\Windows Kits" -ErrorAction SilentlyContinue | Select-Object -First 1).FullName
97+
}
98+
if (-not $cdb) { Write-Host "::warning::cdb not found; in-process trace above is authoritative"; exit 0 }
99+
Write-Host "cdb: $cdb"
100+
# On the int-divide first-chance: enable source lines, print a numbered
101+
# stack with locals + the faulting instruction, then quit.
102+
$cmd = '.lines -e; sxe -c ".echo ==FAULT==; r; .echo ==STACK==; kn 50; .echo ==FRAME0==; .frame /r 0; dv /t /v; .echo ==DISASM==; u @rip L4; .echo ==DONE==; q" 0xc0000094; g; q'
103+
& $cdb -g -G -c $cmd $exe
104+
exit 0

‎tests/CMakeLists.txt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,12 @@ if(TRANSCRIBE_BUILD_REAL_MODEL_TESTS)
720720
target_link_libraries(transcribe_whisper_e2e_smoke
721721
PRIVATE transcribe transcribe-common-example)
722722

723+
# DbgHelp backs the TRANSCRIBE_DIAG_FAULT_TRACE int-÷0 backtrace handler
724+
# (known-issues.md "B"). Drop with that diagnostic.
725+
if(WIN32)
726+
target_link_libraries(transcribe_whisper_e2e_smoke PRIVATE dbghelp)
727+
endif()
728+
723729
target_compile_definitions(transcribe_whisper_e2e_smoke PRIVATE
724730
"TRANSCRIBE_TEST_SAMPLES_DIR=\"${CMAKE_SOURCE_DIR}/samples\"")
725731

‎tests/whisper_e2e_smoke.cpp‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
#include <thread>
2323
#include <vector>
2424

25+
#if defined(_WIN32)
26+
# include <windows.h>
27+
# include <dbghelp.h>
28+
#endif
29+
2530
#ifndef TRANSCRIBE_TEST_SAMPLES_DIR
2631
# define TRANSCRIBE_TEST_SAMPLES_DIR "samples"
2732
#endif
@@ -56,9 +61,78 @@ bool file_exists(const std::string & path) {
5661
return ::stat(path.c_str(), &st) == 0;
5762
}
5863

64+
#if defined(_WIN32)
65+
// Diagnostic (known-issues.md "B"): on a Windows/MSVC integer divide-by-zero
66+
// (STATUS_INTEGER_DIVIDE_BY_ZERO, 0xC0000094) the run faults with no useful
67+
// trace in CI. This vectored exception handler symbolizes the faulting IP and
68+
// the whole stack via DbgHelp — self-contained, so no external debugger (cdb)
69+
// needs to be present on the runner; it only needs the Release+/Z7 PDB. Gated
70+
// by TRANSCRIBE_DIAG_FAULT_TRACE so normal/native-ci runs are untouched. Remove
71+
// this block (and the dbghelp link + diag-b.yml) once "B" is fixed.
72+
void diag_resolve(HANDLE proc, DWORD64 addr, const char * tag) {
73+
char symbuf[sizeof(SYMBOL_INFO) + 512] = {};
74+
auto * sym = reinterpret_cast<SYMBOL_INFO *>(symbuf);
75+
sym->SizeOfStruct = sizeof(SYMBOL_INFO);
76+
sym->MaxNameLen = 511;
77+
DWORD64 sdisp = 0;
78+
const bool have_sym = SymFromAddr(proc, addr, &sdisp, sym) != FALSE;
79+
80+
IMAGEHLP_LINE64 line {};
81+
line.SizeOfStruct = sizeof(line);
82+
DWORD ldisp = 0;
83+
const bool have_line = SymGetLineFromAddr64(proc, addr, &ldisp, &line) != FALSE;
84+
85+
std::fprintf(stderr, " %-12s 0x%016llx %s+0x%llx", tag,
86+
static_cast<unsigned long long>(addr),
87+
have_sym ? sym->Name : "<no-symbol>",
88+
static_cast<unsigned long long>(sdisp));
89+
if (have_line) {
90+
std::fprintf(stderr, " (%s:%lu)", line.FileName, line.LineNumber);
91+
}
92+
std::fprintf(stderr, "\n");
93+
}
94+
95+
LONG WINAPI diag_fault_trace(EXCEPTION_POINTERS * ep) {
96+
const DWORD code = ep->ExceptionRecord->ExceptionCode;
97+
if (code != EXCEPTION_INT_DIVIDE_BY_ZERO && code != EXCEPTION_INT_OVERFLOW) {
98+
return EXCEPTION_CONTINUE_SEARCH; // not ours — let normal handling run
99+
}
100+
HANDLE proc = GetCurrentProcess();
101+
SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
102+
SymInitialize(proc, nullptr, TRUE);
103+
104+
std::fprintf(stderr, "\n==== FAULT 0x%08lx (%s) ====\n",
105+
static_cast<unsigned long>(code),
106+
code == EXCEPTION_INT_DIVIDE_BY_ZERO ? "INT_DIVIDE_BY_ZERO"
107+
: "INT_OVERFLOW");
108+
diag_resolve(proc,
109+
reinterpret_cast<DWORD64>(ep->ExceptionRecord->ExceptionAddress),
110+
"FAULTING-IP");
111+
std::fprintf(stderr, "---- backtrace (top frames first) ----\n");
112+
void * frames[64];
113+
const USHORT n = CaptureStackBackTrace(0, 64, frames, nullptr);
114+
for (USHORT i = 0; i < n; ++i) {
115+
char idx[16];
116+
std::snprintf(idx, sizeof(idx), "#%-2u", i);
117+
diag_resolve(proc, reinterpret_cast<DWORD64>(frames[i]), idx);
118+
}
119+
std::fflush(stderr);
120+
TerminateProcess(proc, 94);
121+
return EXCEPTION_CONTINUE_EXECUTION; // unreached (process is gone)
122+
}
123+
#endif // _WIN32
124+
59125
} // namespace
60126

61127
int main() {
128+
#if defined(_WIN32)
129+
// Diagnostic (known-issues.md "B"): install the int-÷0 backtrace handler
130+
// before anything runs. No-op unless TRANSCRIBE_DIAG_FAULT_TRACE is set.
131+
if (const char * d = std::getenv("TRANSCRIBE_DIAG_FAULT_TRACE");
132+
d != nullptr && d[0] != '\0' && std::strcmp(d, "0") != 0) {
133+
AddVectoredExceptionHandler(/*first=*/1u, diag_fault_trace);
134+
}
135+
#endif
62136
const char * env = std::getenv("TRANSCRIBE_WHISPER_MODEL");
63137
if (env == nullptr || env[0] == '\0') {
64138
std::fprintf(stderr,

0 commit comments

Comments
 (0)