qBittorrent
stacktrace_win.h
Go to the documentation of this file.
1 /***************************************************************************
2 * Copyright (C) 2005-09 by the Quassel Project *
3 * devel@quassel-irc.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20 
21 #pragma once
22 
23 #include <windows.h>
24 #include <dbghelp.h>
25 #include <stdio.h>
26 
27 #include <QCoreApplication>
28 #include <QDir>
29 #include <QTextStream>
30 #ifdef __MINGW32__
31 #include <cxxabi.h>
32 #endif
33 
34 namespace straceWin
35 {
36  void loadHelpStackFrame(IMAGEHLP_STACK_FRAME&, const STACKFRAME64&);
37  BOOL CALLBACK EnumSymbolsCB(PSYMBOL_INFO, ULONG, PVOID);
38  BOOL CALLBACK EnumModulesCB(LPCSTR, DWORD64, PVOID);
39  const QString getBacktrace();
40  struct EnumModulesContext;
41  // Also works for MinGW64
42 #ifdef __MINGW32__
43  void demangle(QString& str);
44 #endif
45 
46  QString getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr);
47  bool makeRelativePath(const QString& dir, QString& file);
48 }
49 
50 #ifdef __MINGW32__
51 void straceWin::demangle(QString& str)
52 {
53  char const* inStr = qPrintable("_" + str); // Really need that underline or demangling will fail
54  int status = 0;
55  size_t outSz = 0;
56  char* demangled_name = abi::__cxa_demangle(inStr, 0, &outSz, &status);
57  if (status == 0)
58  {
59  str = QString::fromLocal8Bit(demangled_name);
60  if (outSz > 0)
61  free(demangled_name);
62  }
63 }
64 #endif
65 
66 void straceWin::loadHelpStackFrame(IMAGEHLP_STACK_FRAME& ihsf, const STACKFRAME64& stackFrame)
67 {
68  ZeroMemory(&ihsf, sizeof(IMAGEHLP_STACK_FRAME));
69  ihsf.InstructionOffset = stackFrame.AddrPC.Offset;
70  ihsf.FrameOffset = stackFrame.AddrFrame.Offset;
71 }
72 
73 BOOL CALLBACK straceWin::EnumSymbolsCB(PSYMBOL_INFO symInfo, ULONG size, PVOID user)
74 {
75  Q_UNUSED(size)
76  auto params = static_cast<QStringList *>(user);
77  if (symInfo->Flags & SYMFLAG_PARAMETER)
78  params->append(symInfo->Name);
79  return TRUE;
80 }
81 
82 
84 {
85  HANDLE hProcess;
86  QTextStream& stream;
88 };
89 
90 BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext)
91 {
92  Q_UNUSED(ModuleName)
93  IMAGEHLP_MODULE64 mod;
94  auto context = static_cast<EnumModulesContext *>(UserContext);
95  mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
96  if(SymGetModuleInfo64(context->hProcess, BaseOfDll, &mod))
97  {
98  QString moduleBase = QString::fromLatin1("0x%1").arg(BaseOfDll, 16, 16, QLatin1Char('0'));
99  QString line = QString::fromLatin1("%1 %2 Image: %3")
100  .arg(mod.ModuleName, -25)
101  .arg(moduleBase, -13)
102  .arg(mod.LoadedImageName);
103  context->stream << line << '\n';
104 
105  QString pdbName(mod.LoadedPdbName);
106  if(!pdbName.isEmpty())
107  {
108  QString line2 = QString::fromLatin1("%1 %2")
109  .arg("", 35)
110  .arg(pdbName);
111  context->stream << line2 << '\n';
112  }
113  }
114  return TRUE;
115 }
116 
117 
122 bool straceWin::makeRelativePath(const QString& dir, QString& file)
123 {
124  QString d = QDir::toNativeSeparators(QDir(dir).absolutePath());
125  QString f = QDir::toNativeSeparators(QFileInfo(file).absoluteFilePath());
126 
127  // append separator at the end of dir
128  QChar separator = QDir::separator();
129  if (!d.isEmpty() && (d[d.length() - 1] != separator))
130  d += separator;
131 
132  if (f.startsWith(d, Qt::CaseInsensitive))
133  {
134  f.remove(0, d.length());
135  file.swap(f);
136 
137  return true;
138  }
139 
140  return false;
141 }
142 
143 QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
144 {
145  IMAGEHLP_LINE64 line {};
146  line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
147  DWORD dwDisplacement = 0;
148 
149  if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line))
150  {
151  QString path(line.FileName);
152 
153 #if defined STACKTRACE_WIN_PROJECT_PATH || defined STACKTRACE_WIN_MAKEFILE_PATH
154 
155 #define STACKTRACE_WIN_QUOTE(x) #x
156 #define STACKTRACE_WIN_STRING(x) STACKTRACE_WIN_QUOTE(x)
157 
158  //prune leading project directory path or build target directory path
159 
160  bool success = false;
161 #ifdef STACKTRACE_WIN_PROJECT_PATH
162  QString projectPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_PROJECT_PATH));
163  success = makeRelativePath(projectPath, path);
164 #endif
165 
166 #ifdef STACKTRACE_WIN_MAKEFILE_PATH
167  if (!success)
168  {
169  QString targetPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_MAKEFILE_PATH));
170  makeRelativePath(targetPath, path);
171  }
172 #endif
173 #endif
174  return QString::fromLatin1("%1 : %2").arg(path).arg(line.LineNumber);
175  }
176 
177  return QString();
178 }
179 
180 
181 #if defined( _M_IX86 ) && defined(Q_CC_MSVC)
182 // Disable global optimization and ignore /GS waning caused by
183 // inline assembly.
184 // not needed with mingw cause we can tell mingw which registers we use
185 #pragma optimize("g", off)
186 #pragma warning(push)
187 #pragma warning(disable : 4748)
188 #endif
189 const QString straceWin::getBacktrace()
190 {
191  DWORD MachineType;
192  CONTEXT Context;
193  STACKFRAME64 StackFrame;
194 
195 #ifdef _M_IX86
196  ZeroMemory(&Context, sizeof(CONTEXT));
197  Context.ContextFlags = CONTEXT_CONTROL;
198 
199 
200 #ifdef __MINGW32__
201  asm ("Label:\n\t"
202  "movl %%ebp,%0;\n\t"
203  "movl %%esp,%1;\n\t"
204  "movl $Label,%%eax;\n\t"
205  "movl %%eax,%2;\n\t"
206  : "=r" (Context.Ebp),"=r" (Context.Esp),"=r" (Context.Eip)
207  : //no input
208  : "eax");
209 #else
210  _asm
211  {
212  Label:
213  mov [Context.Ebp], ebp;
214  mov [Context.Esp], esp;
215  mov eax, [Label];
216  mov [Context.Eip], eax;
217  }
218 #endif
219 #else
220  RtlCaptureContext(&Context);
221 #endif
222 
223  ZeroMemory(&StackFrame, sizeof(STACKFRAME64));
224 #ifdef _M_IX86
225  MachineType = IMAGE_FILE_MACHINE_I386;
226  StackFrame.AddrPC.Offset = Context.Eip;
227  StackFrame.AddrPC.Mode = AddrModeFlat;
228  StackFrame.AddrFrame.Offset = Context.Ebp;
229  StackFrame.AddrFrame.Mode = AddrModeFlat;
230  StackFrame.AddrStack.Offset = Context.Esp;
231  StackFrame.AddrStack.Mode = AddrModeFlat;
232 #elif _M_X64
233  MachineType = IMAGE_FILE_MACHINE_AMD64;
234  StackFrame.AddrPC.Offset = Context.Rip;
235  StackFrame.AddrPC.Mode = AddrModeFlat;
236  StackFrame.AddrFrame.Offset = Context.Rsp;
237  StackFrame.AddrFrame.Mode = AddrModeFlat;
238  StackFrame.AddrStack.Offset = Context.Rsp;
239  StackFrame.AddrStack.Mode = AddrModeFlat;
240 #elif _M_IA64
241  MachineType = IMAGE_FILE_MACHINE_IA64;
242  StackFrame.AddrPC.Offset = Context.StIIP;
243  StackFrame.AddrPC.Mode = AddrModeFlat;
244  StackFrame.AddrFrame.Offset = Context.IntSp;
245  StackFrame.AddrFrame.Mode = AddrModeFlat;
246  StackFrame.AddrBStore.Offset = Context.RsBSP;
247  StackFrame.AddrBStore.Mode = AddrModeFlat;
248  StackFrame.AddrStack.Offset = Context.IntSp;
249  StackFrame.AddrStack.Mode = AddrModeFlat;
250 #else
251 #error "Unsupported platform"
252 #endif
253 
254  QString log;
255  QTextStream logStream(&log);
256  logStream << "```\n";
257 
258  HANDLE hProcess = GetCurrentProcess();
259  HANDLE hThread = GetCurrentThread();
260  SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
261 
262  DWORD64 dwDisplacement;
263 
264  ULONG64 buffer[(sizeof(SYMBOL_INFO) +
265  MAX_SYM_NAME * sizeof(TCHAR) +
266  sizeof(ULONG64) - 1) / sizeof(ULONG64)];
267  auto pSymbol = reinterpret_cast<PSYMBOL_INFO>(buffer);
268  pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
269  pSymbol->MaxNameLen = MAX_SYM_NAME;
270 
271  IMAGEHLP_MODULE64 mod;
272  mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
273 
274  IMAGEHLP_STACK_FRAME ihsf;
275  ZeroMemory(&ihsf, sizeof(IMAGEHLP_STACK_FRAME));
276 
277  int i = 0;
278 
279  while(StackWalk64(MachineType, hProcess, hThread, &StackFrame, &Context, NULL, NULL, NULL, NULL))
280  {
281  if(i == 128)
282  break;
283 
284  loadHelpStackFrame(ihsf, StackFrame);
285  if(StackFrame.AddrPC.Offset != 0)
286  { // Valid frame.
287 
288  QString fileName("???");
289  if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod))
290  {
291  fileName = QString(mod.ImageName);
292  int slashPos = fileName.lastIndexOf('\\');
293  if(slashPos != -1)
294  fileName = fileName.mid(slashPos + 1);
295  }
296  QString funcName;
297  QString sourceFile;
298  if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol))
299  {
300  funcName = QString(pSymbol->Name);
301 #ifdef __MINGW32__
302  demangle(funcName);
303 #endif
304 
305  // now ihsf.InstructionOffset points to the instruction that follows CALL instruction
306  // decrease the query address by one byte to point somewhere in the CALL instruction byte sequence
307  sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
308  }
309  else
310  {
311  funcName = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 8, 16, QLatin1Char('0'));
312  }
313  SymSetContext(hProcess, &ihsf, NULL);
314 #ifndef __MINGW32__
315  QStringList params;
316  SymEnumSymbols(hProcess, 0, NULL, EnumSymbolsCB, (PVOID)&params);
317 #endif
318 
319  QString insOffset = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 16, 16, QLatin1Char('0'));
320  QString formatLine = "#%1 %2 %3 %4";
321 #ifndef __MINGW32__
322  formatLine += "(%5)";
323 #endif
324  QString debugLine = formatLine
325  .arg(i, 3, 10)
326  .arg(fileName, -20)
327  .arg(insOffset, -11)
328  .arg(funcName)
329 #ifndef __MINGW32__
330  .arg(params.join(", "));
331 
332  if (!sourceFile.isEmpty())
333  debugLine += QString::fromLatin1("[ %1 ]").arg(sourceFile);
334 #else
335  ;
336 #endif
337  logStream << debugLine << '\n';
338  i++;
339  }
340  else
341  {
342  break; // we're at the end.
343  }
344  }
345 
346  //logStream << "\n\nList of linked Modules:\n";
347  //EnumModulesContext modulesContext(hProcess, logStream);
348  //SymEnumerateModules64(hProcess, EnumModulesCB, (PVOID)&modulesContext);
349  SymCleanup(hProcess);
350 
351  logStream << "```";
352  return log;
353 }
354 #if defined(_M_IX86) && defined(Q_CC_MSVC)
355 #pragma warning(pop)
356 #pragma optimize("g", on)
357 #endif
QString fileName(const QString &filePath)
Definition: fs.cpp:87
BOOL CALLBACK EnumSymbolsCB(PSYMBOL_INFO, ULONG, PVOID)
BOOL CALLBACK EnumModulesCB(LPCSTR, DWORD64, PVOID)
bool makeRelativePath(const QString &dir, QString &file)
QString getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
void loadHelpStackFrame(IMAGEHLP_STACK_FRAME &, const STACKFRAME64 &)
const QString getBacktrace()
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
Definition: CMakeLists.txt:5
EnumModulesContext(HANDLE hProcess, QTextStream &stream)
void f()
Definition: test2.c:1