851 lines
19 KiB
C++
851 lines
19 KiB
C++
#include "engine/debug.h"
|
|
#include "engine/log.h"
|
|
#include "engine/mt/atomic.h"
|
|
#include "engine/os.h"
|
|
#include "engine/string.h"
|
|
#include <windows.h>
|
|
#pragma warning (push)
|
|
#pragma warning (disable: 4091) // declaration of 'xx' hides previous local declaration
|
|
#include <DbgHelp.h>
|
|
#pragma warning (pop)
|
|
#include <mapi.h>
|
|
#include <cfloat>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include "engine/allocator.h"
|
|
|
|
|
|
#pragma comment(lib, "DbgHelp.lib")
|
|
|
|
|
|
static bool g_is_crash_reporting_enabled = true;
|
|
static Lumix::DefaultAllocator xxallocator;
|
|
|
|
|
|
namespace Lumix
|
|
{
|
|
|
|
|
|
namespace Debug
|
|
{
|
|
|
|
|
|
void enableFloatingPointTraps(bool enable)
|
|
{
|
|
unsigned int cw = _control87(0, 0) & MCW_EM;
|
|
if (enable)
|
|
{
|
|
cw &= ~(_EM_OVERFLOW | _EM_ZERODIVIDE | _EM_INVALID | _EM_DENORMAL); // can not enable _EM_INEXACT because it is common in QT
|
|
}
|
|
else
|
|
{
|
|
cw |= _EM_OVERFLOW | _EM_INVALID | _EM_DENORMAL; // can not enable _EM_INEXACT because it is common in QT
|
|
}
|
|
_control87(cw, MCW_EM);
|
|
}
|
|
|
|
|
|
void debugOutput(const char* message)
|
|
{
|
|
OutputDebugString(message);
|
|
}
|
|
|
|
|
|
void debugBreak()
|
|
{
|
|
DebugBreak();
|
|
}
|
|
|
|
|
|
int StackTree::s_instances = 0;
|
|
|
|
|
|
class StackNode
|
|
{
|
|
public:
|
|
~StackNode()
|
|
{
|
|
LUMIX_DELETE(xxallocator, m_next);
|
|
LUMIX_DELETE(xxallocator, m_first_child);
|
|
}
|
|
|
|
void* m_instruction;
|
|
StackNode* m_next = nullptr;
|
|
StackNode* m_first_child = nullptr;
|
|
StackNode* m_parent;
|
|
};
|
|
|
|
|
|
StackTree::StackTree()
|
|
{
|
|
m_root = nullptr;
|
|
if (MT::atomicIncrement(&s_instances) == 1)
|
|
{
|
|
HANDLE process = GetCurrentProcess();
|
|
SymInitialize(process, nullptr, TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
StackTree::~StackTree()
|
|
{
|
|
LUMIX_DELETE(xxallocator, m_root);
|
|
if (MT::atomicDecrement(&s_instances) == 0)
|
|
{
|
|
HANDLE process = GetCurrentProcess();
|
|
SymCleanup(process);
|
|
}
|
|
}
|
|
|
|
|
|
void StackTree::refreshModuleList()
|
|
{
|
|
ASSERT(s_instances > 0);
|
|
SymRefreshModuleList(GetCurrentProcess());
|
|
}
|
|
|
|
|
|
int StackTree::getPath(StackNode* node, StackNode** output, int max_size)
|
|
{
|
|
int i = 0;
|
|
while (i < max_size && node)
|
|
{
|
|
output[i] = node;
|
|
i++;
|
|
node = node->m_parent;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
StackNode* StackTree::getParent(StackNode* node)
|
|
{
|
|
return node ? node->m_parent : nullptr;
|
|
}
|
|
|
|
|
|
bool StackTree::getFunction(StackNode* node, char* out, int max_size, int* line)
|
|
{
|
|
HANDLE process = GetCurrentProcess();
|
|
u8 symbol_mem[sizeof(SYMBOL_INFO) + 256 * sizeof(char)] = {};
|
|
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(symbol_mem);
|
|
symbol->MaxNameLen = 255;
|
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
BOOL success = SymFromAddr(process, (DWORD64)(node->m_instruction), 0, symbol);
|
|
IMAGEHLP_LINE64 line_info;
|
|
DWORD displacement;
|
|
if (SymGetLineFromAddr64(process, (DWORD64)(node->m_instruction), &displacement, &line_info))
|
|
{
|
|
*line = line_info.LineNumber;
|
|
}
|
|
else
|
|
{
|
|
*line = -1;
|
|
}
|
|
if (success) copyString(out, max_size, symbol->Name);
|
|
|
|
return success != FALSE;
|
|
}
|
|
|
|
|
|
void StackTree::printCallstack(StackNode* node)
|
|
{
|
|
while (node)
|
|
{
|
|
HANDLE process = GetCurrentProcess();
|
|
u8 symbol_mem[sizeof(SYMBOL_INFO) + 256 * sizeof(char)];
|
|
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(symbol_mem);
|
|
memset(symbol_mem, 0, sizeof(symbol_mem));
|
|
symbol->MaxNameLen = 255;
|
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
BOOL success = SymFromAddr(process, (DWORD64)(node->m_instruction), 0, symbol);
|
|
if (success)
|
|
{
|
|
IMAGEHLP_LINE line;
|
|
DWORD offset;
|
|
if (SymGetLineFromAddr(process, (DWORD64)(node->m_instruction), &offset, &line))
|
|
{
|
|
OutputDebugString("\t");
|
|
OutputDebugString(line.FileName);
|
|
OutputDebugString("(");
|
|
char tmp[20];
|
|
toCString((u32)line.LineNumber, tmp, sizeof(tmp));
|
|
OutputDebugString(tmp);
|
|
OutputDebugString("):");
|
|
}
|
|
OutputDebugString("\t");
|
|
OutputDebugString(symbol->Name);
|
|
OutputDebugString("\n");
|
|
}
|
|
else
|
|
{
|
|
OutputDebugString("\tN/A\n");
|
|
}
|
|
node = node->m_parent;
|
|
}
|
|
}
|
|
|
|
|
|
StackNode* StackTree::insertChildren(StackNode* root_node, void** instruction, void** stack)
|
|
{
|
|
StackNode* node = root_node;
|
|
while (instruction >= stack)
|
|
{
|
|
StackNode* new_node = LUMIX_NEW(xxallocator, StackNode)();
|
|
node->m_first_child = new_node;
|
|
new_node->m_parent = node;
|
|
new_node->m_next = nullptr;
|
|
new_node->m_first_child = nullptr;
|
|
new_node->m_instruction = *instruction;
|
|
node = new_node;
|
|
--instruction;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
|
|
StackNode* StackTree::record()
|
|
{
|
|
static const int frames_to_capture = 256;
|
|
void* stack[frames_to_capture];
|
|
USHORT captured_frames_count = CaptureStackBackTrace(2, frames_to_capture, stack, 0);
|
|
|
|
void** ptr = stack + captured_frames_count - 1;
|
|
if (!m_root)
|
|
{
|
|
m_root = LUMIX_NEW(xxallocator, StackNode)();
|
|
m_root->m_instruction = *ptr;
|
|
m_root->m_first_child = nullptr;
|
|
m_root->m_next = nullptr;
|
|
m_root->m_parent = nullptr;
|
|
--ptr;
|
|
return insertChildren(m_root, ptr, stack);
|
|
}
|
|
|
|
StackNode* node = m_root;
|
|
while (ptr >= stack)
|
|
{
|
|
while (node->m_instruction != *ptr && node->m_next)
|
|
{
|
|
node = node->m_next;
|
|
}
|
|
if (node->m_instruction != *ptr)
|
|
{
|
|
node->m_next = LUMIX_NEW(xxallocator, StackNode);
|
|
node->m_next->m_parent = node->m_parent;
|
|
node->m_next->m_instruction = *ptr;
|
|
node->m_next->m_next = nullptr;
|
|
node->m_next->m_first_child = nullptr;
|
|
--ptr;
|
|
return insertChildren(node->m_next, ptr, stack);
|
|
}
|
|
|
|
if (node->m_first_child)
|
|
{
|
|
--ptr;
|
|
node = node->m_first_child;
|
|
}
|
|
else if (ptr != stack)
|
|
{
|
|
--ptr;
|
|
return insertChildren(node, ptr, stack);
|
|
}
|
|
else
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
static const u32 UNINITIALIZED_MEMORY_PATTERN = 0xCD;
|
|
static const u32 FREED_MEMORY_PATTERN = 0xDD;
|
|
static const u32 ALLOCATION_GUARD = 0xFDFDFDFD;
|
|
|
|
|
|
Allocator::Allocator(IAllocator& source)
|
|
: m_source(source)
|
|
, m_root(nullptr)
|
|
, m_total_size(0)
|
|
, m_is_fill_enabled(true)
|
|
, m_are_guards_enabled(true)
|
|
{
|
|
m_sentinels[0].next = &m_sentinels[1];
|
|
m_sentinels[0].previous = nullptr;
|
|
m_sentinels[0].stack_leaf = nullptr;
|
|
m_sentinels[0].size = 0;
|
|
m_sentinels[0].align = 0;
|
|
|
|
m_sentinels[1].next = nullptr;
|
|
m_sentinels[1].previous = &m_sentinels[0];
|
|
m_sentinels[1].stack_leaf = nullptr;
|
|
m_sentinels[1].size = 0;
|
|
m_sentinels[1].align = 0;
|
|
|
|
m_root = &m_sentinels[1];
|
|
}
|
|
|
|
|
|
void Allocator::checkLeaks()
|
|
{
|
|
AllocationInfo* last_sentinel = &m_sentinels[1];
|
|
if (m_root != last_sentinel)
|
|
{
|
|
OutputDebugString("Memory leaks detected!\n");
|
|
AllocationInfo* info = m_root;
|
|
while (info != last_sentinel)
|
|
{
|
|
char tmp[2048];
|
|
sprintf(tmp, "\nAllocation size : %Iu, memory %p\n", info->size, info + sizeof(info));
|
|
OutputDebugString(tmp);
|
|
m_stack_tree.printCallstack(info->stack_leaf);
|
|
info = info->next;
|
|
}
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
|
|
Allocator::~Allocator()
|
|
{
|
|
checkLeaks();
|
|
}
|
|
|
|
|
|
void Allocator::lock()
|
|
{
|
|
m_mutex.enter();
|
|
}
|
|
|
|
|
|
void Allocator::unlock()
|
|
{
|
|
m_mutex.exit();
|
|
}
|
|
|
|
|
|
void Allocator::checkGuards()
|
|
{
|
|
if (m_are_guards_enabled) return;
|
|
|
|
auto* info = m_root;
|
|
while (info)
|
|
{
|
|
auto user_ptr = getUserPtrFromAllocationInfo(info);
|
|
void* system_ptr = getSystemFromUser(user_ptr);
|
|
ASSERT(*(u32*)system_ptr == ALLOCATION_GUARD);
|
|
ASSERT(*(u32*)((u8*)user_ptr + info->size) == ALLOCATION_GUARD);
|
|
|
|
info = info->next;
|
|
}
|
|
}
|
|
|
|
|
|
size_t Allocator::getAllocationOffset()
|
|
{
|
|
return sizeof(AllocationInfo) + (m_are_guards_enabled ? sizeof(ALLOCATION_GUARD) : 0);
|
|
}
|
|
|
|
|
|
size_t Allocator::getNeededMemory(size_t size)
|
|
{
|
|
return size + sizeof(AllocationInfo) + (m_are_guards_enabled ? sizeof(ALLOCATION_GUARD) << 1 : 0);
|
|
}
|
|
|
|
|
|
size_t Allocator::getNeededMemory(size_t size, size_t align)
|
|
{
|
|
return size + sizeof(AllocationInfo) + (m_are_guards_enabled ? sizeof(ALLOCATION_GUARD) << 1 : 0) +
|
|
align;
|
|
}
|
|
|
|
|
|
Allocator::AllocationInfo* Allocator::getAllocationInfoFromSystem(void* system_ptr)
|
|
{
|
|
return (AllocationInfo*)(m_are_guards_enabled ? (u8*)system_ptr + sizeof(ALLOCATION_GUARD)
|
|
: system_ptr);
|
|
}
|
|
|
|
|
|
void* Allocator::getUserPtrFromAllocationInfo(AllocationInfo* info)
|
|
{
|
|
return ((u8*)info + sizeof(AllocationInfo));
|
|
}
|
|
|
|
|
|
Allocator::AllocationInfo* Allocator::getAllocationInfoFromUser(void* user_ptr)
|
|
{
|
|
return (AllocationInfo*)((u8*)user_ptr - sizeof(AllocationInfo));
|
|
}
|
|
|
|
|
|
u8* Allocator::getUserFromSystem(void* system_ptr, size_t align)
|
|
{
|
|
size_t diff = (m_are_guards_enabled ? sizeof(ALLOCATION_GUARD) : 0) + sizeof(AllocationInfo);
|
|
|
|
if (align) diff += (align - diff % align) % align;
|
|
return (u8*)system_ptr + diff;
|
|
}
|
|
|
|
|
|
u8* Allocator::getSystemFromUser(void* user_ptr)
|
|
{
|
|
AllocationInfo* info = getAllocationInfoFromUser(user_ptr);
|
|
size_t diff = (m_are_guards_enabled ? sizeof(ALLOCATION_GUARD) : 0) + sizeof(AllocationInfo);
|
|
if (info->align) diff += (info->align - diff % info->align) % info->align;
|
|
return (u8*)user_ptr - diff;
|
|
}
|
|
|
|
|
|
void* Allocator::reallocate(void* user_ptr, size_t size)
|
|
{
|
|
#ifndef LUMIX_DEBUG
|
|
return m_source.reallocate(user_ptr, size);
|
|
#else
|
|
if (user_ptr == nullptr) return allocate(size);
|
|
if (size == 0) return nullptr;
|
|
|
|
void* new_data = allocate(size);
|
|
if (!new_data) return nullptr;
|
|
|
|
AllocationInfo* info = getAllocationInfoFromUser(user_ptr);
|
|
copyMemory(new_data, user_ptr, info->size < size ? info->size : size);
|
|
|
|
deallocate(user_ptr);
|
|
|
|
return new_data;
|
|
#endif
|
|
}
|
|
|
|
|
|
void* Allocator::allocate_aligned(size_t size, size_t align)
|
|
{
|
|
#ifndef LUMIX_DEBUG
|
|
return m_source.allocate_aligned(size, align);
|
|
#else
|
|
void* system_ptr;
|
|
AllocationInfo* info;
|
|
u8* user_ptr;
|
|
|
|
size_t system_size = getNeededMemory(size, align);
|
|
|
|
m_mutex.enter();
|
|
system_ptr = m_source.allocate_aligned(system_size, align);
|
|
user_ptr = getUserFromSystem(system_ptr, align);
|
|
info = new (NewPlaceholder(), getAllocationInfoFromUser(user_ptr)) AllocationInfo();
|
|
|
|
info->previous = m_root->previous;
|
|
m_root->previous->next = info;
|
|
|
|
info->next = m_root;
|
|
m_root->previous = info;
|
|
|
|
m_root = info;
|
|
|
|
m_total_size += size;
|
|
m_mutex.exit();
|
|
|
|
info->align = u16(align);
|
|
info->stack_leaf = m_stack_tree.record();
|
|
info->size = size;
|
|
if (m_is_fill_enabled)
|
|
{
|
|
memset(user_ptr, UNINITIALIZED_MEMORY_PATTERN, size);
|
|
}
|
|
|
|
if (m_are_guards_enabled)
|
|
{
|
|
*(u32*)system_ptr = ALLOCATION_GUARD;
|
|
*(u32*)((u8*)system_ptr + system_size - sizeof(ALLOCATION_GUARD)) = ALLOCATION_GUARD;
|
|
}
|
|
|
|
return user_ptr;
|
|
#endif
|
|
}
|
|
|
|
|
|
void Allocator::deallocate_aligned(void* user_ptr)
|
|
{
|
|
#ifndef LUMIX_DEBUG
|
|
m_source.deallocate_aligned(user_ptr);
|
|
#else
|
|
if (user_ptr)
|
|
{
|
|
AllocationInfo* info = getAllocationInfoFromUser(user_ptr);
|
|
void* system_ptr = getSystemFromUser(user_ptr);
|
|
if (m_is_fill_enabled)
|
|
{
|
|
memset(user_ptr, FREED_MEMORY_PATTERN, info->size);
|
|
}
|
|
|
|
if (m_are_guards_enabled)
|
|
{
|
|
ASSERT(*(u32*)system_ptr == ALLOCATION_GUARD);
|
|
size_t system_size = getNeededMemory(info->size, info->align);
|
|
ASSERT(*(u32*)((u8*)system_ptr + system_size - sizeof(ALLOCATION_GUARD)) == ALLOCATION_GUARD);
|
|
}
|
|
|
|
{
|
|
MT::CriticalSectionLock lock(m_mutex);
|
|
if (info == m_root)
|
|
{
|
|
m_root = info->next;
|
|
}
|
|
info->previous->next = info->next;
|
|
info->next->previous = info->previous;
|
|
|
|
m_total_size -= info->size;
|
|
} // because of the lock
|
|
|
|
info->~AllocationInfo();
|
|
|
|
m_source.deallocate_aligned((void*)system_ptr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void* Allocator::reallocate_aligned(void* user_ptr, size_t size, size_t align)
|
|
{
|
|
#ifndef LUMIX_DEBUG
|
|
return m_source.reallocate_aligned(user_ptr, size, align);
|
|
#else
|
|
if (user_ptr == nullptr) return allocate_aligned(size, align);
|
|
if (size == 0) return nullptr;
|
|
|
|
void* new_data = allocate_aligned(size, align);
|
|
if (!new_data) return nullptr;
|
|
|
|
AllocationInfo* info = getAllocationInfoFromUser(user_ptr);
|
|
copyMemory(new_data, user_ptr, info->size < size ? info->size : size);
|
|
|
|
deallocate_aligned(user_ptr);
|
|
|
|
return new_data;
|
|
#endif
|
|
}
|
|
|
|
|
|
void* Allocator::allocate(size_t size)
|
|
{
|
|
#ifndef LUMIX_DEBUG
|
|
return m_source.allocate(size);
|
|
#else
|
|
void* system_ptr;
|
|
AllocationInfo* info;
|
|
size_t system_size = getNeededMemory(size);
|
|
{
|
|
MT::CriticalSectionLock lock(m_mutex);
|
|
system_ptr = m_source.allocate(system_size);
|
|
info = new (NewPlaceholder(), getAllocationInfoFromSystem(system_ptr)) AllocationInfo();
|
|
|
|
info->previous = m_root->previous;
|
|
m_root->previous->next = info;
|
|
|
|
info->next = m_root;
|
|
m_root->previous = info;
|
|
|
|
m_root = info;
|
|
|
|
m_total_size += size;
|
|
} // because of the lock
|
|
|
|
void* user_ptr = getUserFromSystem(system_ptr, 0);
|
|
info->stack_leaf = m_stack_tree.record();
|
|
info->size = size;
|
|
info->align = 0;
|
|
if (m_is_fill_enabled)
|
|
{
|
|
memset(user_ptr, UNINITIALIZED_MEMORY_PATTERN, size);
|
|
}
|
|
|
|
if (m_are_guards_enabled)
|
|
{
|
|
*(u32*)system_ptr = ALLOCATION_GUARD;
|
|
*(u32*)((u8*)system_ptr + system_size - sizeof(ALLOCATION_GUARD)) = ALLOCATION_GUARD;
|
|
}
|
|
|
|
return user_ptr;
|
|
#endif
|
|
}
|
|
|
|
void Allocator::deallocate(void* user_ptr)
|
|
{
|
|
#ifndef LUMIX_DEBUG
|
|
m_source.deallocate(user_ptr);
|
|
#else
|
|
if (user_ptr)
|
|
{
|
|
AllocationInfo* info = getAllocationInfoFromUser(user_ptr);
|
|
void* system_ptr = getSystemFromUser(user_ptr);
|
|
if (m_is_fill_enabled)
|
|
{
|
|
memset(user_ptr, FREED_MEMORY_PATTERN, info->size);
|
|
}
|
|
|
|
if (m_are_guards_enabled)
|
|
{
|
|
ASSERT(*(u32*)system_ptr == ALLOCATION_GUARD);
|
|
size_t system_size = getNeededMemory(info->size);
|
|
ASSERT(*(u32*)((u8*)system_ptr + system_size - sizeof(ALLOCATION_GUARD)) == ALLOCATION_GUARD);
|
|
}
|
|
|
|
{
|
|
MT::CriticalSectionLock lock(m_mutex);
|
|
if (info == m_root)
|
|
{
|
|
m_root = info->next;
|
|
}
|
|
info->previous->next = info->next;
|
|
info->next->previous = info->previous;
|
|
|
|
m_total_size -= info->size;
|
|
} // because of the lock
|
|
|
|
info->~AllocationInfo();
|
|
|
|
m_source.deallocate((void*)system_ptr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
} // namespace Debug
|
|
|
|
|
|
BOOL SendFile(LPCSTR lpszSubject,
|
|
LPCSTR lpszTo,
|
|
LPCSTR lpszName,
|
|
LPCSTR lpszText,
|
|
LPCSTR lpszFullFileName)
|
|
{
|
|
HINSTANCE hMAPI = ::LoadLibrary("mapi32.dll");
|
|
if (!hMAPI) return FALSE;
|
|
LPMAPISENDMAIL lpfnMAPISendMail = (LPMAPISENDMAIL)::GetProcAddress(hMAPI, "MAPISendMail");
|
|
|
|
char szDrive[_MAX_DRIVE] = {0};
|
|
char szDir[_MAX_DIR] = {0};
|
|
char szName[_MAX_FNAME] = {0};
|
|
char szExt[_MAX_EXT] = {0};
|
|
_splitpath_s(lpszFullFileName, szDrive, szDir, szName, szExt);
|
|
|
|
char szFileName[MAX_PATH] = {0};
|
|
strcat_s(szFileName, szName);
|
|
strcat_s(szFileName, szExt);
|
|
|
|
char szFullFileName[MAX_PATH] = {0};
|
|
strcat_s(szFullFileName, lpszFullFileName);
|
|
|
|
MapiFileDesc MAPIfile = {0};
|
|
ZeroMemory(&MAPIfile, sizeof(MapiFileDesc));
|
|
MAPIfile.nPosition = 0xFFFFFFFF;
|
|
MAPIfile.lpszPathName = szFullFileName;
|
|
MAPIfile.lpszFileName = szFileName;
|
|
|
|
char szTo[MAX_PATH] = {0};
|
|
strcat_s(szTo, lpszTo);
|
|
|
|
char szNameTo[MAX_PATH] = {0};
|
|
strcat_s(szNameTo, lpszName);
|
|
|
|
MapiRecipDesc recipient = {0};
|
|
recipient.ulRecipClass = MAPI_TO;
|
|
recipient.lpszAddress = szTo;
|
|
recipient.lpszName = szNameTo;
|
|
|
|
char szSubject[MAX_PATH] = {0};
|
|
strcat_s(szSubject, lpszSubject);
|
|
|
|
char szText[MAX_PATH] = {0};
|
|
strcat_s(szText, lpszText);
|
|
|
|
MapiMessage MAPImsg = {0};
|
|
MAPImsg.lpszSubject = szSubject;
|
|
MAPImsg.lpRecips = &recipient;
|
|
MAPImsg.nRecipCount = 1;
|
|
MAPImsg.lpszNoteText = szText;
|
|
MAPImsg.nFileCount = 1;
|
|
MAPImsg.lpFiles = &MAPIfile;
|
|
|
|
ULONG nSent = lpfnMAPISendMail(0, 0, &MAPImsg, 0, 0);
|
|
|
|
FreeLibrary(hMAPI);
|
|
return (nSent == SUCCESS_SUCCESS || nSent == MAPI_E_USER_ABORT);
|
|
}
|
|
|
|
|
|
static void getStack(CONTEXT& context, char* out, int max_size)
|
|
{
|
|
BOOL result;
|
|
STACKFRAME64 stack;
|
|
char symbol_mem[sizeof(IMAGEHLP_SYMBOL64) + 256];
|
|
IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)symbol_mem;
|
|
char name[256];
|
|
copyString(out, max_size, "Crash callstack:\n");
|
|
memset(&stack, 0, sizeof(STACKFRAME64));
|
|
|
|
HANDLE process = GetCurrentProcess();
|
|
HANDLE thread = GetCurrentThread();
|
|
DWORD64 displacement = 0;
|
|
DWORD machineType;
|
|
|
|
#ifdef _M_X64
|
|
machineType = IMAGE_FILE_MACHINE_AMD64;
|
|
stack.AddrPC.Offset = context.Rip;
|
|
stack.AddrPC.Mode = AddrModeFlat;
|
|
stack.AddrStack.Offset = context.Rsp;
|
|
stack.AddrStack.Mode = AddrModeFlat;
|
|
stack.AddrFrame.Offset = context.Rbp;
|
|
stack.AddrFrame.Mode = AddrModeFlat;
|
|
#elif defined _M_IA64
|
|
#error not supported
|
|
machineType = IMAGE_FILE_MACHINE_IA64;
|
|
stack.AddrPC.Offset = context.StIIP;
|
|
stack.AddrPC.Mode = AddrModeFlat;
|
|
stack.AddrFrame.Offset = context.IntSp;
|
|
stack.AddrFrame.Mode = AddrModeFlat;
|
|
stack.AddrBStore.Offset = context.RsBSP;
|
|
stack.AddrBStore.Mode = AddrModeFlat;
|
|
stack.AddrStack.Offset = context.IntSp;
|
|
stack.AddrStack.Mode = AddrModeFlat;
|
|
#else
|
|
#error not supported
|
|
machineType = IMAGE_FILE_MACHINE_I386;
|
|
stack.AddrPC.Offset = context.Eip;
|
|
stack.AddrPC.Mode = AddrModeFlat;
|
|
stack.AddrStack.Offset = context.Esp;
|
|
stack.AddrStack.Mode = AddrModeFlat;
|
|
stack.AddrFrame.Offset = context.Ebp;
|
|
stack.AddrFrame.Mode = AddrModeFlat;
|
|
#endif
|
|
|
|
do
|
|
{
|
|
result = StackWalk64(machineType,
|
|
process,
|
|
thread,
|
|
&stack,
|
|
&context,
|
|
NULL,
|
|
SymFunctionTableAccess64,
|
|
SymGetModuleBase64,
|
|
NULL);
|
|
|
|
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
|
symbol->MaxNameLength = 255;
|
|
|
|
SymGetSymFromAddr64(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbol);
|
|
UnDecorateSymbolName(symbol->Name, (PSTR)name, 256, UNDNAME_COMPLETE);
|
|
|
|
if (!catString(out, max_size, symbol->Name)) return;
|
|
if (!catString(out, max_size, "\n")) return;
|
|
|
|
} while (result);
|
|
}
|
|
|
|
|
|
static LONG WINAPI unhandledExceptionHandler(LPEXCEPTION_POINTERS info)
|
|
{
|
|
if (!g_is_crash_reporting_enabled) return EXCEPTION_CONTINUE_SEARCH;
|
|
|
|
struct CrashInfo
|
|
{
|
|
LPEXCEPTION_POINTERS info;
|
|
DWORD thread_id;
|
|
};
|
|
|
|
auto dumper = [](void* data) -> DWORD {
|
|
LPEXCEPTION_POINTERS info = ((CrashInfo*)data)->info;
|
|
uintptr base = (uintptr)GetModuleHandle(NULL);
|
|
StaticString<4096> message;
|
|
if(info)
|
|
{
|
|
getStack(*info->ContextRecord, message.data, sizeof(message.data));
|
|
message << "\nCode: " << (u32)info->ExceptionRecord->ExceptionCode;
|
|
message << "\nAddress: " << (uintptr)info->ExceptionRecord->ExceptionAddress;
|
|
message << "\nBase: " << (uintptr)base;
|
|
OS::messageBox(message);
|
|
}
|
|
else
|
|
{
|
|
message.data[0] = '\0';
|
|
}
|
|
|
|
char minidump_path[MAX_PATH_LENGTH];
|
|
GetCurrentDirectory(sizeof(minidump_path), minidump_path);
|
|
catString(minidump_path, "\\minidump.dmp");
|
|
|
|
HANDLE process = GetCurrentProcess();
|
|
DWORD process_id = GetProcessId(process);
|
|
HANDLE file = CreateFile(
|
|
minidump_path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
MINIDUMP_TYPE minidump_type = (MINIDUMP_TYPE)(
|
|
MiniDumpWithFullMemoryInfo | MiniDumpFilterMemory | MiniDumpWithHandleData |
|
|
MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules);
|
|
|
|
MINIDUMP_EXCEPTION_INFORMATION minidump_exception_info;
|
|
minidump_exception_info.ThreadId = ((CrashInfo*)data)->thread_id;
|
|
minidump_exception_info.ExceptionPointers = info;
|
|
minidump_exception_info.ClientPointers = FALSE;
|
|
|
|
MiniDumpWriteDump(process,
|
|
process_id,
|
|
file,
|
|
minidump_type,
|
|
info ? &minidump_exception_info : nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
CloseHandle(file);
|
|
|
|
minidump_type = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo |
|
|
MiniDumpFilterMemory | MiniDumpWithHandleData |
|
|
MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules);
|
|
file = CreateFile(
|
|
"fulldump.dmp", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
MiniDumpWriteDump(process,
|
|
process_id,
|
|
file,
|
|
minidump_type,
|
|
info ? &minidump_exception_info : nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
|
|
CloseHandle(file);
|
|
|
|
SendFile("Lumix Studio crash",
|
|
"SMTP:mikulas.florek@gamedev.sk",
|
|
"Lumix Studio",
|
|
message,
|
|
minidump_path);
|
|
return 0;
|
|
};
|
|
|
|
DWORD thread_id;
|
|
CrashInfo crash_info = { info, GetCurrentThreadId() };
|
|
auto handle = CreateThread(0, 0x8000, dumper, &crash_info, 0, &thread_id);
|
|
WaitForSingleObject(handle, INFINITE);
|
|
|
|
StaticString<4096> message;
|
|
getStack(*info->ContextRecord, message.data, sizeof(message.data));
|
|
logError("Engine") << message;
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
|
|
void enableCrashReporting(bool enable)
|
|
{
|
|
g_is_crash_reporting_enabled = enable;
|
|
}
|
|
|
|
|
|
void installUnhandledExceptionHandler()
|
|
{
|
|
SetUnhandledExceptionFilter(unhandledExceptionHandler);
|
|
}
|
|
|
|
|
|
} // namespace Lumix
|