-
Notifications
You must be signed in to change notification settings - Fork 0
Walk faulting thread's stack from ucontext in native crash handler #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,13 +12,26 @@ | |||||||||||||||||
| // library. | ||||||||||||||||||
| // | ||||||||||||||||||
| // This file installs a SIGSEGV/SIGBUS/SIGILL/SIGFPE/SIGABRT handler that | ||||||||||||||||||
| // captures the signal IN-PROCESS, walks the stack with `_Unwind_Backtrace`, | ||||||||||||||||||
| // resolves each frame with `dladdr` (library + symbol + offset), and writes | ||||||||||||||||||
| // the result to **both** logcat (tag `osu!crash`) and a plain text file at a | ||||||||||||||||||
| // path passed in by the C# side (`<external-files-dir>/native_crash.log`). | ||||||||||||||||||
| // That path is reachable by the user via Android's Files app without root | ||||||||||||||||||
| // or adb. After dumping, the previous handler (debuggerd) is invoked so | ||||||||||||||||||
| // the normal Android tombstone is still produced. | ||||||||||||||||||
| // captures the signal IN-PROCESS and writes a structured dump to **both** | ||||||||||||||||||
| // logcat (tag `osu!crash`) and a plain text file at a path passed in by | ||||||||||||||||||
| // the C# side (`<external-files-dir>/native_crash.log`). That path is | ||||||||||||||||||
| // reachable by the user via Android's Files app without root or adb. | ||||||||||||||||||
| // After dumping, the previous handler (debuggerd) is invoked so the normal | ||||||||||||||||||
| // Android tombstone is still produced. | ||||||||||||||||||
| // | ||||||||||||||||||
| // The dump contains: | ||||||||||||||||||
| // 1. Signal info (signal/code/fault address/tid/thread name/uptime). | ||||||||||||||||||
| // 2. Full register state. | ||||||||||||||||||
| // 3. The faulting thread's backtrace, walked from the saved ucontext via | ||||||||||||||||||
| // the AArch64 frame-pointer chain. This is the diagnostic that | ||||||||||||||||||
| // actually matters — it shows where the crash happened. We do NOT | ||||||||||||||||||
| // use `_Unwind_Backtrace` for this because that walks the *handler* | ||||||||||||||||||
| // thread's stack (which terminates at the kernel signal trampoline | ||||||||||||||||||
| // in vdso, giving "crashHandler → libsigchain → vdso" — useless). | ||||||||||||||||||
| // 4. /proc/self/maps so any addresses dladdr couldn't symbolicate | ||||||||||||||||||
| // (internal-namespace / stripped-.dynsym frames) can still be matched | ||||||||||||||||||
| // to a library and offset post-mortem. | ||||||||||||||||||
| // 5. The secondary `_Unwind_Backtrace` output for completeness. | ||||||||||||||||||
| // | ||||||||||||||||||
| // Async-signal safety: | ||||||||||||||||||
| // We use only signal-safe primitives in the handler: | ||||||||||||||||||
|
|
@@ -263,6 +276,192 @@ static void dumpRegisters(int fd, void* ucv) { | |||||||||||||||||
| #endif | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // ---------------------------------------------------------------------------- | ||||||||||||||||||
| // Symbolicate a single PC and write a " #NN pc=0xHEX /lib (sym+0xOFF)\n" line. | ||||||||||||||||||
| // `tagWhenUnresolved` lets the caller annotate frames whose PC is invalid | ||||||||||||||||||
| // (e.g. NULL function-pointer call → pc == 0). | ||||||||||||||||||
| // Also mirrors a short version to logcat. | ||||||||||||||||||
| // ---------------------------------------------------------------------------- | ||||||||||||||||||
| static void writeFrame(int fd, int frameNo, uintptr_t pc, const char* tagWhenUnresolved) { | ||||||||||||||||||
| writeStr(fd, " #"); | ||||||||||||||||||
| if (frameNo < 10) writeStr(fd, "0"); | ||||||||||||||||||
| writeDec(fd, frameNo); | ||||||||||||||||||
| writeStr(fd, " pc 0x"); | ||||||||||||||||||
| writeHex64(fd, (uint64_t)pc); | ||||||||||||||||||
|
|
||||||||||||||||||
| Dl_info info; | ||||||||||||||||||
| bool resolved = (pc != 0) && (dladdr(reinterpret_cast<void*>(pc), &info) != 0); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (resolved && info.dli_fname) { | ||||||||||||||||||
| writeStr(fd, " "); | ||||||||||||||||||
| writeStr(fd, info.dli_fname); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (info.dli_sname) { | ||||||||||||||||||
| uintptr_t off = pc - (uintptr_t)info.dli_saddr; | ||||||||||||||||||
| writeStr(fd, " ("); | ||||||||||||||||||
| writeStr(fd, info.dli_sname); | ||||||||||||||||||
| writeStr(fd, "+0x"); | ||||||||||||||||||
| writeHex64(fd, (uint64_t)off, 1); | ||||||||||||||||||
| writeStr(fd, ")"); | ||||||||||||||||||
| } else if (info.dli_fbase) { | ||||||||||||||||||
| uintptr_t off = pc - (uintptr_t)info.dli_fbase; | ||||||||||||||||||
| writeStr(fd, " (lib+0x"); | ||||||||||||||||||
| writeHex64(fd, (uint64_t)off, 1); | ||||||||||||||||||
| writeStr(fd, ")"); | ||||||||||||||||||
| } | ||||||||||||||||||
| } else if (tagWhenUnresolved) { | ||||||||||||||||||
| writeStr(fd, " "); | ||||||||||||||||||
| writeStr(fd, tagWhenUnresolved); | ||||||||||||||||||
| } else { | ||||||||||||||||||
| writeStr(fd, " <unresolved>"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+292
to
+317
|
||||||||||||||||||
| writeStr(fd, "\n"); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Short logcat mirror. | ||||||||||||||||||
| char line[256]; | ||||||||||||||||||
| int p = 0; | ||||||||||||||||||
| line[p++] = '#'; | ||||||||||||||||||
| if (frameNo < 10) line[p++] = '0'; | ||||||||||||||||||
| long long n = frameNo; | ||||||||||||||||||
| char tmp[12]; int tp = 0; | ||||||||||||||||||
| if (n == 0) tmp[tp++] = '0'; | ||||||||||||||||||
| while (n > 0 && tp < 11) { tmp[tp++] = (char)('0' + (n % 10)); n /= 10; } | ||||||||||||||||||
| while (tp > 0 && p < (int)sizeof(line) - 1) line[p++] = tmp[--tp]; | ||||||||||||||||||
| const char* sep = " pc=0x"; | ||||||||||||||||||
| for (int i = 0; sep[i] && p < (int)sizeof(line) - 1; ++i) line[p++] = sep[i]; | ||||||||||||||||||
| static const char hd[] = "0123456789abcdef"; | ||||||||||||||||||
| for (int sh = 60; sh >= 0 && p < (int)sizeof(line) - 1; sh -= 4) | ||||||||||||||||||
| line[p++] = hd[(pc >> sh) & 0xf]; | ||||||||||||||||||
| if (resolved) { | ||||||||||||||||||
| const char* lib = info.dli_fname ? info.dli_fname : "?"; | ||||||||||||||||||
| const char* sym = info.dli_sname ? info.dli_sname : ""; | ||||||||||||||||||
| if (p < (int)sizeof(line) - 1) line[p++] = ' '; | ||||||||||||||||||
| for (int i = 0; lib[i] && p < (int)sizeof(line) - 1; ++i) line[p++] = lib[i]; | ||||||||||||||||||
| if (sym[0]) { | ||||||||||||||||||
| if (p < (int)sizeof(line) - 1) line[p++] = ' '; | ||||||||||||||||||
| if (p < (int)sizeof(line) - 1) line[p++] = '('; | ||||||||||||||||||
| for (int i = 0; sym[i] && p < (int)sizeof(line) - 2; ++i) line[p++] = sym[i]; | ||||||||||||||||||
| if (p < (int)sizeof(line) - 1) line[p++] = ')'; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| line[p] = '\0'; | ||||||||||||||||||
| logcatWrite(line); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // ---------------------------------------------------------------------------- | ||||||||||||||||||
| // Walk the *crashing thread's* stack from the saved ucontext. | ||||||||||||||||||
| // | ||||||||||||||||||
| // `_Unwind_Backtrace` (used elsewhere in this file) walks the *current* | ||||||||||||||||||
| // thread's stack — i.e., the stack of the signal handler itself, with the | ||||||||||||||||||
| // libgcc unwinder stopping at the kernel signal trampoline (`__kernel_rt_sigreturn`) | ||||||||||||||||||
| // because there's no CFI across the signal frame. In practice that produces | ||||||||||||||||||
| // only "crashHandler → libsigchain → vdso", which is useless for diagnosing | ||||||||||||||||||
| // the actual fault. | ||||||||||||||||||
| // | ||||||||||||||||||
| // To recover the real backtrace we walk the AArch64 frame-pointer chain | ||||||||||||||||||
| // starting from the saved registers in ucontext: | ||||||||||||||||||
| // - frame[0] is `pc` (or, if pc == 0 because of a NULL function pointer | ||||||||||||||||||
| // call, `lr` — the return address of that call, i.e. the call site). | ||||||||||||||||||
| // - subsequent frames come from following `x29 (fp)` chain: | ||||||||||||||||||
|
Comment on lines
+363
to
+365
|
||||||||||||||||||
| // - frame[0] is `pc` (or, if pc == 0 because of a NULL function pointer | |
| // call, `lr` — the return address of that call, i.e. the call site). | |
| // - subsequent frames come from following `x29 (fp)` chain: | |
| // - if `pc != 0`, frame[0] is `pc`. | |
| // - if `pc == 0` (for example, a NULL function pointer call), we first emit | |
| // a synthetic NULL-call frame as frame[0], then emit `lr` as frame[1] | |
| // (the return address of that call, i.e. the call site). | |
| // - subsequent frames come from following the `x29 (fp)` chain: |
Copilot
AI
Apr 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This frame == 0 branch looks unreachable: frame is always incremented by at least one writeFrame() call (even when pc == 0, the synthetic NULL-call frame is emitted). If you want an explicit “no frames” message, check for the specific case you care about (e.g., both pc and lr are 0 and the FP chain yields nothing), otherwise remove this dead branch.
Copilot
AI
Apr 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling fsync() from inside the signal handler is risky because fsync is not async-signal-safe and may block or deadlock on internal locks while the process is already in an unstable state. If the goal is to bound partial writes, consider removing the mid-handler fsync calls (and relying on the final flush/close), or gate them behind a debug/diagnostic build flag with documentation about the trade-off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
writeFrame()duplicates the symbolication + formatting logic that already exists inunwindCallback(). To prevent drift between the “context” and “_Unwind_Backtrace” sections (and to match the intent of having identical formatting), consider refactoringunwindCallback()to callwriteFrame()instead of maintaining two parallel implementations.