// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdmisc.h"
#include "nel/misc/types_nl.h"
#include "nel/misc/mem_displayer.h"
#include "nel/misc/path.h"
#include "nel/misc/command.h"
#include "nel/misc/debug.h"
#ifdef NL_OS_WINDOWS
# include
# pragma comment(lib, "imagehlp.lib")
# ifdef NL_OS_WIN64
# define DWORD_TYPE DWORD64
# else
# define DWORD_TYPE DWORD
# endif // NL_OS_WIN64
#endif // NL_OS_WINDOWS
using namespace std;
#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif
namespace NLMISC {
// UNTIL we found who to walk in the call stack wihtout error, we disactive this feature
#ifdef NL_OS_WINDOWS
static const uint32 stringSize = 1024;
static string getFuncInfo (DWORD_TYPE funcAddr, DWORD_TYPE stackAddr)
{
string str ("NoSymbol");
DWORD symSize = 10000;
PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL) GlobalAlloc (GMEM_FIXED, symSize);
::ZeroMemory (sym, symSize);
sym->SizeOfStruct = symSize;
sym->MaxNameLength = symSize - sizeof(IMAGEHLP_SYMBOL);
DWORD_TYPE disp = 0;
if (SymGetSymFromAddr (GetCurrentProcess(), funcAddr, &disp, sym) == FALSE)
{
return str;
}
CHAR undecSymbol[stringSize];
if (UnDecorateSymbolName (sym->Name, undecSymbol, stringSize, UNDNAME_COMPLETE | UNDNAME_NO_THISTYPE | UNDNAME_NO_SPECIAL_SYMS | UNDNAME_NO_MEMBER_TYPE | UNDNAME_NO_MS_KEYWORDS | UNDNAME_NO_ACCESS_SPECIFIERS ) > 0)
{
str = undecSymbol;
}
else if (SymUnDName (sym, undecSymbol, stringSize) == TRUE)
{
str = undecSymbol;
}
if (disp != 0)
{
str += " + ";
str += toString ((uint32)disp);
str += " bytes";
}
// replace param with the value of the stack for this param
string parse = str;
str = "";
uint pos = 0;
sint stop = 0;
for (uint i = 0; i < parse.size (); i++)
{
if (parse[i] == '<')
stop++;
if (parse[i] == '>')
stop--;
if (stop==0 && (parse[i] == ',' || parse[i] == ')'))
{
char tmp[32];
sprintf(tmp, "=0x%p", (void *)(*((DWORD_TYPE*)(stackAddr) + 2 + pos++)));
str += tmp;
}
str += parse[i];
}
GlobalFree (sym);
return str;
}
static string getSourceInfo (DWORD_TYPE addr)
{
string str;
IMAGEHLP_LINE line;
::ZeroMemory (&line, sizeof (line));
line.SizeOfStruct = sizeof(line);
// It doesn't work in windows 98
/* DWORD disp;
if (SymGetLineFromAddr (GetCurrentProcess(), addr, &disp, &line))
{
str = line.FileName;
str += "(" + toString (line.LineNumber) + ")";
}
else
*/ {
IMAGEHLP_MODULE module;
::ZeroMemory (&module, sizeof(module));
module.SizeOfStruct = sizeof(module);
if (SymGetModuleInfo (GetCurrentProcess(), addr, &module))
{
str = module.ModuleName;
}
else
{
str = "";
}
str += toString("!0x%p", (void*)addr);
}
return str;
}
#ifdef NL_OS_WIN64
static DWORD64 __stdcall GetModuleBase(HANDLE hProcess, DWORD64 dwReturnAddress)
#else
static DWORD __stdcall GetModuleBase(HANDLE hProcess, DWORD dwReturnAddress)
#endif
{
IMAGEHLP_MODULE moduleInfo;
if (SymGetModuleInfo(hProcess, dwReturnAddress, &moduleInfo))
return moduleInfo.BaseOfImage;
else
{
MEMORY_BASIC_INFORMATION memoryBasicInfo;
if (::VirtualQueryEx(hProcess, (LPVOID) dwReturnAddress,
&memoryBasicInfo, sizeof(memoryBasicInfo)))
{
DWORD cch = 0;
char szFile[MAX_PATH] = { 0 };
cch = GetModuleFileNameA((HINSTANCE)memoryBasicInfo.AllocationBase, szFile, MAX_PATH);
if (cch && (lstrcmpA(szFile, "DBFN")== 0))
{
char mn[] = { 'M', 'N', 0x00 };
#ifdef NL_OS_WIN64
if (!SymLoadModule64(
#else
if (!SymLoadModule(
#endif
hProcess,
NULL, mn,
NULL, (uintptr_t)memoryBasicInfo.AllocationBase, 0))
{
// DWORD dwError = GetLastError();
// nlinfo("Error: %d", dwError);
}
}
else
{
#ifdef NL_OS_WIN64
if (!SymLoadModule64(
#else
if (!SymLoadModule(
#endif
hProcess,
NULL, ((cch) ? szFile : NULL),
NULL, (uintptr_t)memoryBasicInfo.AllocationBase, 0))
{
// DWORD dwError = GetLastError();
// nlinfo("Error: %d", dwError);
}
}
return (uintptr_t)memoryBasicInfo.AllocationBase;
}
// else
// nlinfo("Error is %d", GetLastError());
}
return 0;
}
static void displayCallStack (CLog *log)
{
static string symbolPath;
DWORD symOptions = SymGetOptions();
symOptions |= SYMOPT_LOAD_LINES;
symOptions &= ~SYMOPT_UNDNAME;
SymSetOptions (symOptions);
//
// Create the path where to find the symbol
//
if (symbolPath.empty())
{
wchar_t tmpPath[stringSize];
symbolPath = ".";
if (GetEnvironmentVariableW (L"_NT_SYMBOL_PATH", tmpPath, stringSize))
{
symbolPath += ";";
symbolPath += wideToUtf8(tmpPath);
}
if (GetEnvironmentVariableW (L"_NT_ALTERNATE_SYMBOL_PATH", tmpPath, stringSize))
{
symbolPath += ";";
symbolPath += wideToUtf8(tmpPath);
}
if (GetEnvironmentVariableW (L"SYSTEMROOT", tmpPath, stringSize))
{
symbolPath += ";";
symbolPath += wideToUtf8(tmpPath);
symbolPath += ";";
symbolPath += wideToUtf8(tmpPath);
symbolPath += "\\system32";
}
}
//
// Initialize
//
if (SymInitialize (GetCurrentProcess(), NULL, FALSE) == FALSE)
{
nlwarning ("DISP: SymInitialize(%p, '%s') failed", GetCurrentProcess(), symbolPath.c_str());
return;
}
// FIXME: Implement this for MinGW
#ifndef NL_COMP_MINGW
CONTEXT context;
::ZeroMemory (&context, sizeof(context));
context.ContextFlags = CONTEXT_FULL;
if (GetThreadContext (GetCurrentThread(), &context) == FALSE)
{
nlwarning ("DISP: GetThreadContext(%p) failed", GetCurrentThread());
return;
}
STACKFRAME callStack;
::ZeroMemory (&callStack, sizeof(callStack));
#ifdef NL_OS_WIN64
callStack.AddrPC.Offset = context.Rip;
callStack.AddrStack.Offset = context.Rsp;
callStack.AddrFrame.Offset = context.Rbp;
#else
callStack.AddrPC.Offset = context.Eip;
callStack.AddrStack.Offset = context.Esp;
callStack.AddrFrame.Offset = context.Ebp;
#endif
callStack.AddrPC.Mode = AddrModeFlat;
callStack.AddrStack.Mode = AddrModeFlat;
callStack.AddrFrame.Mode = AddrModeFlat;
for (uint32 i = 0; ; i++)
{
DWORD MachineType;
#ifdef NL_OS_WIN64
MachineType = IMAGE_FILE_MACHINE_AMD64;
BOOL res = StackWalk64(MachineType, GetCurrentProcess(), GetCurrentThread(), &callStack,
NULL, NULL, SymFunctionTableAccess, GetModuleBase, NULL);
#else
MachineType = IMAGE_FILE_MACHINE_I386;
BOOL res = StackWalk(MachineType, GetCurrentProcess(), GetCurrentThread(), &callStack,
NULL, NULL, SymFunctionTableAccess, GetModuleBase, NULL);
#endif
/* if (res == FALSE)
{
DWORD r = GetLastError ();
nlinfo ("%d",r);
}
*/
if (i == 0)
continue;
if (res == FALSE || callStack.AddrFrame.Offset == 0)
break;
string symInfo, srcInfo;
symInfo = getFuncInfo (callStack.AddrPC.Offset, callStack.AddrFrame.Offset);
srcInfo = getSourceInfo (callStack.AddrPC.Offset);
log->displayNL (" %s : %s", srcInfo.c_str(), symInfo.c_str());
}
#endif
}
#else // NL_OS_WINDOWS
static void displayCallStack (CLog *log)
{
log->displayNL ("no call stack info available");
}
#endif // NL_OS_WINDOWS
/*
* Constructor
*/
CMemDisplayer::CMemDisplayer (const char *displayerName) : IDisplayer (displayerName), _NeedHeader(true), _MaxStrings(50), _CanUseStrings(true)
{
setParam (50);
}
void CMemDisplayer::setParam (uint32 maxStrings)
{
_MaxStrings = maxStrings;
}
// Log format: "2000/01/15 12:05:30 : "
void CMemDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message )
{
// stringstream ss;
string str;
bool needSpace = false;
if (!_CanUseStrings) return;
if (_NeedHeader)
{
str += HeaderString();
_NeedHeader = false;
}
if (args.Date != 0)
{
str += dateToHumanString(args.Date);
needSpace = true;
}
if (!args.ProcessName.empty())
{
if (needSpace) { str += " "; needSpace = false; }
str += args.ProcessName;
needSpace = true;
}
if (args.LogType != CLog::LOG_NO)
{
if (needSpace) { str += " "; needSpace = false; }
str += logTypeToString(args.LogType);
needSpace = true;
}
// Write thread identifier
if ( args.ThreadId != 0 )
{
if (needSpace) { str += " "; needSpace = false; }
#ifdef NL_OS_WINDOWS
str += NLMISC::toString("%5x", args.ThreadId);
#else
str += NLMISC::toString("%08x", args.ThreadId);
#endif
needSpace = true;
}
if (args.FileName != NULL)
{
if (needSpace) { str += " "; needSpace = false; }
str += CFile::getFilename(args.FileName);
needSpace = true;
}
if (args.Line != -1)
{
if (needSpace) { str += " "; needSpace = false; }
str += NLMISC::toString(args.Line);
needSpace = true;
}
if (needSpace) { str += " : "; needSpace = false; }
str += message;
// clear old line
while (_Strings.size () > _MaxStrings)
{
_Strings.pop_front ();
}
_Strings.push_back (str);
}
void CMemDisplayer::write (CLog *log, bool quiet)
{
if (log == NULL)
log = InfoLog;
if ( ! quiet )
{
log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
log->forceDisplayRaw ("----------------------------------------- display MemDisplayer history -------\n");
log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
}
for (deque::iterator it = _Strings.begin(); it != _Strings.end(); it++)
{
log->forceDisplayRaw ((*it).c_str());
}
if ( ! quiet )
{
log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
log->forceDisplayRaw ("----------------------------------------- display MemDisplayer callstack -----\n");
log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
displayCallStack(log);
log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
log->forceDisplayRaw ("----------------------------------------- end of MemDisplayer display --------\n");
log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
}
}
void CMemDisplayer::write (string &str, bool crLf)
{
for (deque::iterator it = _Strings.begin(); it != _Strings.end(); it++)
{
str += (*it);
if ( crLf )
{
if ( (!str.empty()) && (str[str.size()-1] == '\n') )
{
str[str.size()-1] = '\r';
str += '\n';
}
}
}
}
void CLightMemDisplayer::doDisplay ( const CLog::TDisplayInfo& /* args */, const char *message )
{
//stringstream ss;
string str;
//bool needSpace = false;
if (!_CanUseStrings) return;
str += message;
// clear old line
while (_Strings.size () >= _MaxStrings)
{
_Strings.pop_front ();
}
_Strings.push_back (str);
}
} // NLMISC