Manual Reconstruction of Call Stack from Memory Dump File

Amit Kumar Parida
4 min readMar 18, 2022

Sometimes WinDbg !analyze or k commands display incorrect call-stack (or stack-trace). Therefore, some critical thought is required to distinguish between correct and incorrect stack traces.

Incorrect stack traces usually

  • Have WinDbg warning: “Following frames may be wrong”
  • Don’t have the correct bottom frame like kernel32!BaseThreadStart (in usermode)
  • Have function calls that don’t make any sense
  • Have strange looking disassembled function code or code that doesn’t make any sense from compiler perspective
  • Have ChildEBP and RetAddr addresses that don’t make any sense

Consider the following stack trace:

0:011> kChildEBP RetAddrWARNING: Frame IP not in any known module. Following frames may be wrong.0184e434 7c830b10 0×184e5bf0184e51c 7c81f832 ntdll!RtlGetFullPathName_Ustr+0×15b0184e5f8 7c83b1dd ntdll!RtlpLowFragHeapAlloc+0xc6a00099d30 00000000 ntdll!RtlpLowFragHeapFree+0xa70184e51c 7c81f832 ntdll!RtlGetFullPathName_Ustr+0×15b0184e5f8 7c83b1dd ntdll!RtlpLowFragHeapAlloc+0xc6a00099d30 00000000 ntdll!RtlpLowFragHeapFree+0xa7

Here we have almost all attributes of the wrong stack trace. At the first glance it looks like some heap corruption happened (runtime heap alloc and free functions are present) but if we give it second thought we would see that the low fragmentation heap Free function shouldn’t call low the fragmentation heap Alloc function and the latter shouldn’t query the full path name. That doesn’t make any sense.

What we should do here? Look at raw stack and try to build the correct stack trace ourselves. In our case this is very easy. We need to traverse stack frames from BaseThreadStart+0×34 until we don’t find any function call or reach the top. When functions are called (no optimization, most compilers) EBP registers are linked together as explained in the following image:

0:011> !tebTEB at 7ffd8000ExceptionList:             0184ebdcStackBase:                 01850000StackLimit:                01841000SubSystemTib:              00000000FiberData:                 00001e00ArbitraryUserPointer:      00000000Self:                      7ffd8000EnvironmentPointer:        00000000ClientId:                  0000061c . 00001b60RpcHandle:                 00000000Tls Storage:               00000000PEB Address:               7ffdf000LastErrorValue:            0LastStatusValue:           c0000034Count Owned Locks:         0HardErrorMode:             0

Let’s look at raw stack between the StackBase (01850000) and StackLimit (01841000) addresses:

0:011> dds 01841000 0185000001841000 000000000184eef0 0184ef0c0184eef4 7615dff2 localspl!SplDriverEvent+0×210184eef8 00bc3e080184eefc 000000030184ef00 000000010184ef04 000000000184ef08 0184efb00184ef0c 0184ef300184ef10 7615f9d0 localspl!PrinterDriverEvent+0×460184ef14 00bc3e080184ef18 000000030184ef1c 000000000184ef20 0184efb00184ef24 00b852a80184ef28 00c3ec580184ef2c 00bafcc00184ef30 0184f3f80184ef34 7614a9b4 localspl!SplAddPrinter+0×5f30184ef38 00c3ec580184ef3c 000000030184ef40 000000000184ef44 0184efb00184ef48 00c117f80184ff28 000000000184ff2c 000000000184ff30 0184ff840184ff34 77c75286 RPCRT4!LRPC_ADDRESS::ReceiveLotsaCalls+0×3a0184ff38 0184ff4c0184ff3c 77c75296 RPCRT4!LRPC_ADDRESS::ReceiveLotsaCalls+0×4a0184ff40 7c82f2fc ntdll!RtlLeaveCriticalSection0184ff44 000de3780184ff48 00097df00184ff4c 4d2fa2000184ff50 ffffffff0184ff54 ca5b17000184ff58 ffffffff0184ff5c 8082d8210184ff60 0184fe380184ff64 00097df00184ff68 000000aa0184ff6c 800200000184ff70 0184ff540184ff74 800200000184ff78 000b0c780184ff7c 00a501800184ff80 0184fe380184ff84 0184ff8c0184ff88 77c5778f RPCRT4!RecvLotsaCallsWrapper+0xd0184ff8c 0184ffac0184ff90 77c5f7dd RPCRT4!BaseCachedThreadRoutine+0×9d0184ff94 0009c4100184ff98 000000000184ff9c 000000000184ffa0 00097df00184ffa4 00097df00184ffa8 00015f900184ffac 0184ffb80184ffb0 77c5de88 RPCRT4!ThreadStartRoutine+0×1b0184ffb4 000882580184ffb8 0184ffec0184ffbc 77e6608b kernel32!BaseThreadStart+0×340184ffc0 00097df00184ffc4 000000000184ffc8 000000000184ffcc 00097df00184ffd0 8ad848180184ffd4 0184ffc40184ffd8 8980a7000184ffdc ffffffff0184ffe0 77e6b7d0 kernel32!_except_handler30184ffe4 77e66098 kernel32!`string‘+0×980184ffe8 000000000184ffec 000000000184fff0 0000000077c5de6d RPCRT4!ThreadStartRoutine0184fff8 00097df00184fffc 0000000001850000 00000008

Next, we need to use custom k command and specify the base pointer. In our case the last found stack address that links EBP pointers is 0184eef0:

0:011> k L=0184eef0ChildEBP RetAddrWARNING: Frame IP not in any known module. Following frames may be wrong.0184eef0 7615dff2 0×184e5bf0184ef0c 7615f9d0 localspl!SplDriverEvent+0×210184ef30 7614a9b4 localspl!PrinterDriverEvent+0×460184f3f8 761482de localspl!SplAddPrinter+0×5f30184f424 74067c8f localspl!LocalAddPrinterEx+0×2e0184f874 74067b76 SPOOLSS!AddPrinterExW+0×1510184f890 01007e29 SPOOLSS!AddPrinterW+0×170184f8ac 01006ec3 spoolsv!YAddPrinter+0×750184f8d0 77c70f3b spoolsv!RpcAddPrinter+0×370184f8f8 77ce23f7 RPCRT4!Invoke+0×300184fcf8 77ce26ed RPCRT4!NdrStubCall2+0×2990184fd14 77c709be RPCRT4!NdrServerCall2+0×190184fd48 77c7093f RPCRT4!DispatchToStubInCNoAvrf+0×380184fd9c 77c70865 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0×1170184fdc0 77c734b1 RPCRT4!RPC_INTERFACE::DispatchToStub+0xa30184fdfc 77c71bb3 RPCRT4!LRPC_SCALL::DealWithRequestMessage+0×42c0184fe20 77c75458 RPCRT4!LRPC_ADDRESS::DealWithLRPCRequest+0×1270184ff84 77c5778f RPCRT4!LRPC_ADDRESS::ReceiveLotsaCalls+0×4300184ff8c 77c5f7dd RPCRT4!RecvLotsaCallsWrapper+0xd

Stack traces make more sense now, but we don’t see BaseThreadStart+0×34. By default, WinDbg displays only certain amount of function calls (stack frames) so we need to specify stack frame count, for example, 100:

0:011> k L=0184eef0 100ChildEBP RetAddrWARNING: Frame IP not in any known module. Following frames may be wrong.0184eef0 7615dff2 0×184e5bf0184ef0c 7615f9d0 localspl!SplDriverEvent+0×210184ef30 7614a9b4 localspl!PrinterDriverEvent+0×460184f3f8 761482de localspl!SplAddPrinter+0×5f30184f424 74067c8f localspl!LocalAddPrinterEx+0×2e0184f874 74067b76 SPOOLSS!AddPrinterExW+0×1510184f890 01007e29 SPOOLSS!AddPrinterW+0×170184f8ac 01006ec3 spoolsv!YAddPrinter+0×750184f8d0 77c70f3b spoolsv!RpcAddPrinter+0×370184f8f8 77ce23f7 RPCRT4!Invoke+0×300184fcf8 77ce26ed RPCRT4!NdrStubCall2+0×2990184fd14 77c709be RPCRT4!NdrServerCall2+0×190184fd48 77c7093f RPCRT4!DispatchToStubInCNoAvrf+0×380184fd9c 77c70865 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0×1170184fdc0 77c734b1 RPCRT4!RPC_INTERFACE::DispatchToStub+0xa30184fdfc 77c71bb3 RPCRT4!LRPC_SCALL::DealWithRequestMessage+0×42c0184fe20 77c75458 RPCRT4!LRPC_ADDRESS::DealWithLRPCRequest+0×1270184ff84 77c5778f RPCRT4!LRPC_ADDRESS::ReceiveLotsaCalls+0×4300184ff8c 77c5f7dd RPCRT4!RecvLotsaCallsWrapper+0xd0184ffac 77c5de88 RPCRT4!BaseCachedThreadRoutine+0×9d0184ffb8 77e6608b RPCRT4!ThreadStartRoutine+0×1b0184ffec 00000000 kernel32!BaseThreadStart+0×34

Now our stack trace looks much better.

Sometimes incorrect stack trace is reported when symbols were not applied. Non-symbol gaps in stack traces can be the sign of this pattern too:

STACK_TEXT:WARNING: Stack unwind information not available. Following frames may bewrong.00b2f42c 091607aa mydll!foo+0×833800b2f4cc 7c83ab9e mydll!foo+0×8fe300b2f4ec 7c832d06 ntdll!RtlFindNextActivationContextSection+0×4600b2f538 001a5574 ntdll!RtlFindActivationContextSectionString+0xe100b2f554 7c8302b3 0×1a557400b2f560 7c82f9c1 ntdll!RtlpFreeToHeapLookaside+0×2200b2f640 7c832b7f ntdll!RtlFreeHeap+0×20e001dd000 00080040 ntdll!LdrUnlockLoaderLock+0xad001dd00c 0052005c 0×80040001dd010 00470045 0×52005c0052005c 00000000 0×470045

References:
1. Debugger Reference — Windows drivers | Microsoft Docs
2. Advanced Windows Debugging Book by Mario Hewardt
3. Memory Dump Analysis Anthology Book by Dmitry Vostokov

--

--