Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / misc / mem_displayer.cpp
blobcefa995896d86f4a3a97cfbfb53f319c206faf01
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdmisc.h"
22 #include "nel/misc/types_nl.h"
24 #include "nel/misc/mem_displayer.h"
25 #include "nel/misc/path.h"
26 #include "nel/misc/command.h"
27 #include "nel/misc/debug.h"
29 #ifdef NL_OS_WINDOWS
30 # include <imagehlp.h>
31 # pragma comment(lib, "imagehlp.lib")
32 # ifdef NL_OS_WIN64
33 # define DWORD_TYPE DWORD64
34 # else
35 # define DWORD_TYPE DWORD
36 # endif // NL_OS_WIN64
37 #endif // NL_OS_WINDOWS
39 using namespace std;
41 #ifdef DEBUG_NEW
42 #define new DEBUG_NEW
43 #endif
45 namespace NLMISC {
48 // UNTIL we found who to walk in the call stack wihtout error, we disactive this feature
51 #ifdef NL_OS_WINDOWS
53 static const uint32 stringSize = 1024;
55 static string getFuncInfo (DWORD_TYPE funcAddr, DWORD_TYPE stackAddr)
57 string str ("NoSymbol");
59 DWORD symSize = 10000;
60 PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL) GlobalAlloc (GMEM_FIXED, symSize);
61 if (!sym) return str;
63 ::ZeroMemory (sym, symSize);
64 sym->SizeOfStruct = symSize;
65 sym->MaxNameLength = symSize - sizeof(IMAGEHLP_SYMBOL);
67 DWORD_TYPE disp = 0;
68 if (SymGetSymFromAddr (GetCurrentProcess(), funcAddr, &disp, sym) == FALSE)
70 return str;
73 CHAR undecSymbol[stringSize];
74 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)
76 str = undecSymbol;
78 else if (SymUnDName (sym, undecSymbol, stringSize) == TRUE)
80 str = undecSymbol;
83 if (disp != 0)
85 str += " + ";
86 str += toString ((uint32)disp);
87 str += " bytes";
90 // replace param with the value of the stack for this param
92 string parse = str;
93 str.clear();
94 uint pos = 0;
95 sint stop = 0;
97 for (uint i = 0; i < parse.size (); i++)
99 if (parse[i] == '<')
100 stop++;
101 if (parse[i] == '>')
102 stop--;
104 if (stop==0 && (parse[i] == ',' || parse[i] == ')'))
106 char tmp[32];
107 sprintf(tmp, "=0x%p", (void *)(*((DWORD_TYPE*)(stackAddr) + 2 + pos++)));
108 str += tmp;
110 str += parse[i];
112 GlobalFree (sym);
114 return str;
117 static string getSourceInfo (DWORD_TYPE addr)
119 string str;
121 IMAGEHLP_LINE line;
122 ::ZeroMemory (&line, sizeof (line));
123 line.SizeOfStruct = sizeof(line);
125 // It doesn't work in windows 98
126 /* DWORD disp;
127 if (SymGetLineFromAddr (GetCurrentProcess(), addr, &disp, &line))
129 str = line.FileName;
130 str += "(" + toString (line.LineNumber) + ")";
132 else
133 */ {
134 IMAGEHLP_MODULE module;
135 ::ZeroMemory (&module, sizeof(module));
136 module.SizeOfStruct = sizeof(module);
138 if (SymGetModuleInfo (GetCurrentProcess(), addr, &module))
140 str = module.ModuleName;
142 else
144 str = "<NoModule>";
147 str += toString("!0x%p", (void*)addr);
150 return str;
153 #ifdef NL_OS_WIN64
154 static DWORD64 __stdcall GetModuleBase(HANDLE hProcess, DWORD64 dwReturnAddress)
155 #else
156 static DWORD __stdcall GetModuleBase(HANDLE hProcess, DWORD dwReturnAddress)
157 #endif
159 IMAGEHLP_MODULE moduleInfo;
161 if (SymGetModuleInfo(hProcess, dwReturnAddress, &moduleInfo))
162 return moduleInfo.BaseOfImage;
163 else
165 MEMORY_BASIC_INFORMATION memoryBasicInfo;
167 if (::VirtualQueryEx(hProcess, (LPVOID) dwReturnAddress,
168 &memoryBasicInfo, sizeof(memoryBasicInfo)))
170 DWORD cch = 0;
171 char szFile[MAX_PATH] = { 0 };
173 cch = GetModuleFileNameA((HINSTANCE)memoryBasicInfo.AllocationBase, szFile, MAX_PATH);
175 if (cch && (lstrcmpA(szFile, "DBFN")== 0))
177 char mn[] = { 'M', 'N', 0x00 };
178 #ifdef NL_OS_WIN64
179 if (!SymLoadModule64(
180 #else
181 if (!SymLoadModule(
182 #endif
183 hProcess,
184 NULL, mn,
185 NULL, (uintptr_t)memoryBasicInfo.AllocationBase, 0))
187 // DWORD dwError = GetLastError();
188 // nlinfo("Error: %d", dwError);
191 else
193 #ifdef NL_OS_WIN64
194 if (!SymLoadModule64(
195 #else
196 if (!SymLoadModule(
197 #endif
198 hProcess,
199 NULL, ((cch) ? szFile : NULL),
200 NULL, (uintptr_t)memoryBasicInfo.AllocationBase, 0))
202 // DWORD dwError = GetLastError();
203 // nlinfo("Error: %d", dwError);
209 return (uintptr_t)memoryBasicInfo.AllocationBase;
211 // else
212 // nlinfo("Error is %d", GetLastError());
215 return 0;
219 static void displayCallStack (CLog *log)
221 static string symbolPath;
223 DWORD symOptions = SymGetOptions();
224 symOptions |= SYMOPT_LOAD_LINES;
225 symOptions &= ~SYMOPT_UNDNAME;
226 SymSetOptions (symOptions);
229 // Create the path where to find the symbol
232 if (symbolPath.empty())
234 wchar_t tmpPath[stringSize];
236 symbolPath = ".";
238 if (GetEnvironmentVariableW (L"_NT_SYMBOL_PATH", tmpPath, stringSize))
240 symbolPath += ";";
241 symbolPath += wideToUtf8(tmpPath);
244 if (GetEnvironmentVariableW (L"_NT_ALTERNATE_SYMBOL_PATH", tmpPath, stringSize))
246 symbolPath += ";";
247 symbolPath += wideToUtf8(tmpPath);
250 if (GetEnvironmentVariableW (L"SYSTEMROOT", tmpPath, stringSize))
252 symbolPath += ";";
253 symbolPath += wideToUtf8(tmpPath);
254 symbolPath += ";";
255 symbolPath += wideToUtf8(tmpPath);
256 symbolPath += "\\system32";
261 // Initialize
264 if (SymInitialize (GetCurrentProcess(), NULL, FALSE) == FALSE)
266 nlwarning ("DISP: SymInitialize(%p, '%s') failed", GetCurrentProcess(), symbolPath.c_str());
267 return;
270 // FIXME: Implement this for MinGW
271 #ifndef NL_COMP_MINGW
272 CONTEXT context;
273 ::ZeroMemory (&context, sizeof(context));
274 context.ContextFlags = CONTEXT_FULL;
276 if (GetThreadContext (GetCurrentThread(), &context) == FALSE)
278 nlwarning ("DISP: GetThreadContext(%p) failed", GetCurrentThread());
279 return;
282 STACKFRAME callStack;
283 ::ZeroMemory (&callStack, sizeof(callStack));
285 #ifdef NL_OS_WIN64
286 callStack.AddrPC.Offset = context.Rip;
287 callStack.AddrStack.Offset = context.Rsp;
288 callStack.AddrFrame.Offset = context.Rbp;
289 #else
290 callStack.AddrPC.Offset = context.Eip;
291 callStack.AddrStack.Offset = context.Esp;
292 callStack.AddrFrame.Offset = context.Ebp;
293 #endif
295 callStack.AddrPC.Mode = AddrModeFlat;
296 callStack.AddrStack.Mode = AddrModeFlat;
297 callStack.AddrFrame.Mode = AddrModeFlat;
299 for (uint32 i = 0; ; i++)
301 DWORD MachineType;
303 #ifdef NL_OS_WIN64
304 MachineType = IMAGE_FILE_MACHINE_AMD64;
305 BOOL res = StackWalk64(MachineType, GetCurrentProcess(), GetCurrentThread(), &callStack,
306 NULL, NULL, SymFunctionTableAccess, GetModuleBase, NULL);
307 #else
308 MachineType = IMAGE_FILE_MACHINE_I386;
309 BOOL res = StackWalk(MachineType, GetCurrentProcess(), GetCurrentThread(), &callStack,
310 NULL, NULL, SymFunctionTableAccess, GetModuleBase, NULL);
311 #endif
313 /* if (res == FALSE)
315 DWORD r = GetLastError ();
316 nlinfo ("%d",r);
319 if (i == 0)
320 continue;
322 if (res == FALSE || callStack.AddrFrame.Offset == 0)
323 break;
325 string symInfo, srcInfo;
327 symInfo = getFuncInfo (callStack.AddrPC.Offset, callStack.AddrFrame.Offset);
328 srcInfo = getSourceInfo (callStack.AddrPC.Offset);
330 log->displayNL (" %s : %s", srcInfo.c_str(), symInfo.c_str());
332 #endif
335 #else // NL_OS_WINDOWS
337 static void displayCallStack (CLog *log)
339 log->displayNL ("no call stack info available");
342 #endif // NL_OS_WINDOWS
346 * Constructor
348 CMemDisplayer::CMemDisplayer (const char *displayerName) : IDisplayer (displayerName), _NeedHeader(true), _MaxStrings(50), _CanUseStrings(true)
350 setParam (50);
353 void CMemDisplayer::setParam (uint32 maxStrings)
355 _MaxStrings = maxStrings;
359 // Log format: "2000/01/15 12:05:30 <ProcessName> <LogType> <ThreadId> <FileName> <Line> : <Msg>"
360 void CMemDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message )
362 // stringstream ss;
363 string str;
364 bool needSpace = false;
366 if (!_CanUseStrings) return;
368 if (_NeedHeader)
370 str += HeaderString();
371 _NeedHeader = false;
374 if (args.Date != 0)
376 str += dateToHumanString(args.Date);
377 needSpace = true;
380 if (!args.ProcessName.empty())
382 if (needSpace) { str += " "; needSpace = false; }
383 str += args.ProcessName;
384 needSpace = true;
387 if (args.LogType != CLog::LOG_NO)
389 if (needSpace) { str += " "; needSpace = false; }
390 str += logTypeToString(args.LogType);
391 needSpace = true;
394 // Write thread identifier
395 if ( args.ThreadId != 0 )
397 if (needSpace) { str += " "; needSpace = false; }
398 #ifdef NL_OS_WINDOWS
399 str += NLMISC::toString("%5x", args.ThreadId);
400 #else
401 str += NLMISC::toString("%08x", args.ThreadId);
402 #endif
403 needSpace = true;
406 if (args.FileName != NULL)
408 if (needSpace) { str += " "; needSpace = false; }
409 str += CFile::getFilename(args.FileName);
410 needSpace = true;
413 if (args.Line != -1)
415 if (needSpace) { str += " "; needSpace = false; }
416 str += NLMISC::toString(args.Line);
417 needSpace = true;
420 if (needSpace) { str += " : "; needSpace = false; }
422 str += message;
424 // clear old line
425 while (_Strings.size () > _MaxStrings)
427 _Strings.pop_front ();
430 _Strings.push_back (str);
433 void CMemDisplayer::write (CLog *log, bool quiet)
435 if (log == NULL)
436 log = InfoLog;
438 if ( ! quiet )
440 log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
441 log->forceDisplayRaw ("----------------------------------------- display MemDisplayer history -------\n");
442 log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
444 for (deque<string>::iterator it = _Strings.begin(); it != _Strings.end(); it++)
446 log->forceDisplayRaw ((*it).c_str());
448 if ( ! quiet )
450 log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
451 log->forceDisplayRaw ("----------------------------------------- display MemDisplayer callstack -----\n");
452 log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
453 displayCallStack(log);
454 log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
455 log->forceDisplayRaw ("----------------------------------------- end of MemDisplayer display --------\n");
456 log->forceDisplayRaw ("------------------------------------------------------------------------------\n");
460 void CMemDisplayer::write (string &str, bool crLf)
462 for (deque<string>::iterator it = _Strings.begin(); it != _Strings.end(); it++)
464 str += (*it);
465 if ( crLf )
467 if ( (!str.empty()) && (str[str.size()-1] == '\n') )
469 str[str.size()-1] = '\r';
470 str += '\n';
477 void CLightMemDisplayer::doDisplay ( const CLog::TDisplayInfo& /* args */, const char *message )
479 //stringstream ss;
480 string str;
481 //bool needSpace = false;
483 if (!_CanUseStrings) return;
485 str += message;
487 // clear old line
488 while (_Strings.size () >= _MaxStrings)
490 _Strings.pop_front ();
493 _Strings.push_back (str);
497 } // NLMISC