Modules/Callstack/private/Callstack.cpp
Шурупов Илья Викторович 63a6eee8c2 Write a hang report when a window's UI thread stops responding
Every Application now runs a HangWatchdog from start() to finish().
The main loop marks its phase (idle, input, draw, present); when the
UI thread stays in a non-idle phase past a threshold (2 s), a watchdog
thread writes a report folder to the per-user state directory:
$XDG_STATE_HOME/modules/hangs/<app>-<date>-build<rev>/, overridable
with MODULES_HANG_DIR, keeping the newest 20 reports.

summary.txt names the stuck phase, the chain of open TP_PROFILE_SCOPE
sections on the UI thread and a verdict: blocked at one place (with
the kernel wait channel), busy running, or waiting at changing places.
threads.txt holds five samples of every thread's stack. Follow-ups at
10 s and 30 s are appended, and recovery is noted when the UI thread
moves on. Applications add their own files through hang contributors;
Sketch2D writes engine queue state, read with try-lock so a report
never waits on the lock the UI may be stuck behind.

Architecture: the UI scope trail is a lock-free stack in Modules fed
by ProfileScope, so it works with the profiler off. Callstack gains
StackTrace, the single place for capturing a stack into a caller
buffer, naming frames with dladdr, and sampling all threads through a
real-time signal; CallStackCapture is rebuilt on it, removing the
addr2line subprocess. The watchdog lives in a new Diagnostics library
above both. The process also allows any same-user debugger to attach,
so gdb -p or eu-stack work on a frozen window.

Debug > Simulate hang (5 s) exercises the whole path in the app.
2026-10-22 01:07:42 +03:00

272 lines
No EOL
6.8 KiB
C++

#include "Callstack.hpp"
#include <iostream>
using namespace tp;
// frame to reference on no sapce left
static void errorNoSpaceLeft() {
// to disable optimization of function
static int dummy;
dummy = 10;
}
ualni CallStackCapture::CallStack::getDepth() const {
ualni len = 0;
for (long long frame : frames) {
if (!frame) {
break;
}
len++;
}
return len++;
}
ualni CallStackCapture::hashCallStack(CallStackKey key) {
auto const cs = key.cs;
ualni out = 0;
for (ualni i = 0; cs->frames[i]; i++) {
out += cs->frames[i];
}
return out;
}
bool CallStackCapture::CallStackKey::operator==(const CallStackCapture::CallStackKey& in) const {
for (ualni i = 0; i < MAX_CALL_DEPTH_CAPTURE; i++) {
if (cs->frames[i] != in.cs->frames[i]) {
return false;
}
if (cs->frames[i] == 0 && in.cs->frames[i] == 0) {
return true;
}
}
DEBUG_ASSERT(0 && "Must Not Happen")
return true;
}
CallStackCapture::CallStackCapture() {
static_assert(MAX_CALL_DEPTH_CAPTURE >= 1);
static_assert(MAX_CALL_CAPTURES_MEM_SIZE_MB > 0);
static_assert(MAX_DEBUG_INFO_LEN > sizeof("unresolved"));
static_assert(MAX_CALL_CAPTURES_MEM_SIZE_MB * 1024 * 1024 > sizeof(CallStack));
mBuffLoad = 0;
mBuffLen = STACKS_LENGTH;
mBuff = (CallStack*) malloc(mBuffLen * sizeof(CallStack));
mErrorSnapshot.frames[0] = (alni) &errorNoSpaceLeft;
platformInit();
initialized = true;
}
const CallStackCapture::CallStack* CallStackCapture::getSnapshot() {
if (mBuffLoad > mBuffLen) {
return &mErrorSnapshot;
}
CallStack* cs = &mBuff[mBuffLoad];
platformWriteStackTrace(cs);
auto idx = mSnapshots.presents({ cs });
if (idx) {
return mSnapshots.getSlotVal(idx);
}
mSnapshots.put({ cs }, cs);
mBuffLoad++;
return cs;
}
const CallStackCapture::DebugSymbols* CallStackCapture::getSymbols(FramePointer frame) {
auto idx = mSymbols.presents(frame);
if (idx) {
return &mSymbols.getSlotVal(idx);
}
mSymbols.put(frame, {});
auto symbols = &mSymbols.get(frame);
platformWriteDebugSymbols(frame, symbols);
return symbols;
}
void CallStackCapture::clear() {
mBuffLoad = 0;
mSnapshots.removeAll();
mSymbols.removeAll();
}
CallStackCapture::~CallStackCapture() {
free(mBuff);
initialized = false;
}
// ---------------------------------- Platform Depended ---------------------------------- //
#if defined(ENV_OS_LINUX) || defined(ENV_OS_ANDROID)
#include "StackTrace.hpp"
#include <cstring>
namespace {
void copyTail(char* out, const std::string& text) {
const size_t length = text.size();
const size_t start =
length > CallStackCapture::MAX_DEBUG_INFO_LEN ? length - CallStackCapture::MAX_DEBUG_INFO_LEN : 0;
std::strcpy(out, text.c_str() + start);
}
}
void CallStackCapture::platformInit() {}
void CallStackCapture::platformDeinit() {}
void CallStackCapture::platformWriteStackTrace(CallStack* stack) {
uintptr_t frames[MAX_CALL_DEPTH_CAPTURE];
const size_t depth = StackTrace::captureCallingThread(frames, MAX_CALL_DEPTH_CAPTURE - 1);
for (size_t index = 0; index < depth; index++)
stack->frames[index] = (FramePointer) frames[index];
stack->frames[depth] = 0;
}
void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) {
const FrameSymbol symbol = StackTrace::symbolize((uintptr_t) frame);
copyTail(out->file, symbol.module.empty() ? std::string("unresolved") : symbol.module);
copyTail(out->function, symbol.function.empty() ? std::string("unresolved") : symbol.function);
out->line = symbol.line;
}
void CallStackCapture::printSnapshot(const CallStack* snapshot) {
printf("CallStack: \n");
if (snapshot) {
for (auto frame : *snapshot) {
auto symbols = getSymbols(frame.getFrame());
printf(" %s ----- %s:%llu\n", symbols->getFunc(), symbols->getFile(), symbols->getLine());
}
}
printf("\n");
}
void CallStackCapture::logAll() {
for (auto cs : *this) {
printSnapshot(cs.getCallStack());
}
}
#else
#include <windows.h>
#include <dbghelp.h>
#include <stdio.h>
#pragma comment(lib, "dbghelp.lib")
void CallStackCapture::platformInit() {
HANDLE process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
}
void CallStackCapture::platformDeinit() {
HANDLE process = GetCurrentProcess();
SymCleanup(process);
}
void CallStackCapture::platformWriteStackTrace(CallStack* stack) {
DWORD hash;
CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
STACKFRAME64 stackFrame;
memset(&stackFrame, 0, sizeof(STACKFRAME64));
#ifdef _M_IX86
hash = IMAGE_FILE_MACHINE_I386;
stackFrame.AddrPC.Offset = context.Eip;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Ebp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Esp;
stackFrame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
hash = IMAGE_FILE_MACHINE_AMD64;
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Rbp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Rsp;
stackFrame.AddrStack.Mode = AddrModeFlat;
#endif
for (int frameIndex = 0; frameIndex < MAX_CALL_DEPTH_CAPTURE; ++frameIndex) {
if (!StackWalk64(
hash,
GetCurrentProcess(),
GetCurrentThread(),
&stackFrame,
&context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL
)) {
stack->frames[frameIndex] = 0;
break;
}
stack->frames[frameIndex] = (alni) (void*) stackFrame.AddrPC.Offset;
}
stack->frames[MAX_CALL_DEPTH_CAPTURE - 1] = 0;
}
void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) {
HANDLE process = GetCurrentProcess();
IMAGEHLP_LINE64 lineInfo;
DWORD displacement;
lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(process, (DWORD64) frame, &displacement, &lineInfo)) {
strcpy(out->file, lineInfo.FileName);
out->line = lineInfo.LineNumber;
} else {
strcpy(out->file, "unresolved");
out->line = 0;
}
DWORD64 displacementSym = 0;
char symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH] = { 0 };
IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*) symbolBuffer;
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
symbol->MaxNameLength = MAX_PATH;
if (SymGetSymFromAddr64(process, (DWORD64) frame, &displacementSym, symbol)) {
strcpy(out->function, symbol->Name);
} else {
strcpy(out->function, "unresolved");
}
}
void CallStackCapture::printSnapshot(const CallStack* snapshot) {
printf("CallStack: \n");
if (snapshot) {
for (int i = 0; i < snapshot->getDepth(); ++i) {
DebugSymbols symbols;
platformWriteDebugSymbols(snapshot->frames[i], &symbols);
printf(" %s ----- %s:%lu\n", symbols.function, symbols.file, symbols.line);
}
}
printf("\n");
}
void CallStackCapture::logAll() {
for (auto cs : *this) {
printSnapshot(cs.getCallStack());
}
}
#endif