0032437: Coding Rules - eliminate MinGW warning -Wmaybe-uninitialized
[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 #include <Standard_WarningDisableFunctionCast.hxx>
21
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 }