7fb9d6d5 |
1 | // Created on: 2020-11-30 |
2 | // Copyright (c) 2020 OPEN CASCADE SAS |
3 | // |
4 | // This file is part of Open CASCADE Technology software library. |
5 | // |
6 | // This library is free software; you can redistribute it and/or modify it under |
7 | // the terms of the GNU Lesser General Public License version 2.1 as published |
8 | // by the Free Software Foundation, with special exception defined in the file |
9 | // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT |
10 | // distribution for complete text of the license and disclaimer of any warranty. |
11 | // |
12 | // Alternatively, this file may be used under the terms of Open CASCADE |
13 | // commercial license or contractual agreement. |
14 | |
15 | #include <Standard.hxx> |
16 | |
17 | #include <Message.hxx> |
18 | #include <Standard_Mutex.hxx> |
19 | |
902b31b6 |
20 | #include <Standard_WarningDisableFunctionCast.hxx> |
21 | |
7fb9d6d5 |
22 | #if defined(__APPLE__) |
23 | #import <TargetConditionals.h> |
24 | #endif |
25 | |
26 | #if defined(__EMSCRIPTEN__) |
27 | #include <emscripten/emscripten.h> |
28 | #elif defined(__ANDROID__) |
29 | //#include <unwind.h> |
30 | #elif defined(__QNX__) |
31 | //#include <backtrace.h> // requires linking to libbacktrace |
32 | #elif !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) |
33 | #include <execinfo.h> |
34 | #elif defined(_WIN32) && !defined(OCCT_UWP) |
35 | |
36 | #include <Standard_WarningsDisable.hxx> |
37 | #include <dbghelp.h> |
38 | #include <Standard_WarningsRestore.hxx> |
39 | |
40 | //! This is a wrapper of DbgHelp library loaded dynamically. |
41 | //! DbgHelp is coming with Windows SDK, so that technically it is always available. |
42 | //! However, it's usage requires extra steps: |
43 | //! - .pdb files are necessary to resolve function names; |
44 | //! Normal release DLLs without PDBs will show no much useful info. |
45 | //! - DbgHelp.dll and friends (SymSrv.dll, SrcSrv.dll) should be packaged with application; |
46 | //! DbgHelp.dll coming with system might be of other incompatible version |
47 | //! (some applications load it dynamically to avoid packaging extra DLL, |
48 | //! with a extra hacks determining library version) |
49 | class Standard_DbgHelper |
50 | { |
51 | public: // dbgHelp.dll function types |
52 | |
53 | typedef BOOL (WINAPI *SYMINITIALIZEPROC) (HANDLE, PCSTR, BOOL); |
54 | typedef BOOL (WINAPI *STACKWALK64PROC) (DWORD, HANDLE, HANDLE, LPSTACKFRAME64, |
55 | PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, |
56 | PFUNCTION_TABLE_ACCESS_ROUTINE64, |
57 | PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64); |
58 | typedef BOOL (WINAPI *SYMCLEANUPPROC) (HANDLE); |
59 | typedef BOOL (WINAPI *SYMFROMADDRPROC) (HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO); |
60 | |
61 | public: |
62 | |
63 | //! Return global instance. |
64 | static Standard_DbgHelper& GetDbgHelper() |
65 | { |
66 | static Standard_DbgHelper aDbgHelper; |
67 | return aDbgHelper; |
68 | } |
69 | |
70 | //! Return global mutex. |
71 | static Standard_Mutex& Mutex() |
72 | { |
73 | static Standard_Mutex THE_MUTEX_LOCK; |
74 | return THE_MUTEX_LOCK; |
75 | } |
76 | |
77 | public: |
78 | |
79 | SYMINITIALIZEPROC SymInitialize; |
80 | SYMCLEANUPPROC SymCleanup; |
81 | STACKWALK64PROC StackWalk64; |
82 | PFUNCTION_TABLE_ACCESS_ROUTINE64 SymFunctionTableAccess64; |
83 | PGET_MODULE_BASE_ROUTINE64 SymGetModuleBase64; |
84 | SYMFROMADDRPROC SymFromAddr; |
85 | |
86 | //! Return TRUE if library has been loaded. |
87 | bool IsLoaded() const { return myDbgHelpLib != NULL; } |
88 | |
89 | //! Return loading error message. |
90 | const char* ErrorMessage() const { return myError; } |
91 | |
92 | private: |
93 | |
94 | //! Main constructor. |
95 | Standard_DbgHelper() |
96 | : SymInitialize (NULL), |
97 | SymCleanup (NULL), |
98 | StackWalk64 (NULL), |
99 | SymFunctionTableAccess64 (NULL), |
100 | SymGetModuleBase64 (NULL), |
101 | SymFromAddr (NULL), |
102 | myDbgHelpLib (LoadLibraryW (L"DbgHelp.dll")), |
103 | myError (NULL) |
104 | { |
105 | if (myDbgHelpLib == NULL) |
106 | { |
107 | myError = "Standard_DbgHelper, Failed to load DbgHelp.dll"; |
108 | return; |
109 | } |
110 | |
111 | if ((SymInitialize = (SYMINITIALIZEPROC) GetProcAddress (myDbgHelpLib, "SymInitialize")) == NULL) |
112 | { |
113 | myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymInitialize"; |
114 | unload(); |
115 | return; |
116 | } |
117 | if ((SymCleanup = (SYMCLEANUPPROC) GetProcAddress (myDbgHelpLib, "SymCleanup")) == NULL) |
118 | { |
119 | myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymCleanup"; |
120 | unload(); |
121 | return; |
122 | } |
123 | if ((StackWalk64 = (STACKWALK64PROC) GetProcAddress (myDbgHelpLib, "StackWalk64")) == NULL) |
124 | { |
125 | myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: StackWalk64"; |
126 | unload(); |
127 | return; |
128 | } |
129 | if ((SymFunctionTableAccess64 = (PFUNCTION_TABLE_ACCESS_ROUTINE64) GetProcAddress (myDbgHelpLib, "SymFunctionTableAccess64")) == NULL) |
130 | { |
131 | myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymFunctionTableAccess64"; |
132 | unload(); |
133 | return; |
134 | } |
135 | if ((SymGetModuleBase64 = (PGET_MODULE_BASE_ROUTINE64) GetProcAddress (myDbgHelpLib, "SymGetModuleBase64")) == NULL) |
136 | { |
137 | myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymGetModuleBase64"; |
138 | unload(); |
139 | return; |
140 | } |
141 | if ((SymFromAddr = (SYMFROMADDRPROC) GetProcAddress (myDbgHelpLib, "SymFromAddr")) == NULL) |
142 | { |
143 | myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymFromAddr"; |
144 | unload(); |
145 | return; |
146 | } |
147 | } |
148 | |
149 | //! Destructor. |
150 | ~Standard_DbgHelper() |
151 | { |
152 | // we could unload library here, but don't do it as it is kept loaded |
153 | //unload(); |
154 | } |
155 | |
156 | //! Unload library. |
157 | void unload() |
158 | { |
159 | if (myDbgHelpLib != NULL) |
160 | { |
161 | FreeLibrary (myDbgHelpLib); |
162 | myDbgHelpLib = NULL; |
163 | } |
164 | } |
165 | |
166 | private: |
167 | |
168 | Standard_DbgHelper (const Standard_DbgHelper& ); |
169 | Standard_DbgHelper& operator= (const Standard_DbgHelper& ); |
170 | |
171 | private: |
172 | |
173 | HMODULE myDbgHelpLib; //!< handle to DbgHelp |
174 | const char* myError; //!< loading error message |
175 | |
176 | }; |
177 | |
178 | #endif |
179 | |
180 | //======================================================================= |
181 | //function : StackTrace |
182 | //purpose : |
183 | //======================================================================= |
184 | Standard_Boolean Standard::StackTrace (char* theBuffer, |
185 | const int theBufferSize, |
186 | const int theNbTraces = 10, |
187 | void* theContext, |
188 | const int theNbTopSkip) |
189 | { |
190 | (void )theContext; |
191 | if (theBufferSize < 1 |
192 | || theNbTraces < 1 |
193 | || theBuffer == NULL |
194 | || theNbTopSkip < 0) |
195 | { |
196 | return false; |
197 | } |
198 | |
199 | #if defined(__EMSCRIPTEN__) |
200 | // theNbTraces is ignored |
201 | // EM_LOG_JS_STACK? |
202 | return emscripten_get_callstack (EM_LOG_C_STACK | EM_LOG_DEMANGLE | EM_LOG_NO_PATHS | EM_LOG_FUNC_PARAMS, theBuffer, theBufferSize) > 0; |
203 | #elif defined(__ANDROID__) |
204 | Message::SendTrace ("Standard::StackTrace() is not implemented for this platform"); |
205 | return false; |
206 | #elif defined(__QNX__) |
207 | // bt_get_backtrace() |
208 | Message::SendTrace ("Standard::StackTrace() is not implemented for this platform"); |
209 | return false; |
210 | #elif defined(OCCT_UWP) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) |
211 | Message::SendTrace ("Standard::StackTrace() is not implemented for this platform"); |
212 | return false; |
213 | #elif defined(_WIN32) |
214 | // Each CPU architecture requires manual stack frame setup, |
215 | // and 32-bit version requires also additional hacks to retrieve current context; |
216 | // this implementation currently covers only x86_64 architecture. |
217 | #if defined(_M_X64) |
218 | int aNbTraces = theNbTraces; |
219 | const HANDLE anHProcess = GetCurrentProcess(); |
220 | const HANDLE anHThread = GetCurrentThread(); |
221 | CONTEXT aCtx; |
222 | if (theContext != NULL) |
223 | { |
224 | memcpy (&aCtx, theContext, sizeof(aCtx)); |
225 | } |
226 | else |
227 | { |
228 | ++aNbTraces; |
229 | memset (&aCtx, 0, sizeof(aCtx)); |
230 | aCtx.ContextFlags = CONTEXT_FULL; |
231 | RtlCaptureContext (&aCtx); |
232 | } |
233 | |
234 | // DbgHelp is not thread-safe library, hence global lock is used for serial access |
235 | Standard_Mutex::Sentry aSentry (Standard_DbgHelper::Mutex()); |
236 | Standard_DbgHelper& aDbgHelp = Standard_DbgHelper::GetDbgHelper(); |
237 | if (!aDbgHelp.IsLoaded()) |
238 | { |
239 | strcat_s (theBuffer, theBufferSize, "\n==Backtrace==\n"); |
240 | strcat_s (theBuffer, theBufferSize, aDbgHelp.ErrorMessage()); |
241 | strcat_s (theBuffer, theBufferSize, "\n============="); |
242 | return false; |
243 | } |
244 | |
245 | aDbgHelp.SymInitialize (anHProcess, NULL, TRUE); |
246 | |
247 | DWORD anImage = 0; |
248 | STACKFRAME64 aStackFrame; |
249 | memset (&aStackFrame, 0, sizeof(aStackFrame)); |
250 | |
251 | anImage = IMAGE_FILE_MACHINE_AMD64; |
252 | aStackFrame.AddrPC.Offset = aCtx.Rip; |
253 | aStackFrame.AddrPC.Mode = AddrModeFlat; |
254 | aStackFrame.AddrFrame.Offset = aCtx.Rsp; |
255 | aStackFrame.AddrFrame.Mode = AddrModeFlat; |
256 | aStackFrame.AddrStack.Offset = aCtx.Rsp; |
257 | aStackFrame.AddrStack.Mode = AddrModeFlat; |
258 | |
259 | char aModBuffer[MAX_PATH] = {}; |
260 | char aSymBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(CHAR)]; |
261 | SYMBOL_INFO* aSymbol = (SYMBOL_INFO*) aSymBuffer; |
262 | aSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
263 | aSymbol->MaxNameLen = MAX_SYM_NAME; |
264 | |
265 | int aTopSkip = theNbTopSkip + 1; // skip this function call and specified extra number |
266 | strcat_s (theBuffer, theBufferSize, "\n==Backtrace=="); |
267 | for (int aLineIter = 0; aLineIter < aNbTraces; ++aLineIter) |
268 | { |
269 | BOOL aRes = aDbgHelp.StackWalk64 (anImage, anHProcess, anHThread, |
270 | &aStackFrame, &aCtx, NULL, |
271 | aDbgHelp.SymFunctionTableAccess64, aDbgHelp.SymGetModuleBase64, NULL); |
272 | if (!aRes) |
273 | { |
274 | break; |
275 | } |
276 | |
277 | if (theContext == NULL && aTopSkip > 0) |
278 | { |
279 | --aTopSkip; |
280 | continue; |
281 | } |
282 | if (aStackFrame.AddrPC.Offset == 0) |
283 | { |
284 | break; |
285 | } |
286 | |
287 | strcat_s (theBuffer, theBufferSize, "\n"); |
288 | |
289 | const DWORD64 aModuleBase = aDbgHelp.SymGetModuleBase64 (anHProcess, aStackFrame.AddrPC.Offset); |
290 | if (aModuleBase != 0 |
291 | && GetModuleFileNameA ((HINSTANCE) aModuleBase, aModBuffer, MAX_PATH)) |
292 | { |
293 | strcat_s (theBuffer, theBufferSize, aModBuffer); |
294 | } |
295 | |
296 | DWORD64 aDisp = 0; |
297 | strcat_s (theBuffer, theBufferSize, "("); |
298 | if (aDbgHelp.SymFromAddr (anHProcess, aStackFrame.AddrPC.Offset, &aDisp, aSymbol)) |
299 | { |
300 | strcat_s (theBuffer, theBufferSize, aSymbol->Name); |
301 | } |
302 | else |
303 | { |
304 | strcat_s (theBuffer, theBufferSize, "???"); |
305 | } |
306 | strcat_s (theBuffer, theBufferSize, ")"); |
307 | } |
308 | strcat_s (theBuffer, theBufferSize, "\n============="); |
309 | |
310 | aDbgHelp.SymCleanup (anHProcess); |
311 | return true; |
312 | #else |
313 | Message::SendTrace ("Standard::StackTrace() is not implemented for this CPU architecture"); |
314 | return false; |
315 | #endif |
316 | #else |
317 | const int aTopSkip = theNbTopSkip + 1; // skip this function call and specified extra number |
318 | int aNbTraces = theNbTraces + aTopSkip; |
319 | void** aStackArr = (void** )alloca (sizeof(void*) * aNbTraces); |
320 | if (aStackArr == NULL) |
321 | { |
322 | return false; |
323 | } |
324 | |
325 | aNbTraces = ::backtrace (aStackArr, aNbTraces); |
326 | if (aNbTraces <= 1) |
327 | { |
328 | return false; |
329 | } |
330 | |
331 | aNbTraces -= aTopSkip; |
332 | char** aStrings = ::backtrace_symbols (aStackArr + aTopSkip, aNbTraces); |
333 | if (aStrings == NULL) |
334 | { |
335 | return false; |
336 | } |
337 | |
338 | const size_t aLenInit = strlen (theBuffer); |
339 | size_t aLimit = (size_t) theBufferSize - aLenInit - 1; |
340 | if (aLimit > 14) |
341 | { |
342 | strcat (theBuffer, "\n==Backtrace=="); |
343 | aLimit -= 14; |
344 | } |
345 | for (int aLineIter = 0; aLineIter < aNbTraces; ++aLineIter) |
346 | { |
347 | const size_t aLen = strlen (aStrings[aLineIter]); |
348 | if (aLen + 1 >= aLimit) |
349 | { |
350 | break; |
351 | } |
352 | |
353 | strcat (theBuffer, "\n"); |
354 | strcat (theBuffer, aStrings[aLineIter]); |
355 | aLimit -= aLen + 1; |
356 | } |
357 | free (aStrings); |
358 | if (aLimit > 14) |
359 | { |
360 | strcat (theBuffer, "\n============="); |
361 | } |
362 | return true; |
363 | #endif |
364 | } |