8000 Extend Windows minidump with referenced memory regions. (#15111) · open-bigdata/arangodb@45c28d1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 45c28d1

Browse files
authored
Extend Windows minidump with referenced memory regions. (arangodb#15111)
1 parent 011d354 commit 45c28d1

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
devel
22
-----
33

4+
* Extend Windows minidumps with memory regions referenced from CPU registers or
5+
the stack to provide more contextual information in case of crashes.
6+
47
* Add option to content-transfer encode gzip Foxx replies.
58

69
* Simplify internal request compression/decompression handling code.

lib/Basics/CrashHandler.cpp

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,16 @@ void crashHandlerSignalHandler(int signal, siginfo_t* info, void* ucontext) {
492492
static std::string miniDumpDirectory = "C:\\temp";
493493
static std::mutex miniDumpLock;
494494

495+
// TODO - ATM if these are put inside the function MSVC complains that they
496+
// are not captured in the lambda, but a lamba with captures cannot convert
497+
// to a function pointer. Since these are constexpr is should not be necessary
498+
// to capture them, and with C++20 enabled MSVC no longer complains.
499+
// So once we have upgraded to C++20 these should be move into the function.
500+
constexpr DWORD64 blockSize = 1024; // 1kb
501+
constexpr DWORD64 maxStackAddrs = 2048;
502+
constexpr DWORD maxNumAddrs = 160000;
503+
constexpr DWORD numRegs = 16;
504+
495505
void createMiniDump(EXCEPTION_POINTERS* pointers) {
496506
// we have to serialize calls to MiniDumpWriteDump
497507
std::lock_guard<std::mutex> lock(miniDumpLock);
@@ -525,10 +535,106 @@ void createMiniDump(EXCEPTION_POINTERS* pointers) {
525535
exceptionInfo.ThreadId = GetCurrentThreadId();
526536
exceptionInfo.ExceptionPointers = pointers;
527537
exceptionInfo.ClientPointers = FALSE;
538+
539+
// We try to gather some additional information from referenced memory
540+
// In total we gather up to 16000 memory blocks of 1kb each.
541+
// We consider only addresses that reference some memory block that can
542+
// actually be read (-> !IsBadReadPtr).
543+
544+
// we want to have enough addresses to cover all 16 registers plus all indirections and all
545+
// maxStackAddrs stack addresses
546+
static_assert(maxNumAddrs > maxStackAddrs + numRegs * (blockSize / sizeof(void*)));
547+
548+
DWORD64 addrs[maxNumAddrs];
549+
DWORD numAddrs = 0;
550+
551+
if (pointers) {
552+
auto addAddr = [&addrs, &numAddrs](DWORD64 reg) {
553+
auto base = reg & ~(blockSize - 1);
554+
if (base == 0 || IsBadReadPtr((void*)base, blockSize) || numAddrs >= maxNumAddrs) {
555+
return;
556+
}
557+
for (DWORD i = 0; i < numAddrs; ++i) {
558+
if (addrs[i] == base) {
559+
return;
560+
}
561+
}
562+
addrs[numAddrs++] = base;
563+
};
564+
565+
// we take the values of all general purpose registers
566+
auto& ctx = *pointers->ContextRecord;
567+
addAddr(ctx.Rax);
568+
addAddr(ctx.Rcx);
569+
addAddr(ctx.Rdx);
570+
addAddr(ctx.Rbx);
571+
addAddr(ctx.Rsp);
572+
addAddr(ctx.Rbp);
573+
addAddr(ctx.Rsi);
574+
addAddr(ctx.Rdi);
575+
addAddr(ctx.R8);
576+
addAddr(ctx.R9);
577+
addAddr(ctx.R10);
578+
addAddr(ctx.R11);
579+
addAddr(ctx.R12);
580+
addAddr(ctx.R13);
581+
addAddr(ctx.R14);
582+
addAddr(ctx.R15);
583+
TRI_ASSERT(numAddrs <= numRegs);
584+
585+
// Take the first 2048 pointers from the stack and add them to the address list.
586+
// We use the thread information block (TIB) to get the base address of the stack
587+
// to handle the (unlikely) cases where the stack has less than 2048 items.
588+
auto* tib = (PNT_TIB)NtCurrentTeb();
589+
auto numStackAddrs = std::min(((DWORD64)tib->StackBase - ctx.Rsp) / sizeof(void*), maxStackAddrs);
590+
void** p = (void**)ctx.Rsp;
591+
for (DWORD64 i = 0; i < numStackAddrs; ++i) {
592+
addAddr((DWORD64)p[i]);
593+
}
594+
595+
// Now we take all the addresses we gathered so far and add all indirect addresses,
596+
// i.e., we take each 1024 byte block and add all 128 potential addresses from that
597+
// block (as long as we don't exceed our limit).
598+
// That way can follow at least one level of indirection when analyzing the dump.
599+
DWORD idx = numAddrs;
600+
do {
601+
--idx;
602+
void** p = (void**)addrs[idx];
603+
for (DWORD i = 0; i < blockSize / sizeof(void*) && numAddrs < maxNumAddrs; ++i) {
604+
auto base = (DWORD64)p[i] & ~(blockSize - 1);
605+
if (base != 0) {
606+
addrs[numAddrs++] = base;
607+
}
608+
}
609+
} while (idx != 0 && numAddrs < maxNumAddrs);
610+
}
611+
612+
struct CallbackParam {
613+
DWORD64* addrs;
614+
DWORD idx;
615+
DWORD numAddrs;
616+
};
617+
CallbackParam param{addrs, 0, numAddrs};
618+
619+
auto callback = [](PVOID callbackParam, PMINIDUMP_CALLBACK_INPUT callbackInput, PMINIDUMP_CALLBACK_OUTPUT callbackOutput) -> BOOL {
620+
auto* param = static_cast<CallbackParam*>(callbackParam);
621+
if (callbackInput->CallbackType == MemoryCallback && param->idx < param->numAddrs) {
622+
callbackOutput->MemoryBase = param->addrs[param->idx];
623+
callbackOutput->MemorySize = blockSize;
624+
++param->idx;
625+
}
626+
return TRUE;
627+
};
628+
629+
MINIDUMP_CALLBACK_INFORMATION callbackInfo{
630+
callback,
631+
&param
632+
};
528633

529634
if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
530-
MINIDUMP_TYPE(MiniDumpNormal | MiniDumpWithProcessThreadData | MiniDumpWithDataSegs),
531-
pointers ? &exceptionInfo : nullptr, nullptr, nullptr)) {
635+
MINIDUMP_TYPE(MiniDumpNormal | MiniDumpWithProcessThreadData | MiniDumpWithDataSegs | MiniDumpIgnoreInaccessibleMemory),
636+
pointers ? &exceptionInfo : nullptr, nullptr,
637+
pointers ? &callbackInfo : nullptr)) {
532638
char* p = &buffer[0];
533639
appendNullTerminatedString("Wrote minidump: ", p);
534640
appendNullTerminatedString(filename, p);

0 commit comments

Comments
 (0)
0