RabbitCommon v2.3.3
Loading...
Searching...
No Matches
StackTrace.cpp
1// Copyright Copyright (c) Kang Lin studio, All Rights Reserved
2// Author Kang Lin <kl222@126.com>
3
4#include <QThread>
5#include <QFileInfo>
6#include <QLoggingCategory>
7#include <QUrl>
8#include <QMimeData>
9#ifdef HAVE_RABBITCOMMON_GUI
10#include <QMessageBox>
11#include <QPushButton>
12#include <QDesktopServices>
13#include <QClipboard>
14#include <QApplication>
15#endif
16
17#if defined(Q_OS_WIN)
18#include <process.h>
19#include <Windows.h>
20#include <dbghelp.h>
21#if HAVE_StackWalker
22#include "StackWalker.h"
23#endif
24#include "CoreDump/MiniDumper.h"
25#elif defined(Q_OS_ANDROID)
26#include <unwind.h>
27#include <dlfcn.h>
28#include <vector>
29#else
30#include <execinfo.h>
31#include <cxxabi.h>
32#endif
33
34#include "RabbitCommonTools.h"
35#include "Log/Log.h"
36#include "StackTrace.h"
37
38namespace RabbitCommon {
39
40const char* g_PattenFileLine = "%p [%s] in %s:%lu";
41const char* g_PattenDLL = "%p [%s] in %s";
42
43static Q_LOGGING_CATEGORY(log, "RabbitCommon.StackTrace")
44
45CCallTrace::CCallTrace(QObject *parent)
46 : QObject{parent}
47 , m_hMainThread(QThread::currentThreadId())
48{}
49
50#ifdef HAVE_RABBITCOMMON_GUI
51void CCallTrace::ShowCoreDialog(QString szTitle, QString szContent, QString szDetail, QString szCoreDumpFile)
52{
53 if(QThread::currentThreadId() == m_hMainThread) {
54 qDebug(log) << "Main thread:" << m_hMainThread << "core";
55 return slotShowCoreDialog(szTitle, szContent, szDetail, szCoreDumpFile);
56 }
57 qDebug(log) << "Thread:" << QThread::currentThreadId() << "core."
58 << "main thread:" << m_hMainThread;
59 bool check = false;
60 check = connect(this, SIGNAL(sigShowCoreDialog(QString,QString,QString,QString)),
61 this, SLOT(slotShowCoreDialog(QString,QString,QString,QString)),
62 Qt::BlockingQueuedConnection);
63 Q_ASSERT(check);
64 emit sigShowCoreDialog(szTitle, szContent, szDetail, szCoreDumpFile);
65}
66
67void CCallTrace::slotShowCoreDialog(QString szTitle, QString szContent,
68 QString szDetail, QString szCoreDumpFile)
69{
70 qDebug(log) << "CCallTrace::slotShowCoreDialog";
71 QMessageBox msg(QMessageBox::Icon::Critical, szTitle, szContent, QMessageBox::StandardButton::Close);
72 QPushButton* pOpenLogFile = msg.addButton(QObject::tr("Open log file"), QMessageBox::ActionRole);
73 QPushButton* pOpenCoreDumpFolder = msg.addButton(QObject::tr("Open core dump folder"), QMessageBox::ActionRole);
74#ifndef QT_NO_CLIPBOARD
75 QPushButton* pCopyClipboard = msg.addButton(tr("Copy to clipboard"), QMessageBox::ActionRole);
76#endif
77 if(!szDetail.isEmpty())
78 msg.setDetailedText(szDetail);
79 RC_SHOW_WINDOW(&msg);
80 if(msg.clickedButton() == pOpenLogFile)
81 {
82 OpenLogFile();
83 } else if (msg.clickedButton() == pOpenCoreDumpFolder) {
84 QFileInfo info(szCoreDumpFile);
85 QDesktopServices::openUrl(QUrl::fromLocalFile(info.absolutePath()));
86#ifndef QT_NO_CLIPBOARD
87 } else if(msg.clickedButton() == pCopyClipboard) {
88 QClipboard* cb = QGuiApplication::clipboard();
89 if(!cb) {
90 qCritical(log) << "The application has not clipboard";
91 return;
92 }
93 QMimeData* m = new QMimeData();
94 if(!m) {
95 qCritical(log) << "new QMimeData fail";
96 return;
97 }
98 QList<QUrl> lstUrl;
99 lstUrl << CLog::Instance()->GetLogFile() << szCoreDumpFile;
100 m->setUrls(lstUrl);
101 cb->setMimeData(m);
102 qDebug(log) << "Clipboard urls" << cb->mimeData()->urls();
103#endif
104 }
105 qDebug(log) << "CCallTrace::slotShowCoreDialog end";
106}
107#endif //#ifdef HAVE_RABBITCOMMON_GUI
108
109QString CCallTrace::GetStack(uint index)
110{
111 QString szMsg("Stack:\n");
112 QStringList szTrace = GetStack(index, 63);
113 for(int i = 0; i < szTrace.length(); i++) {
114 szMsg += " " + QString::number(i + 1) + " " + szTrace[i] + "\n";
115 }
116 return szMsg;
117}
118
119#if defined(Q_OS_WIN)
120
121// See: https://segmentfault.com/q/1010000042761513
122#define TRACE_MAX_STACK_FRAMES 62
123QStringList CCallTrace::GetStack(uint index, unsigned int max_frames)
124{
125 QStringList szStack;
126 const int nLen = 1024;
127 void *stack[TRACE_MAX_STACK_FRAMES];
128 HANDLE process = GetCurrentProcess();
129 SymInitialize(process, NULL, TRUE);
130 WORD numberOfFrames = CaptureStackBackTrace(index, TRACE_MAX_STACK_FRAMES, stack, NULL);
131 char buf[sizeof(SYMBOL_INFO) + (nLen - 1) * sizeof(TCHAR)];
132 SYMBOL_INFO* symbol = (SYMBOL_INFO*)buf;
133 symbol->MaxNameLen = nLen;
134 symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
135 DWORD displacement;
136 IMAGEHLP_LINE64 line;
137 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
138
139 QScopedArrayPointer<char> bufStack(new char[nLen]);
140 if(!bufStack)
141 {
142 qCritical(log) << "new buffer fail";
143 return szStack;
144 }
145 for (int i = 0; i < numberOfFrames; i++)
146 {
147 DWORD64 address = (DWORD64)(stack[i]);
148 SymFromAddr(process, address, NULL, symbol);
149 if (SymGetLineFromAddr64(process, address, &displacement, &line))
150 {
151 snprintf(bufStack.data(), nLen, g_PattenFileLine,
152 (LPVOID)symbol->Address, symbol->Name, line.FileName, line.LineNumber);
153 }
154 else
155 {
156 //failed to get line
157 const int MaxNameLen = 256;
158 QScopedArrayPointer<char> module(new char[MaxNameLen]);
159 HMODULE hModule = NULL;
160 lstrcpyA(module.data(), "");
161 GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
162 | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
163 (LPCTSTR) address, &hModule);
164
165 //at least print module name
166 if(hModule != NULL)
167 GetModuleFileNameA(hModule, module.data(), MaxNameLen);
168
169 snprintf(bufStack.data(), nLen, g_PattenDLL,
170 (LPVOID)symbol->Address, symbol->Name, module.data());
171 }
172 szStack << bufStack.data();
173 }
174 return szStack;
175}
176
177
178#if HAVE_StackWalker
179// See: https://github.com/JochenKalmbach/StackWalker
180static void MyStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc)
181{
182 if (nMaxDestSize <= 0)
183 return;
184 strncpy_s(szDest, nMaxDestSize, szSrc, _TRUNCATE);
185 // INFO: _TRUNCATE will ensure that it is null-terminated;
186 // but with older compilers (<1400) it uses "strncpy" and this does not!)
187 szDest[nMaxDestSize - 1] = 0;
188} // MyStrCpy
189class MyStackWalker : public StackWalker
190{
191public:
192 MyStackWalker(QStringList *lstText,
193 int index,
194 int options = OptionsAll, // 'int' is by design, to combine the enum-flags
195 LPCSTR szSymPath = NULL,
196 DWORD dwProcessId = GetCurrentProcessId(),
197 HANDLE hProcess = GetCurrentProcess())
198 : StackWalker(options, szSymPath, dwProcessId, hProcess)
199 {
200 m_lstText = lstText;
201 m_Index = index + 1;
202 }
203 QStringList *m_lstText;
204 int m_Index;
205
206protected:
207 virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) override;
208 virtual void OnOutput(LPCSTR szText) override
209 {}
210};
211
212void MyStackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry)
213{
214 if(m_Index) {
215 m_Index--;
216 return;
217 }
218
219 CHAR buffer[STACKWALK_MAX_NAMELEN];
220 size_t maxLen = STACKWALK_MAX_NAMELEN;
221#if _MSC_VER >= 1400
222 maxLen = _TRUNCATE;
223#endif
224 if ((eType != lastEntry) && (entry.offset != 0))
225 {
226 if (entry.name[0] == 0)
227 MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, "(function-name not available)");
228 if (entry.undName[0] != 0)
229 MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undName);
230 if (entry.undFullName[0] != 0)
231 MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undFullName);
232 if (entry.moduleName[0] == 0)
233 MyStrCpy(entry.moduleName, STACKWALK_MAX_NAMELEN, "(module-name not available)");
234 if (entry.lineFileName[0] == 0)
235 {
236 MyStrCpy(entry.lineFileName, STACKWALK_MAX_NAMELEN, "(filename not available)");
237 }
238 _snprintf_s(buffer, maxLen, "%s in [%s] (%s:%d) address: %p",
239 entry.name, entry.moduleName,
240 entry.lineFileName, entry.lineNumber,
241 (LPVOID)entry.offset);
242 buffer[STACKWALK_MAX_NAMELEN - 1] = 0;
243 *m_lstText << buffer;
244 }
245}
246
247QStringList PrintStackTrace1(uint index, unsigned int max_frames)
248{
249 QStringList lstStack;
250 MyStackWalker sw(&lstStack, index, StackWalker::RetrieveSymbol);
251 sw.ShowCallstack();
252 return lstStack;
253}
254#endif //#if HAVE_StackWalker
255
256#elif defined(Q_OS_ANDROID)
257// See: https://blog.csdn.net/taohongtaohuyiwei/article/details/105147933
258
259static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
260{
261 std::vector<_Unwind_Word> &stack = *(std::vector<_Unwind_Word>*)arg;
262 stack.push_back(_Unwind_GetIP(context));
263 return _URC_NO_REASON;
264}
265
266QStringList CCallTrace::GetStack(uint index, unsigned int max_frames)
267{
268 QStringList lstStack;
269
270 std::vector<_Unwind_Word> stack;
271 _Unwind_Backtrace(unwindCallback, (void*)&stack);
272
273 int nBufferSize = 1024;
274 QScopedArrayPointer<char> buffer(new char[nBufferSize]);
275 if(!buffer)
276 {
277 qCritical(log) << "new buffer fail";
278 return lstStack;
279 }
280 for (int i = index; i < stack.size(); i++) {
281 Dl_info info;
282 if (!dladdr((void*)stack[i], &info)) {
283 continue;
284 }
285 int addr = (char*)stack[i] - (char*)info.dli_fbase - 1;
286 if (info.dli_sname == NULL || strlen(info.dli_sname) == 0) {
287 sprintf(buffer.data(), "%p [%s]", addr, info.dli_fname);
288 } else {
289 sprintf(buffer.data(), "%p [%s] in %s",
290 addr, info.dli_sname, info.dli_fname);
291 }
292 lstStack << buffer.data();
293 }
294
295 return lstStack;
296}
297#else
298// See: https://segmentfault.com/q/1010000042689957
299// http://skyscribe.github.io/blog/2012/11/27/linuxshang-ru-he-cong-c-plus-plus-cheng-xu-zhong-huo-qu-backtracexin-xi/
301QStringList CCallTrace::GetStack(uint index, unsigned int max_frames)
302{
303 QStringList szMsg;
304
305 int nBufferSize = 1024;
306 QScopedArrayPointer<char> buffer(new char[nBufferSize]);
307 if(!buffer)
308 {
309 qCritical(log) << "new buffer fail";
310 return szMsg;
311 }
312
313 // storage array for stack trace address data
314 void* addrlist[max_frames + 1];
315
316 // retrieve current stack addresses
317 int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
318 if (addrlen == 0) {
319 qCritical(log) << "Get backtrace is empty, possibly corrupt";
320 return szMsg;
321 }
322
332 char** symbollist = backtrace_symbols(addrlist, addrlen);
333
334 // allocate string which will be filled with the demangled function name
335 size_t funcnamesize = 256;
336 char* funcname = (char*)malloc(funcnamesize);
337
338 // iterate over the returned symbol lines. skip the first, it is the
339 // address of this function.
340 for (int i = index; i < addrlen; i++)
341 {
342 char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
343
344 // find parentheses and +address offset surrounding the mangled name:
345 // ./module(function+0x15c) [0x8048a6d]
346 for (char *p = symbollist[i]; *p; ++p)
347 {
348 if (*p == '(')
349 begin_name = p;
350 else if (*p == '+')
351 begin_offset = p;
352 else if (*p == ')' && begin_offset) {
353 end_offset = p;
354 break;
355 }
356 }
357
358 if (begin_name && begin_offset && end_offset
359 && begin_name < begin_offset)
360 {
361 *begin_name++ = '\0';
362 *begin_offset++ = '\0';
363 *end_offset = '\0';
364
365 // mangled name is now in [begin_name, begin_offset) and caller
366 // offset in [begin_offset, end_offset). now apply
367 // __cxa_demangle():
368
369 int status;
370 char* ret = abi::__cxa_demangle(begin_name,
371 funcname, &funcnamesize, &status);
372 if (status == 0) {
373 funcname = ret; // use possibly realloc()-ed string
374
375 snprintf(buffer.data(), nBufferSize, "%p [%s] in %s",
376 begin_offset, funcname, symbollist[i]);
377 } else {
378 // demangling failed. Output function name as a C function with
379 // no arguments.
380 snprintf(buffer.data(), nBufferSize, "%p [%s] in %s",
381 begin_offset, begin_name, symbollist[i]);
382 }
383 }
384 else
385 {
386 // couldn't parse the line? print the whole line.
387 snprintf(buffer.data(), nBufferSize, "%s", symbollist[i]);
388 }
389 szMsg << buffer.data();
390 }
391
392 if(funcname)
393 free(funcname);
394 if(symbollist)
395 free(symbollist);
396 return szMsg;
397}
398#endif // #if defined(Q_OS_WIN)
399
400} //namespace RabbitCommon {