1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
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"
30 # include <imagehlp.h>
31 # pragma comment(lib, "imagehlp.lib")
33 # define DWORD_TYPE DWORD64
35 # define DWORD_TYPE DWORD
36 # endif // NL_OS_WIN64
37 #endif // NL_OS_WINDOWS
48 // UNTIL we found who to walk in the call stack wihtout error, we disactive this feature
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
);
63 ::ZeroMemory (sym
, symSize
);
64 sym
->SizeOfStruct
= symSize
;
65 sym
->MaxNameLength
= symSize
- sizeof(IMAGEHLP_SYMBOL
);
68 if (SymGetSymFromAddr (GetCurrentProcess(), funcAddr
, &disp
, sym
) == FALSE
)
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)
78 else if (SymUnDName (sym
, undecSymbol
, stringSize
) == TRUE
)
86 str
+= toString ((uint32
)disp
);
90 // replace param with the value of the stack for this param
97 for (uint i
= 0; i
< parse
.size (); i
++)
104 if (stop
==0 && (parse
[i
] == ',' || parse
[i
] == ')'))
107 sprintf(tmp
, "=0x%p", (void *)(*((DWORD_TYPE
*)(stackAddr
) + 2 + pos
++)));
117 static string
getSourceInfo (DWORD_TYPE addr
)
122 ::ZeroMemory (&line
, sizeof (line
));
123 line
.SizeOfStruct
= sizeof(line
);
125 // It doesn't work in windows 98
127 if (SymGetLineFromAddr (GetCurrentProcess(), addr, &disp, &line))
130 str += "(" + toString (line.LineNumber) + ")";
134 IMAGEHLP_MODULE module
;
135 ::ZeroMemory (&module
, sizeof(module
));
136 module
.SizeOfStruct
= sizeof(module
);
138 if (SymGetModuleInfo (GetCurrentProcess(), addr
, &module
))
140 str
= module
.ModuleName
;
147 str
+= toString("!0x%p", (void*)addr
);
154 static DWORD64 __stdcall
GetModuleBase(HANDLE hProcess
, DWORD64 dwReturnAddress
)
156 static DWORD __stdcall
GetModuleBase(HANDLE hProcess
, DWORD dwReturnAddress
)
159 IMAGEHLP_MODULE moduleInfo
;
161 if (SymGetModuleInfo(hProcess
, dwReturnAddress
, &moduleInfo
))
162 return moduleInfo
.BaseOfImage
;
165 MEMORY_BASIC_INFORMATION memoryBasicInfo
;
167 if (::VirtualQueryEx(hProcess
, (LPVOID
) dwReturnAddress
,
168 &memoryBasicInfo
, sizeof(memoryBasicInfo
)))
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 };
179 if (!SymLoadModule64(
185 NULL
, (uintptr_t)memoryBasicInfo
.AllocationBase
, 0))
187 // DWORD dwError = GetLastError();
188 // nlinfo("Error: %d", dwError);
194 if (!SymLoadModule64(
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
;
212 // nlinfo("Error is %d", GetLastError());
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
];
238 if (GetEnvironmentVariableW (L
"_NT_SYMBOL_PATH", tmpPath
, stringSize
))
241 symbolPath
+= wideToUtf8(tmpPath
);
244 if (GetEnvironmentVariableW (L
"_NT_ALTERNATE_SYMBOL_PATH", tmpPath
, stringSize
))
247 symbolPath
+= wideToUtf8(tmpPath
);
250 if (GetEnvironmentVariableW (L
"SYSTEMROOT", tmpPath
, stringSize
))
253 symbolPath
+= wideToUtf8(tmpPath
);
255 symbolPath
+= wideToUtf8(tmpPath
);
256 symbolPath
+= "\\system32";
264 if (SymInitialize (GetCurrentProcess(), NULL
, FALSE
) == FALSE
)
266 nlwarning ("DISP: SymInitialize(%p, '%s') failed", GetCurrentProcess(), symbolPath
.c_str());
270 // FIXME: Implement this for MinGW
271 #ifndef NL_COMP_MINGW
273 ::ZeroMemory (&context
, sizeof(context
));
274 context
.ContextFlags
= CONTEXT_FULL
;
276 if (GetThreadContext (GetCurrentThread(), &context
) == FALSE
)
278 nlwarning ("DISP: GetThreadContext(%p) failed", GetCurrentThread());
282 STACKFRAME callStack
;
283 ::ZeroMemory (&callStack
, sizeof(callStack
));
286 callStack
.AddrPC
.Offset
= context
.Rip
;
287 callStack
.AddrStack
.Offset
= context
.Rsp
;
288 callStack
.AddrFrame
.Offset
= context
.Rbp
;
290 callStack
.AddrPC
.Offset
= context
.Eip
;
291 callStack
.AddrStack
.Offset
= context
.Esp
;
292 callStack
.AddrFrame
.Offset
= context
.Ebp
;
295 callStack
.AddrPC
.Mode
= AddrModeFlat
;
296 callStack
.AddrStack
.Mode
= AddrModeFlat
;
297 callStack
.AddrFrame
.Mode
= AddrModeFlat
;
299 for (uint32 i
= 0; ; i
++)
304 MachineType
= IMAGE_FILE_MACHINE_AMD64
;
305 BOOL res
= StackWalk64(MachineType
, GetCurrentProcess(), GetCurrentThread(), &callStack
,
306 NULL
, NULL
, SymFunctionTableAccess
, GetModuleBase
, NULL
);
308 MachineType
= IMAGE_FILE_MACHINE_I386
;
309 BOOL res
= StackWalk(MachineType
, GetCurrentProcess(), GetCurrentThread(), &callStack
,
310 NULL
, NULL
, SymFunctionTableAccess
, GetModuleBase
, NULL
);
315 DWORD r = GetLastError ();
322 if (res
== FALSE
|| callStack
.AddrFrame
.Offset
== 0)
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());
335 #else // NL_OS_WINDOWS
337 static void displayCallStack (CLog
*log
)
339 log
->displayNL ("no call stack info available");
342 #endif // NL_OS_WINDOWS
348 CMemDisplayer::CMemDisplayer (const char *displayerName
) : IDisplayer (displayerName
), _NeedHeader(true), _MaxStrings(50), _CanUseStrings(true)
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
)
364 bool needSpace
= false;
366 if (!_CanUseStrings
) return;
370 str
+= HeaderString();
376 str
+= dateToHumanString(args
.Date
);
380 if (!args
.ProcessName
.empty())
382 if (needSpace
) { str
+= " "; needSpace
= false; }
383 str
+= args
.ProcessName
;
387 if (args
.LogType
!= CLog::LOG_NO
)
389 if (needSpace
) { str
+= " "; needSpace
= false; }
390 str
+= logTypeToString(args
.LogType
);
394 // Write thread identifier
395 if ( args
.ThreadId
!= 0 )
397 if (needSpace
) { str
+= " "; needSpace
= false; }
399 str
+= NLMISC::toString("%5x", args
.ThreadId
);
401 str
+= NLMISC::toString("%08x", args
.ThreadId
);
406 if (args
.FileName
!= NULL
)
408 if (needSpace
) { str
+= " "; needSpace
= false; }
409 str
+= CFile::getFilename(args
.FileName
);
415 if (needSpace
) { str
+= " "; needSpace
= false; }
416 str
+= NLMISC::toString(args
.Line
);
420 if (needSpace
) { str
+= " : "; needSpace
= false; }
425 while (_Strings
.size () > _MaxStrings
)
427 _Strings
.pop_front ();
430 _Strings
.push_back (str
);
433 void CMemDisplayer::write (CLog
*log
, bool 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());
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
++)
467 if ( (!str
.empty()) && (str
[str
.size()-1] == '\n') )
469 str
[str
.size()-1] = '\r';
477 void CLightMemDisplayer::doDisplay ( const CLog::TDisplayInfo
& /* args */, const char *message
)
481 //bool needSpace = false;
483 if (!_CanUseStrings
) return;
488 while (_Strings
.size () >= _MaxStrings
)
490 _Strings
.pop_front ();
493 _Strings
.push_back (str
);