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