修复clash的问题
This commit is contained in:
@@ -0,0 +1,512 @@
|
||||
// CrashHandler.cpp
|
||||
//
|
||||
// Async-signal-safe crash dumper for the face_sdk native library. The goal
|
||||
// is that the next time the app dies (e.g. another FORTIFY: pthread_mutex_lock
|
||||
// called on a destroyed mutex from inside the Vulkan driver) we don't only
|
||||
// have logcat — we also have a self-contained dump on the device's app data
|
||||
// directory that the user can pull off and send back, even after a reboot.
|
||||
//
|
||||
// Why we need this:
|
||||
// - DebugLog only writes "happy path" events. The Vulkan crash happens
|
||||
// synchronously inside vkQueueSubmit / vkFreeCommandBuffers — there is
|
||||
// no DebugLog call near the crash site, so the file log just stops.
|
||||
// - logcat survives across the crash (debuggerd dumps the backtrace there)
|
||||
// but logcat is volatile: a couple of reboots, a logcat -c, or a long
|
||||
// idle period and it's gone. We want a persistent file.
|
||||
// - tombstones in /data/tombstones/ are root-only on consumer devices.
|
||||
//
|
||||
// Implementation notes:
|
||||
// - Inside a signal handler we MUST stick to async-signal-safe APIs
|
||||
// (man 7 signal-safety). That rules out fprintf / snprintf / malloc.
|
||||
// We use write(), our own integer-to-string conversion, and a single
|
||||
// pre-opened fd. dladdr() is technically not on the POSIX safe list but
|
||||
// bionic's implementation only takes one rwlock and is widely used in
|
||||
// other crash dumpers (breakpad, crashpad, libunwindstack). We accept
|
||||
// that risk because the alternative is no symbol at all.
|
||||
// - We use <unwind.h> (_Unwind_Backtrace) instead of execinfo.h because
|
||||
// bionic doesn't ship execinfo.h on all NDK levels, and _Unwind_Backtrace
|
||||
// is the same primitive Android's own tombstoned uses.
|
||||
// - We re-raise the original signal with the default handler at the end so
|
||||
// the OS still produces a tombstone / ANR record for vendors that
|
||||
// read /data/tombstones/.
|
||||
|
||||
#include "CrashHandler.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <unwind.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace {
|
||||
|
||||
// ---------- Globals (touched from the handler -> only POD/atomics) ---------
|
||||
|
||||
constexpr size_t kCrashStackSize = 64 * 1024; // sigaltstack
|
||||
constexpr size_t kMaxFrames = 64;
|
||||
constexpr size_t kNoteCapacity = 256;
|
||||
|
||||
uint8_t g_sigStack[kCrashStackSize];
|
||||
int g_crashFd = -1; // crash log fd (append, sync)
|
||||
int g_debugFd = -1; // optional: also dup write to debug log
|
||||
std::atomic<bool> g_installed{false};
|
||||
std::atomic<bool> g_handlingCrash{false};
|
||||
|
||||
// Single writer of g_note: setNote() (uses memcpy under a tiny lock, but the
|
||||
// handler reads byte-by-byte so a torn read at most produces a truncated
|
||||
// note, never a deref of bad memory).
|
||||
char g_note[kNoteCapacity] = {0};
|
||||
pthread_mutex_t g_noteMtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
const int g_signals[] = { SIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSYS };
|
||||
constexpr size_t kNumSignals = sizeof(g_signals) / sizeof(g_signals[0]);
|
||||
|
||||
// ----------------------- Async-signal-safe writers -------------------------
|
||||
|
||||
// Write a NUL-terminated string. Drops the trailing NUL.
|
||||
void sigWrite(int fd, const char* s) {
|
||||
if (fd < 0 || !s) return;
|
||||
size_t n = 0;
|
||||
while (s[n]) ++n;
|
||||
if (n == 0) return;
|
||||
// Loop until everything is written or we error out. Async-safe.
|
||||
while (n > 0) {
|
||||
ssize_t w = write(fd, s, n);
|
||||
if (w <= 0) {
|
||||
if (w < 0 && errno == EINTR) continue;
|
||||
return;
|
||||
}
|
||||
s += w;
|
||||
n -= (size_t)w;
|
||||
}
|
||||
}
|
||||
|
||||
// Write n bytes from buf. Used for the note (may contain anything).
|
||||
void sigWriteN(int fd, const char* buf, size_t n) {
|
||||
if (fd < 0 || !buf) return;
|
||||
while (n > 0) {
|
||||
ssize_t w = write(fd, buf, n);
|
||||
if (w <= 0) {
|
||||
if (w < 0 && errno == EINTR) continue;
|
||||
return;
|
||||
}
|
||||
buf += w;
|
||||
n -= (size_t)w;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the same string to both the crash log and the debug log (if any).
|
||||
void sigDump(const char* s) {
|
||||
sigWrite(g_crashFd, s);
|
||||
sigWrite(g_debugFd, s);
|
||||
}
|
||||
|
||||
// Convert an unsigned integer to its decimal representation. Returns the
|
||||
// number of characters written into buf (without a NUL).
|
||||
size_t u64ToDec(uint64_t v, char* buf, size_t cap) {
|
||||
if (cap == 0) return 0;
|
||||
char tmp[32];
|
||||
size_t i = 0;
|
||||
if (v == 0) {
|
||||
tmp[i++] = '0';
|
||||
} else {
|
||||
while (v && i < sizeof(tmp)) {
|
||||
tmp[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
}
|
||||
size_t out = (i < cap) ? i : cap;
|
||||
for (size_t k = 0; k < out; ++k) {
|
||||
buf[k] = tmp[i - 1 - k];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert an unsigned 64-bit value to a fixed-width 16-digit hex string.
|
||||
// Useful for PC values.
|
||||
size_t u64ToHex16(uint64_t v, char* buf, size_t cap) {
|
||||
static const char digits[] = "0123456789abcdef";
|
||||
if (cap < 16) return 0;
|
||||
for (int i = 15; i >= 0; --i) {
|
||||
buf[i] = digits[v & 0xF];
|
||||
v >>= 4;
|
||||
}
|
||||
return 16;
|
||||
}
|
||||
|
||||
// Write "<key>=<u64>\n".
|
||||
void sigWriteKV_u64(int fd, const char* key, uint64_t v) {
|
||||
sigWrite(fd, key);
|
||||
sigWrite(fd, "=");
|
||||
char num[24];
|
||||
size_t n = u64ToDec(v, num, sizeof(num));
|
||||
sigWriteN(fd, num, n);
|
||||
sigWrite(fd, "\n");
|
||||
}
|
||||
|
||||
// Write "<key>=0x<hex>\n".
|
||||
void sigWriteKV_ptr(int fd, const char* key, uint64_t v) {
|
||||
sigWrite(fd, key);
|
||||
sigWrite(fd, "=0x");
|
||||
char hx[16];
|
||||
u64ToHex16(v, hx, sizeof(hx));
|
||||
sigWriteN(fd, hx, 16);
|
||||
sigWrite(fd, "\n");
|
||||
}
|
||||
|
||||
// ------------------------- Signal name lookup ------------------------------
|
||||
|
||||
const char* signalName(int signo) {
|
||||
switch (signo) {
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
case SIGABRT: return "SIGABRT";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
case SIGFPE: return "SIGFPE";
|
||||
case SIGILL: return "SIGILL";
|
||||
case SIGSYS: return "SIGSYS";
|
||||
case SIGTRAP: return "SIGTRAP";
|
||||
default: return "SIG?";
|
||||
}
|
||||
}
|
||||
|
||||
// si_code to short string. Only the most common ones; everything else falls
|
||||
// back to the numeric value via sigWriteKV_u64.
|
||||
const char* siCodeName(int signo, int code) {
|
||||
switch (signo) {
|
||||
case SIGSEGV:
|
||||
if (code == SEGV_MAPERR) return "SEGV_MAPERR";
|
||||
if (code == SEGV_ACCERR) return "SEGV_ACCERR";
|
||||
break;
|
||||
case SIGBUS:
|
||||
if (code == BUS_ADRALN) return "BUS_ADRALN";
|
||||
if (code == BUS_ADRERR) return "BUS_ADRERR";
|
||||
if (code == BUS_OBJERR) return "BUS_OBJERR";
|
||||
break;
|
||||
case SIGFPE:
|
||||
if (code == FPE_INTDIV) return "FPE_INTDIV";
|
||||
if (code == FPE_INTOVF) return "FPE_INTOVF";
|
||||
if (code == FPE_FLTDIV) return "FPE_FLTDIV";
|
||||
break;
|
||||
case SIGILL:
|
||||
if (code == ILL_ILLOPC) return "ILL_ILLOPC";
|
||||
if (code == ILL_ILLOPN) return "ILL_ILLOPN";
|
||||
break;
|
||||
case SIGABRT:
|
||||
if (code == SI_TKILL) return "SI_TKILL";
|
||||
if (code == SI_USER) return "SI_USER";
|
||||
break;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
// ------------------------- Backtrace via _Unwind_Backtrace -----------------
|
||||
|
||||
struct UnwindCtx {
|
||||
uintptr_t* frames;
|
||||
size_t count;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code unwindCallback(_Unwind_Context* ctx, void* arg) {
|
||||
UnwindCtx* uc = static_cast<UnwindCtx*>(arg);
|
||||
if (uc->count >= uc->cap) return _URC_END_OF_STACK;
|
||||
|
||||
uintptr_t pc = _Unwind_GetIP(ctx);
|
||||
if (pc) {
|
||||
// Trim Thumb bit on 32-bit ARM. No-op on aarch64/x86_64.
|
||||
pc &= ~(uintptr_t)1;
|
||||
uc->frames[uc->count++] = pc;
|
||||
}
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
|
||||
size_t captureBacktrace(uintptr_t* out, size_t cap) {
|
||||
UnwindCtx uc{out, 0, cap};
|
||||
_Unwind_Backtrace(&unwindCallback, &uc);
|
||||
return uc.count;
|
||||
}
|
||||
|
||||
// Dump one frame: " #02 pc 000000000000abcd /path/lib.so (Symbol+0x10)"
|
||||
void dumpFrame(int fd, size_t idx, uintptr_t pc) {
|
||||
sigWrite(fd, " #");
|
||||
char num[8];
|
||||
if (idx < 10) {
|
||||
num[0] = '0';
|
||||
num[1] = (char)('0' + idx);
|
||||
sigWriteN(fd, num, 2);
|
||||
} else {
|
||||
size_t n = u64ToDec(idx, num, sizeof(num));
|
||||
sigWriteN(fd, num, n);
|
||||
}
|
||||
sigWrite(fd, " pc ");
|
||||
char hx[16];
|
||||
u64ToHex16((uint64_t)pc, hx, sizeof(hx));
|
||||
sigWriteN(fd, hx, 16);
|
||||
|
||||
Dl_info info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
if (dladdr(reinterpret_cast<void*>(pc), &info) && info.dli_fname) {
|
||||
sigWrite(fd, " ");
|
||||
sigWrite(fd, info.dli_fname);
|
||||
|
||||
if (info.dli_sname) {
|
||||
uintptr_t sym = reinterpret_cast<uintptr_t>(info.dli_saddr);
|
||||
uintptr_t off = (sym && pc >= sym) ? (pc - sym) : 0;
|
||||
sigWrite(fd, " (");
|
||||
sigWrite(fd, info.dli_sname);
|
||||
sigWrite(fd, "+0x");
|
||||
char ohx[16];
|
||||
u64ToHex16((uint64_t)off, ohx, sizeof(ohx));
|
||||
sigWriteN(fd, ohx, 16);
|
||||
sigWrite(fd, ")");
|
||||
} else if (info.dli_fbase) {
|
||||
uintptr_t base = reinterpret_cast<uintptr_t>(info.dli_fbase);
|
||||
uintptr_t off = (pc >= base) ? (pc - base) : 0;
|
||||
sigWrite(fd, " (offset 0x");
|
||||
char ohx[16];
|
||||
u64ToHex16((uint64_t)off, ohx, sizeof(ohx));
|
||||
sigWriteN(fd, ohx, 16);
|
||||
sigWrite(fd, ")");
|
||||
}
|
||||
}
|
||||
sigWrite(fd, "\n");
|
||||
}
|
||||
|
||||
// ------------------------- Time + tid helpers ------------------------------
|
||||
|
||||
// Builds "YYYY-MM-DD HH:MM:SS.mmm UTC" into the given buffer (no NUL).
|
||||
// Returns the number of bytes written. Async-signal-safe (no stdio, no
|
||||
// localtime_r tz lookups).
|
||||
size_t formatTimestamp(char* b, size_t cap) {
|
||||
timespec ts{};
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
struct tm tm_info{};
|
||||
time_t s = ts.tv_sec;
|
||||
gmtime_r(&s, &tm_info);
|
||||
|
||||
size_t i = 0;
|
||||
auto putUInt = [&](unsigned v, int width) {
|
||||
char tmp[8];
|
||||
size_t n = u64ToDec(v, tmp, sizeof(tmp));
|
||||
while ((int)n < (size_t)width) {
|
||||
if (i < cap) b[i++] = '0';
|
||||
++n;
|
||||
}
|
||||
for (size_t k = 0; k < n; ++k) {
|
||||
if (i < cap) b[i++] = tmp[k];
|
||||
}
|
||||
};
|
||||
|
||||
putUInt((unsigned)(tm_info.tm_year + 1900), 4); if (i < cap) b[i++] = '-';
|
||||
putUInt((unsigned)(tm_info.tm_mon + 1), 2); if (i < cap) b[i++] = '-';
|
||||
putUInt((unsigned)(tm_info.tm_mday), 2); if (i < cap) b[i++] = ' ';
|
||||
putUInt((unsigned)(tm_info.tm_hour), 2); if (i < cap) b[i++] = ':';
|
||||
putUInt((unsigned)(tm_info.tm_min), 2); if (i < cap) b[i++] = ':';
|
||||
putUInt((unsigned)(tm_info.tm_sec), 2); if (i < cap) b[i++] = '.';
|
||||
putUInt((unsigned)(ts.tv_nsec / 1000000), 3);
|
||||
static const char kSuffix[] = " UTC";
|
||||
for (size_t k = 0; k < sizeof(kSuffix) - 1; ++k) {
|
||||
if (i < cap) b[i++] = kSuffix[k];
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
pid_t currentTid() {
|
||||
return static_cast<pid_t>(syscall(SYS_gettid));
|
||||
}
|
||||
|
||||
// ------------------------- Signal handler ----------------------------------
|
||||
|
||||
void crashHandler(int signo, siginfo_t* info, void* ucontext) {
|
||||
(void)ucontext;
|
||||
|
||||
// Re-entry guard: if a second signal fires while we're dumping (e.g. our
|
||||
// own dladdr trips a SIGSEGV) just chain to the default handler.
|
||||
bool expected = false;
|
||||
if (!g_handlingCrash.compare_exchange_strong(expected, true,
|
||||
std::memory_order_acq_rel)) {
|
||||
// Already in handler -> default + bail.
|
||||
signal(signo, SIG_DFL);
|
||||
raise(signo);
|
||||
return;
|
||||
}
|
||||
|
||||
sigDump("\n========== FACE_SDK CRASH ==========\n");
|
||||
sigDump("time=");
|
||||
{
|
||||
char tsbuf[48];
|
||||
size_t tn = formatTimestamp(tsbuf, sizeof(tsbuf));
|
||||
sigWriteN(g_crashFd, tsbuf, tn);
|
||||
sigWriteN(g_debugFd, tsbuf, tn);
|
||||
}
|
||||
sigDump("\n");
|
||||
|
||||
sigDump("signal=");
|
||||
sigDump(signalName(signo));
|
||||
sigDump(" code=");
|
||||
sigDump(siCodeName(signo, info ? info->si_code : 0));
|
||||
sigDump("\n");
|
||||
|
||||
if (info) {
|
||||
sigWriteKV_u64(g_crashFd, "si_signo", (uint64_t)info->si_signo);
|
||||
sigWriteKV_u64(g_debugFd, "si_signo", (uint64_t)info->si_signo);
|
||||
sigWriteKV_u64(g_crashFd, "si_code", (uint64_t)info->si_code);
|
||||
sigWriteKV_u64(g_debugFd, "si_code", (uint64_t)info->si_code);
|
||||
sigWriteKV_ptr(g_crashFd, "si_addr", (uint64_t)(uintptr_t)info->si_addr);
|
||||
sigWriteKV_ptr(g_debugFd, "si_addr", (uint64_t)(uintptr_t)info->si_addr);
|
||||
}
|
||||
sigWriteKV_u64(g_crashFd, "pid", (uint64_t)getpid());
|
||||
sigWriteKV_u64(g_debugFd, "pid", (uint64_t)getpid());
|
||||
sigWriteKV_u64(g_crashFd, "tid", (uint64_t)currentTid());
|
||||
sigWriteKV_u64(g_debugFd, "tid", (uint64_t)currentTid());
|
||||
|
||||
// Note (current frame index, current motion, ...).
|
||||
sigDump("note=");
|
||||
sigWriteN(g_crashFd, g_note, strnlen(g_note, kNoteCapacity));
|
||||
sigWriteN(g_debugFd, g_note, strnlen(g_note, kNoteCapacity));
|
||||
sigDump("\n");
|
||||
|
||||
sigDump("backtrace:\n");
|
||||
uintptr_t frames[kMaxFrames];
|
||||
size_t nf = captureBacktrace(frames, kMaxFrames);
|
||||
for (size_t i = 0; i < nf; ++i) {
|
||||
dumpFrame(g_crashFd, i, frames[i]);
|
||||
dumpFrame(g_debugFd, i, frames[i]);
|
||||
}
|
||||
sigDump("==================================\n");
|
||||
|
||||
// Make sure everything reaches disk before we self-destruct.
|
||||
if (g_crashFd >= 0) fsync(g_crashFd);
|
||||
if (g_debugFd >= 0) fsync(g_debugFd);
|
||||
|
||||
// Mirror to logcat too so a quick `adb logcat -d` after a reboot still
|
||||
// shows the SIGNAL line (helps cross-checking the file).
|
||||
__android_log_print(ANDROID_LOG_FATAL, "FACE_DBG_CRASH",
|
||||
"fatal %s @ tid=%d, see face_sdk_crash.log",
|
||||
signalName(signo), currentTid());
|
||||
|
||||
// Restore default handler and re-raise. This produces /data/tombstones/*
|
||||
// on rooted devices and tells debuggerd to print the official Android
|
||||
// backtrace into logcat (`crash_dump64 ... DEBUG`).
|
||||
struct sigaction dfl{};
|
||||
dfl.sa_handler = SIG_DFL;
|
||||
sigemptyset(&dfl.sa_mask);
|
||||
sigaction(signo, &dfl, nullptr);
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace CrashHandler {
|
||||
|
||||
void install(const std::string& internalDataPath,
|
||||
const std::string& debugLogPath) {
|
||||
bool expected = false;
|
||||
if (!g_installed.compare_exchange_strong(expected, true,
|
||||
std::memory_order_acq_rel)) {
|
||||
return; // already installed
|
||||
}
|
||||
|
||||
// Open the persistent crash log file in append mode. We keep it open
|
||||
// forever so the signal handler doesn't have to call open() (which is
|
||||
// safe but slow).
|
||||
{
|
||||
std::string path = internalDataPath.empty()
|
||||
? std::string("/data/local/tmp/face_sdk_crash.log")
|
||||
: internalDataPath + "/face_sdk_crash.log";
|
||||
g_crashFd = open(path.c_str(),
|
||||
O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC,
|
||||
0644);
|
||||
if (g_crashFd >= 0) {
|
||||
// Header for the new run.
|
||||
sigWrite(g_crashFd, "\n=== CrashHandler installed pid=");
|
||||
char pidbuf[16];
|
||||
size_t n = u64ToDec((uint64_t)getpid(), pidbuf, sizeof(pidbuf));
|
||||
sigWriteN(g_crashFd, pidbuf, n);
|
||||
sigWrite(g_crashFd, " ===\n");
|
||||
fsync(g_crashFd);
|
||||
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
|
||||
"CrashHandler log file: %s", path.c_str());
|
||||
} else {
|
||||
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||
"CrashHandler failed to open %s: %s",
|
||||
path.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Also keep a writable fd to the DebugLog file (if any) so dumps land
|
||||
// next to the regular tail of the log. We don't touch g_fp inside
|
||||
// DebugLog because that would need its mutex — not safe in handler.
|
||||
if (!debugLogPath.empty()) {
|
||||
g_debugFd = open(debugLogPath.c_str(),
|
||||
O_WRONLY | O_APPEND | O_CLOEXEC,
|
||||
0644);
|
||||
if (g_debugFd < 0) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||
"CrashHandler: cannot open debug log %s: %s",
|
||||
debugLogPath.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Set up an alternate stack so we still have stack space if the original
|
||||
// thread ran out (very common for Vulkan crashes inside deep driver
|
||||
// call chains).
|
||||
stack_t ss{};
|
||||
ss.ss_sp = g_sigStack;
|
||||
ss.ss_size = sizeof(g_sigStack);
|
||||
ss.ss_flags = 0;
|
||||
if (sigaltstack(&ss, nullptr) != 0) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||
"CrashHandler: sigaltstack failed: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
struct sigaction sa{};
|
||||
sa.sa_sigaction = &crashHandler;
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
for (size_t i = 0; i < kNumSignals; ++i) {
|
||||
if (sigaction(g_signals[i], &sa, nullptr) != 0) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
|
||||
"CrashHandler: sigaction(%d) failed: %s",
|
||||
g_signals[i], strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
|
||||
"CrashHandler installed for SIGSEGV/SIGABRT/SIGBUS/SIGFPE/SIGILL/SIGSYS");
|
||||
}
|
||||
|
||||
void setNote(const char* note) {
|
||||
if (!note) note = "";
|
||||
pthread_mutex_lock(&g_noteMtx);
|
||||
size_t n = strnlen(note, kNoteCapacity - 1);
|
||||
memcpy(g_note, note, n);
|
||||
g_note[n] = '\0';
|
||||
pthread_mutex_unlock(&g_noteMtx);
|
||||
}
|
||||
|
||||
} // namespace CrashHandler
|
||||
|
||||
#else // _WIN32 — no-op on host build
|
||||
|
||||
namespace CrashHandler {
|
||||
void install(const std::string&, const std::string&) {}
|
||||
void setNote(const char*) {}
|
||||
} // namespace CrashHandler
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user