/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/NucleusLib/pnCrashExe/pnCreError.cpp
*
***/
#include "../Pch.h"
#pragma hdrstop
namespace Crash {
/*****************************************************************************
*
* Private
*
***/
struct Module {
LINK(Module) link;
unsigned_ptr address;
unsigned buildId;
unsigned branchId;
wchar * name;
wchar * buildString;
~Module () { FREE(name); FREE(buildString); }
};
struct EmailParams : AtomicRef {
char * smtp;
char * sender;
char * recipients;
char * username;
char * password;
char * replyTo;
~EmailParams () {
FREE(smtp);
FREE(sender);
FREE(recipients);
FREE(username);
FREE(password);
FREE(replyTo);
}
};
struct ErrLog {
unsigned pos;
wchar name[MAX_PATH];
char buffer[512*1024];
char terminator;
};
struct DeadlockCheck {
LINK(DeadlockCheck) link;
HANDLE thread;
unsigned deadlockEmailMs;
unsigned deadlockTerminateMs;
bool deadlocked;
bool emailSent;
wchar debugStr[256];
};
/*****************************************************************************
*
* Private data
*
***/
// Assertion results from ProcessErrorLog()
const unsigned kErrFlagAssertionBreakpoint = 0x01;
const unsigned kErrFlagAssertionExitProgram = 0x02;
// Exception results from ProcessErrorLog()
const unsigned kErrFlagExceptionBreakpoint = 0x04;
const unsigned kErrFlagExceptionExecHandler = 0x08;
static const char s_unknown[] = "*unknown*";
static const char s_sectionFmt_s[] = "--------> %s <--------\r\n";
static const unsigned kStackDumpBytes = 1024;
static const unsigned kCodeDumpBytes = 64;
static unsigned s_deadlockEmailMs; // sends an email if a thread exceeds this time. If set to zero this is disabled
static unsigned s_deadlockTerminateMs; // kills the process if a thread exceeds this time. If set to zero this is disabled
static CCritSect * s_critsect;
static LISTDECL(Module, link) s_modules;
static EmailParams * s_params;
static bool s_running;
static bool s_deadlockEnabled;
static unsigned s_nextDeadlockCheck;
static LISTDECL(DeadlockCheck, link) s_deadlockList;
static CCritSect s_spareCrit;
static TSpareList s_deadlockSpares;
#define SAFE_CRITSECT_ENTER() if (s_critsect) s_critsect->Enter()
#define SAFE_CRITSECT_LEAVE() if (s_critsect) s_critsect->Leave()
/*****************************************************************************
*
* Internal functions
*
***/
//============================================================================
static void DelayDeadlockChecking () {
// Delay deadlock checking for the next 2 minutes
s_nextDeadlockCheck = 1 | (TimeGetMs() + 2 * 60 * 1000);
}
//============================================================================
static void ReplaceEmailParams (EmailParams * newParams) {
if (newParams)
newParams->IncRef();
SAFE_CRITSECT_ENTER();
{
SWAP(newParams, s_params);
}
SAFE_CRITSECT_LEAVE();
if (newParams)
newParams->DecRef();
}
//============================================================================
static EmailParams * GetEmailParamsIncRef () {
ref(GetEmailParamsIncRef);
EmailParams * params;
SAFE_CRITSECT_ENTER();
{
if (nil != (params = s_params))
params->IncRef();
}
SAFE_CRITSECT_LEAVE();
return params;
};
//============================================================================
#ifdef _M_IX86
static void __declspec(naked) CrashFunc () {
*(int *) 0 = 0;
}
#endif // def _M_IX86
//============================================================================
#ifdef _M_IX86
static void MakeThreadCrashOnResume (HANDLE handle) {
// Point the thread's instruction pointer to CrashFunc
// so that it will crash once we resume it.
CONTEXT context;
context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(handle, &context);
context.Eip = (DWORD) CrashFunc;
SetThreadContext(handle, &context);
}
#endif // def _M_IX86
//============================================================================
static inline const char * GetExceptionString (unsigned code) {
#if defined(HS_DEBUGGING) || defined(SERVER)
switch (code) {
#define EXCEPTION(x) case EXCEPTION_##x: return #x;
EXCEPTION(ACCESS_VIOLATION)
EXCEPTION(DATATYPE_MISALIGNMENT)
EXCEPTION(BREAKPOINT)
EXCEPTION(SINGLE_STEP)
EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
EXCEPTION(FLT_DENORMAL_OPERAND)
EXCEPTION(FLT_DIVIDE_BY_ZERO)
EXCEPTION(FLT_INEXACT_RESULT)
EXCEPTION(FLT_INVALID_OPERATION)
EXCEPTION(FLT_OVERFLOW)
EXCEPTION(FLT_STACK_CHECK)
EXCEPTION(FLT_UNDERFLOW)
EXCEPTION(INT_DIVIDE_BY_ZERO)
EXCEPTION(INT_OVERFLOW)
EXCEPTION(PRIV_INSTRUCTION)
EXCEPTION(IN_PAGE_ERROR)
EXCEPTION(ILLEGAL_INSTRUCTION)
EXCEPTION(NONCONTINUABLE_EXCEPTION)
EXCEPTION(STACK_OVERFLOW)
EXCEPTION(INVALID_DISPOSITION)
EXCEPTION(GUARD_PAGE)
EXCEPTION(INVALID_HANDLE)
#undef EXCEPTION
default:
return s_unknown;
}
#else // if defined(HS_DEBUGGING) || defined(SERVER)
{
ref(code);
return "";
}
#endif // if defined(HS_DEBUGGING) || defined(SERVER)
}
//============================================================================
static void LogWriteToDisk (ErrLog * const log) {
HANDLE file = CreateFileW(
log->name,
GENERIC_WRITE,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES) nil,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) nil
);
if (file != INVALID_HANDLE_VALUE) {
DWORD bytesWritten;
SetFilePointer(file, 0, 0, FILE_END);
WriteFile(file, log->buffer, StrLen(log->buffer), &bytesWritten, 0);
CloseHandle(file);
}
}
//============================================================================
static unsigned ProcessErrorLog (
ErrLog * const log,
const char programName[],
const char errorType[]
) {
ref(programName);
ref(errorType);
LogWriteToDisk(log);
// Servers email the error and continue running
#ifdef SERVER
{
// @@@ TODO: Write log to file here
if (EmailParams * params = GetEmailParamsIncRef()) {
CrashSendEmail(
params->smtp,
params->sender,
params->recipients,
params->username,
params->password,
params->replyTo,
programName,
errorType,
log->buffer
);
params->DecRef();
}
return kErrFlagAssertionBreakpoint | kErrFlagExceptionExecHandler;
}
// Client programs display an error dialog giving the user a choice of
// sending the error or not
#else
{
// Todo: make a dialog box to handle this
return kErrFlagAssertionExitProgram | kErrFlagExceptionExecHandler;
}
#endif
}
//============================================================================
static unsigned ProcessBreakException () {
#ifdef SERVER
// Servers running as daemons should attempt to keep running
if (ErrorGetOption(kErrOptNonGuiAsserts))
return kErrFlagExceptionExecHandler;
else
return kErrFlagExceptionBreakpoint;
#else
return kErrFlagExceptionBreakpoint;
#endif
}
//============================================================================
static ErrLog * CreateErrLog () {
// Allocate log memory
ErrLog * log = (ErrLog *) VirtualAlloc(nil, sizeof(*log), MEM_COMMIT, PAGE_READWRITE);
log->pos = 0;
log->terminator = 0;
// Initialize log filename
wchar srcName[MAX_PATH];
SYSTEMTIME currTime;
GetLocalTime(&currTime);
StrPrintf(
srcName,
arrsize(srcName),
L"Crash%02u%02u%02u.log",
currTime.wYear % 100,
currTime.wMonth,
currTime.wDay
);
// Set log directory + filename
AsyncLogGetDirectory(log->name, arrsize(log->name));
PathAddFilename(log->name, log->name, srcName, arrsize(log->name));
return log;
}
//============================================================================
static void DestroyErrLog (ErrLog * errLog) {
VirtualFree(errLog, 0, MEM_RELEASE);
}
//============================================================================
static void __cdecl LogPrintf (
ErrLog * const log,
const char fmt[],
...
) {
va_list args;
va_start(args, fmt);
char * pos = log->buffer + log->pos;
unsigned len = StrPrintfV(
pos,
arrsize(log->buffer) - log->pos,
fmt,
args
);
va_end(args);
log->pos += len;
}
//============================================================================
static inline void LogSectionHeader (
ErrLog * const log,
const char section[]
) {
LogPrintf(log, s_sectionFmt_s, section);
}
//============================================================================
#ifdef _M_IX86
static void LogStackWalk (
ErrLog * const log,
const CImageHelp & ih,
HANDLE hThread,
const CONTEXT & ctx
) {
STACKFRAME stk;
ZeroMemory(&stk, sizeof(stk));
stk.AddrPC.Offset = ctx.Eip;
stk.AddrPC.Mode = AddrModeFlat;
stk.AddrStack.Offset = ctx.Esp;
stk.AddrStack.Mode = AddrModeFlat;
stk.AddrFrame.Offset = ctx.Ebp;
stk.AddrFrame.Mode = AddrModeFlat;
LogSectionHeader(log, "Trace");
for (unsigned i = 0; i < 100; i++) {
const bool result = ih.StackWalk(
IMAGE_FILE_MACHINE_I386,
ih.Process(),
hThread,
&stk,
nil, // context
nil, // read memory routine
ih.SymFunctionTableAccess,
ih.SymGetModuleBase,
nil // translate address routine
);
if (!result)
break;
LogPrintf(
log,
"Pc:%08x Fr:%08x Rt:%08x Arg:%08x %08x %08x %08x ",
stk.AddrPC.Offset,
stk.AddrFrame.Offset,
stk.AddrReturn.Offset,
stk.Params[0],
stk.Params[1],
stk.Params[2],
stk.Params[3]
);
LogPrintf(log, "\r\n");
}
LogPrintf(log, "\r\n");
}
#endif // _M_IX86
//============================================================================
static void LogThread (
ErrLog * const log,
const CImageHelp & ih,
HANDLE hThread,
const wchar name[],
const CONTEXT & ctx
) {
char threadName[256];
StrPrintf(threadName, arrsize(threadName), "%SThread %#x", name, hThread);
LogSectionHeader(log, threadName);
LogPrintf(
log,
"eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx\r\n"
"esi=%08lx edi=%08lx\r\n"
"eip=%08lx esp=%08lx ebp=%08lx\r\n"
"cs=%04lx ss=%04lx ds=%04lx es=%04lx fs=%04lx gs=%04lx efl=%08lx\r\n\r\n",
ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.Esi, ctx.Edi,
ctx.Eip, ctx.Esp, ctx.Ebp, ctx.EFlags,
ctx.SegCs, ctx.SegSs, ctx.SegDs, ctx.SegEs, ctx.SegFs, ctx.SegGs, ctx.EFlags
);
#ifdef _M_IX86
LogStackWalk(log, ih, hThread, ctx);
#else
ref(ih);
ref(hThread);
#endif
}
//============================================================================
static void LogCrashInfo (
const char msg[],
ErrLog * const log,
const CImageHelp & ih
) {
// Log application information
{
LogSectionHeader(log, "Program");
wchar productIdStr[64];
GuidToString(ProductId(), productIdStr, arrsize(productIdStr));
wchar productBuildTag[128];
ProductString(productBuildTag, arrsize(productBuildTag));
SYSTEMTIME currtime;
GetLocalTime(&currtime);
LogPrintf(
log,
// beginning of format string
"App : %s\r\n"
"Build : "
#ifdef PLASMA_EXTERNAL_RELEASE
"External "
#else
"Internal "
#endif
#ifdef HS_DEBUGGING
"Debug"
#else
"Release"
#endif
"\r\n"
"BuildMark : %S\r\n"
"ProductTag : %S\r\n"
"ProductId : %S\r\n"
"Crashed : %d/%d/%d %02d:%02d:%02d\r\n"
"Msg : %s\r\n"
"\r\n",
// end of format string
ih.GetProgramName(),
ProductBuildString(),
productBuildTag,
productIdStr,
currtime.wMonth,
currtime.wDay,
currtime.wYear,
currtime.wHour,
currtime.wMinute,
currtime.wSecond,
msg
);
}
// Log system information
{
LogSectionHeader(log, "System");
char machineName[128];
DWORD len = arrsize(machineName);
if (!GetComputerName(machineName, &len))
StrCopy(machineName, s_unknown, arrsize(machineName));
LogPrintf(log, "Machine : %s\r\n", machineName);
for (;;) {
WSADATA wsaData;
if (WSAStartup(0x101, &wsaData) || (wsaData.wVersion != 0x101))
break;
wchar ipAddress[256];
NetAddressNode addrNodes[16];
unsigned addrCount = NetAddressGetLocal(16, addrNodes);
LogPrintf(log, "IpAddrs : ");
for (unsigned i = 0; i < addrCount; ++i) {
NetAddressNodeToString(addrNodes[i], ipAddress, arrsize(ipAddress));
LogPrintf(log, "%S, ", ipAddress);
}
LogPrintf(log, "\r\n");
WSACleanup();
break;
}
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD ver = GetVersion();
LogPrintf(
log,
"OS Version : %u.%u\r\n"
"CPU Count : %u\r\n"
"\r\n",
LOBYTE(LOWORD(ver)), HIBYTE(LOWORD(ver)),
si.dwNumberOfProcessors
);
}
// Log loaded modules
{
LogSectionHeader(log, "Modules");
SAFE_CRITSECT_ENTER();
for (const Module * p = s_modules.Head(); p; p = p->link.Next()) {
LogPrintf(
log,
"%p %S (%u.%u.%S)\r\n",
p->address,
p->name,
p->buildId,
p->branchId,
p->buildString
);
}
SAFE_CRITSECT_LEAVE();
LogPrintf(log, "\r\n");
}
}
//============================================================================
static LONG ProcessException (const char occasion[], EXCEPTION_POINTERS * ep) {
// log non-breakpont exceptions
unsigned result;
if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT) {
// if this is a stack fault, allocate a new stack so we have
// enough space to log the error.
static void * newStack;
#define STACKBYTES (64 * 1024)
if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) {
newStack = VirtualAlloc(nil, STACKBYTES, MEM_COMMIT, PAGE_READWRITE);
__asm {
mov eax, [newStack] // get new memory block
add eax, STACKBYTES - 4 // point to end of block
mov [eax], esp // save current stack pointer
mov esp, eax // set new stack pointer
}
}
DelayDeadlockChecking();
// Allocate error log memory
ErrLog * log = CreateErrLog();
// get crash log data
static const char s_exception[] = "Exception";
LogSectionHeader(log, "Crash");
LogPrintf(
log,
"%s: %08x %s\r\n",
s_exception,
ep->ExceptionRecord->ExceptionCode,
GetExceptionString(ep->ExceptionRecord->ExceptionCode)
);
if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
LogPrintf(
log,
"Memory at address %08x could not be %s\r\n",
ep->ExceptionRecord->ExceptionInformation[1],
ep->ExceptionRecord->ExceptionInformation[0] ? "written" : "read"
);
}
LogPrintf(log, "\r\n");
CImageHelp ih((HINSTANCE)ModuleGetInstance());
LogCrashInfo(occasion, log, ih);
// log crashed thread
LogThread(
log,
ih,
GetCurrentThread(),
L"",
*ep->ContextRecord
);
// display the error
result = ProcessErrorLog(
log,
ih.GetProgramName(),
occasion
);
DestroyErrLog(log);
ErrorSetOption(kErrOptDisableMemLeakChecking, true);
// if this is a stack overflow, restore original stack
if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) {
__asm {
mov eax, [newStack] // get new memory block
add eax, STACKBYTES - 4 // point to end of block
mov esp, [eax] // restore old stack pointer
}
VirtualFree(newStack, 0, MEM_RELEASE);
}
}
else {
result = ProcessBreakException();
}
// If this is a debug build and the user pressed the "debug" button
// then we return EXCEPTION_CONTINUE_SEARCH so the program will
// activate just-in-time debugging, or barring that, bring up the
// Microsoft error handler.
if (result & kErrFlagExceptionBreakpoint)
return EXCEPTION_CONTINUE_SEARCH;
return EXCEPTION_EXECUTE_HANDLER;
}
//============================================================================
static LONG WINAPI ExceptionFilter (EXCEPTION_POINTERS * ep) {
ref(ExceptionFilter);
LONG result = ProcessException("Unhandled Exception", ep);
// If the instruction pointer is inside CrashFunc then this exception
// is a deadlock that couldn't be resumed, so terminate the program.
#ifdef _M_IX86
if ((ep->ContextRecord->Eip >= (DWORD) CrashFunc)
&& (ep->ContextRecord->Eip <= (DWORD) CrashFunc + 5))
TerminateProcess(GetCurrentProcess(), 1);
#else
# error "ExceptionFilter not implemented for this CPU"
#endif // def _M_IX86
return result;
}
//============================================================================
static void ProcessDeadlock_CS (const char occasion[], bool crashIt = true) {
unsigned currTimeMs = TimeGetMs();
unsigned crashCount = 0;
// Suspend all threads so that we can dump their callstacks
DeadlockCheck * next, * check = s_deadlockList.Head();
for (; check; check = next) {
next = s_deadlockList.Next(check);
SuspendThread(check->thread);
}
// Allocate error log memory
ErrLog * log = CreateErrLog();
// Log report header and system data
CImageHelp ih((HINSTANCE)ModuleGetInstance());
LogCrashInfo(occasion, log, ih);
// Log all threads
if (!s_deadlockList.Head())
LogPrintf(log, "*** No threads queued for deadlock check ***\r\n\r\n");
check = s_deadlockList.Head();
for (; check; check = next) {
next = s_deadlockList.Next(check);
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_CONTROL;
GetThreadContext(
check->thread,
&ctx
);
if (true == (check->deadlocked = (int)(check->deadlockEmailMs - currTimeMs) < 0) && !check->emailSent && s_deadlockEmailMs) {
LogPrintf(log, "This thread has hit the min deadlock check time\r\n");
check->emailSent = true;
}
else if (true == (check->deadlocked = (int)(check->deadlockTerminateMs - currTimeMs) < 0) && s_deadlockTerminateMs) {
LogPrintf(log, "This thread has hit the max deadlock check time\r\n");
if (crashIt) {
MakeThreadCrashOnResume(check->thread);
++crashCount;
}
}
if(check->deadlocked) {
LogPrintf(log, "Debug information: ");
LogPrintf(log, "%S\r\n", check->debugStr);
}
LogThread(
log,
ih,
check->thread,
L"",
ctx
);
}
(void) ProcessErrorLog(
log,
ih.GetProgramName(),
occasion
);
DestroyErrLog(log);
ErrorSetOption(kErrOptDisableMemLeakChecking, true);
// Resume all threads
unsigned elapsedMs = TimeGetMs() - currTimeMs;
check = s_deadlockList.Head();
for (; check; check = next) {
next = s_deadlockList.Next(check);
if (check->deadlocked && crashIt)
// remove 'check' from list since we're crashing the thread on resume
check->link.Unlink();
else {
// Update deadlock time to offset by the amount of time we had them suspended.
check->deadlockEmailMs += elapsedMs;
check->deadlockTerminateMs += elapsedMs;
}
ResumeThread(check->thread);
}
// Allow the resumed thread a bit of time to crash and
// send the resulting email then terminate the process
if (crashCount) {
Sleep(60 * 1000);
TerminateProcess(GetCurrentProcess(), 1);
}
}
//============================================================================
static void DeadlockCheckProc (void *) {
ref(DeadlockCheckProc);
while (s_running) {
Sleep(5 * 1000);
if (!s_deadlockEnabled)
continue;
unsigned currTimeMs = TimeGetMs();
// Check for a forced delay in deadlock checking
if (s_nextDeadlockCheck && (int)(s_nextDeadlockCheck - currTimeMs) > 0)
continue;
s_nextDeadlockCheck = 0;
SAFE_CRITSECT_ENTER();
for (;;) {
DeadlockCheck * next, * check = s_deadlockList.Head();
for (; check; check = next) {
next = s_deadlockList.Next(check);
if ((int)(check->deadlockEmailMs - currTimeMs) <= 0 && !check->emailSent && s_deadlockEmailMs) {
// we have hit our minimum deadlock check time. Send and email but dont crash the process, yet.
ProcessDeadlock_CS("DeadlockChecker", false);
check->emailSent = true;
break;
}
else if ((int)(check->deadlockTerminateMs - currTimeMs) <= 0 && s_deadlockTerminateMs){
// we have hit out max deadlock check time. This will send an email and crash the process so the service can restart.
ProcessDeadlock_CS("DeadlockChecker(Process Terminated)");
break;
}
}
break;
}
SAFE_CRITSECT_LEAVE();
}
}
//============================================================================
#ifdef SERVER
static void StartDeadlockThread () {
(void)_beginthread(DeadlockCheckProc, 0, nil);
}
#endif
//============================================================================
#ifdef SERVER
static void DeadlockCheckNowProc (void *) {
SAFE_CRITSECT_ENTER();
{
ProcessDeadlock_CS("Deadlock Check");
}
SAFE_CRITSECT_LEAVE();
}
#endif
//============================================================================
#ifdef SERVER
static void ThreadReportProc (void *) {
SAFE_CRITSECT_ENTER();
{
ProcessDeadlock_CS("Thread Report", false);
}
SAFE_CRITSECT_LEAVE();
}
#endif
//============================================================================
static void pnCrashExeShutdown () {
s_running = false;
ReplaceEmailParams(nil);
ASSERT(!s_deadlockList.Head());
s_deadlockSpares.CleanUp();
}
//============================================================================
AUTO_INIT_FUNC(pnCrashExe) {
// The critical section has to be initialized
// before program startup and never freed
static byte rawMemory[sizeof CCritSect];
s_critsect = new(rawMemory) CCritSect;
s_running = true;
atexit(pnCrashExeShutdown);
#ifdef SERVER
SetUnhandledExceptionFilter(ExceptionFilter);
StartDeadlockThread();
#endif
}
/*****************************************************************************
*
* Module functions
*
***/
} // namespace Crash
/*****************************************************************************
*
* Exports
*
***/
//============================================================================
void CrashExceptionDump (const char occasion[], void * info) {
(void) ProcessException(occasion, (EXCEPTION_POINTERS *) info);
}
//============================================================================
void CrashSetEmailParameters (
const char smtp[],
const char sender[],
const char recipients[],
const char username[],
const char password[],
const char replyTo[]
) {
ASSERT(smtp);
ASSERT(sender);
ASSERT(recipients);
EmailParams * params = NEWZERO(EmailParams);
params->smtp = StrDup(smtp);
params->sender = StrDup(sender);
params->recipients = StrDup(recipients);
if (username)
params->username = StrDup(username);
if (password)
params->password = StrDup(password);
if (replyTo)
params->replyTo = StrDup(replyTo);
ReplaceEmailParams(params);
}
//============================================================================
void * CrashAddModule (
unsigned_ptr address,
unsigned buildId,
unsigned branchId,
const wchar name[],
const wchar buildString[]
) {
ASSERT(name);
Module * module = NEWZERO(Module);
module->address = address;
module->buildId = buildId;
module->branchId = branchId;
module->name = StrDup(name);
module->buildString = StrDup(buildString);
// trim trailing spaces from buildString
for (unsigned i = StrLen(buildString) - 1; i > 0; --i) {
if (module->buildString[i] != L' ')
break;
module->buildString[i] = 0;
}
SAFE_CRITSECT_ENTER();
{
s_modules.Link(module);
}
SAFE_CRITSECT_LEAVE();
return module;
}
//============================================================================
void CrashRemoveModule (
void * param
) {
Module * module = (Module *) param;
SAFE_CRITSECT_ENTER();
{
DEL(module);
}
SAFE_CRITSECT_LEAVE();
}
/*****************************************************************************
*
* Deadlock detection (server only)
*
***/
//============================================================================
#ifdef SERVER
void * CrashAddDeadlockCheck (
void * thread,
const wchar debugStr[]
) {
s_spareCrit.Enter();
DeadlockCheck * check = (DeadlockCheck *)s_deadlockSpares.Alloc();
s_spareCrit.Leave();
(void) new(check) DeadlockCheck;
check->deadlockEmailMs = TimeGetMs() + s_deadlockEmailMs;
check->deadlockTerminateMs = TimeGetMs() + s_deadlockTerminateMs;
check->thread = (HANDLE) thread;
check->emailSent = false;
StrCopy(check->debugStr, debugStr, arrsize(check->debugStr));
SAFE_CRITSECT_ENTER();
{
s_deadlockList.Link(check);
}
SAFE_CRITSECT_LEAVE();
return check;
}
#endif
//============================================================================
#ifdef SERVER
void CrashRemoveDeadlockCheck (
void * ptr
) {
ASSERT(ptr);
DeadlockCheck * check = (DeadlockCheck *)ptr;
SAFE_CRITSECT_ENTER();
{
s_deadlockList.Unlink(check);
}
SAFE_CRITSECT_LEAVE();
check->~DeadlockCheck();
s_spareCrit.Enter();
s_deadlockSpares.Free(check);
s_spareCrit.Leave();
}
#endif
//============================================================================
#ifdef SERVER
bool CrashEnableDeadlockChecking (
bool enable
) {
SWAP(s_deadlockEnabled, enable);
return enable;
}
#endif
//============================================================================
#ifdef SERVER
void CrashSetDeadlockCheckTimes(unsigned emailSec, unsigned terminateSec) {
if(!emailSec && !terminateSec)
CrashEnableDeadlockChecking(false);
else {
CrashEnableDeadlockChecking(true);
s_deadlockEmailMs = emailSec * 1000;
s_deadlockTerminateMs = terminateSec * 1000;
}
}
#endif
//============================================================================
#ifdef SERVER
void CrashDeadlockCheckNow () {
// Perform in a thread not queued for deadlock checking
(void)_beginthread(DeadlockCheckNowProc, 0, nil);
}
#endif
//============================================================================
#ifdef SERVER
void CrashSendThreadReport () {
// Perform in a thread not queued for deadlock checking
(void)_beginthread(ThreadReportProc, 0, nil);
}
#endif