Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / app / stacktrace_win.h
blobc4d9e13a9689add4c5d5cad684798bca683965b1
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 ***************************************************************************/
21 #pragma once
23 #include <windows.h>
24 #include <dbghelp.h>
25 #include <stdio.h>
27 #include <QCoreApplication>
28 #include <QDir>
29 #include <QTextStream>
30 #ifdef __MINGW32__
31 #include <cxxabi.h>
32 #endif
34 #include "base/global.h"
36 namespace straceWin
38 void loadHelpStackFrame(IMAGEHLP_STACK_FRAME&, const STACKFRAME64&);
39 BOOL CALLBACK EnumSymbolsCB(PSYMBOL_INFO, ULONG, PVOID);
40 BOOL CALLBACK EnumModulesCB(LPCSTR, DWORD64, PVOID);
41 const QString getBacktrace();
42 struct EnumModulesContext;
43 // Also works for MinGW64
44 #ifdef __MINGW32__
45 void demangle(QString& str);
46 #endif
48 QString getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr);
49 bool makeRelativePath(const QString& dir, QString& file);
52 #ifdef __MINGW32__
53 void straceWin::demangle(QString& str)
55 char const* inStr = qPrintable(u"_" + str); // Really need that underline or demangling will fail
56 int status = 0;
57 size_t outSz = 0;
58 char* demangled_name = abi::__cxa_demangle(inStr, 0, &outSz, &status);
59 if (status == 0)
61 str = QString::fromLocal8Bit(demangled_name);
62 if (outSz > 0)
63 free(demangled_name);
66 #endif
68 void straceWin::loadHelpStackFrame(IMAGEHLP_STACK_FRAME& ihsf, const STACKFRAME64& stackFrame)
70 ZeroMemory(&ihsf, sizeof(IMAGEHLP_STACK_FRAME));
71 ihsf.InstructionOffset = stackFrame.AddrPC.Offset;
72 ihsf.FrameOffset = stackFrame.AddrFrame.Offset;
75 BOOL CALLBACK straceWin::EnumSymbolsCB(PSYMBOL_INFO symInfo, ULONG size, PVOID user)
77 Q_UNUSED(size)
78 auto params = static_cast<QStringList *>(user);
79 if (symInfo->Flags & SYMFLAG_PARAMETER)
80 params->append(QString::fromUtf8(symInfo->Name));
81 return TRUE;
85 struct straceWin::EnumModulesContext
87 HANDLE hProcess;
88 QTextStream& stream;
89 EnumModulesContext(HANDLE hProcess, QTextStream& stream): hProcess(hProcess), stream(stream) {}
92 BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext)
94 Q_UNUSED(ModuleName)
95 IMAGEHLP_MODULE64 mod;
96 auto context = static_cast<EnumModulesContext *>(UserContext);
97 mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
98 if(SymGetModuleInfo64(context->hProcess, BaseOfDll, &mod))
100 QString moduleBase = QString::fromLatin1("0x%1").arg(BaseOfDll, 16, 16, QLatin1Char('0'));
101 QString line = QString::fromLatin1("%1 %2 Image: %3")
102 .arg(QString::fromUtf8(mod.ModuleName), -25)
103 .arg(moduleBase, -13)
104 .arg(QString::fromUtf8(mod.LoadedImageName));
105 context->stream << line << '\n';
107 const auto pdbName = QString::fromUtf8(mod.LoadedPdbName);
108 if(!pdbName.isEmpty())
110 QString line2 = QString::fromLatin1("%1 %2")
111 .arg(u""_qs, 35)
112 .arg(pdbName);
113 context->stream << line2 << '\n';
116 return TRUE;
121 * Cuts off leading 'dir' path from 'file' path, otherwise leaves it unchanged
122 * returns true if 'dir' is an ancestor of 'file', otherwise - false
124 bool straceWin::makeRelativePath(const QString &dir, QString &file)
126 QString d = QDir::toNativeSeparators(QDir(dir).absolutePath());
127 QString f = QDir::toNativeSeparators(QFileInfo(file).absoluteFilePath());
129 // append separator at the end of dir
130 QChar separator = QDir::separator();
131 if (!d.isEmpty() && (d[d.length() - 1] != separator))
132 d += separator;
134 if (f.startsWith(d, Qt::CaseInsensitive))
136 f.remove(0, d.length());
137 file.swap(f);
139 return true;
142 return false;
145 QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
147 IMAGEHLP_LINE64 line {};
148 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
149 DWORD dwDisplacement = 0;
151 if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line))
153 auto path = QString::fromUtf8(line.FileName);
155 #if defined STACKTRACE_WIN_PROJECT_PATH || defined STACKTRACE_WIN_MAKEFILE_PATH
157 #define STACKTRACE_WIN_QUOTE(x) #x
158 #define STACKTRACE_WIN_STRING(x) STACKTRACE_WIN_QUOTE(x)
160 //prune leading project directory path or build target directory path
162 bool success = false;
163 #ifdef STACKTRACE_WIN_PROJECT_PATH
164 const auto projectPath = QStringLiteral(STACKTRACE_WIN_STRING(STACKTRACE_WIN_PROJECT_PATH));
165 success = makeRelativePath(projectPath, path);
166 #endif
168 #ifdef STACKTRACE_WIN_MAKEFILE_PATH
169 if (!success)
171 const auto targetPath = QStringLiteral(STACKTRACE_WIN_STRING(STACKTRACE_WIN_MAKEFILE_PATH));
172 makeRelativePath(targetPath, path);
174 #endif
175 #endif
176 return QString::fromLatin1("%1 : %2").arg(path).arg(line.LineNumber);
179 return QString();
183 #if defined( _M_IX86 ) && defined(Q_CC_MSVC)
184 // Disable global optimization and ignore /GS waning caused by
185 // inline assembly.
186 // not needed with mingw cause we can tell mingw which registers we use
187 #pragma optimize("g", off)
188 #pragma warning(push)
189 #pragma warning(disable : 4748)
190 #endif
191 const QString straceWin::getBacktrace()
193 DWORD MachineType;
194 CONTEXT Context;
195 STACKFRAME64 StackFrame;
197 #ifdef _M_IX86
198 ZeroMemory(&Context, sizeof(CONTEXT));
199 Context.ContextFlags = CONTEXT_CONTROL;
202 #ifdef __MINGW32__
203 asm ("Label:\n\t"
204 "movl %%ebp,%0;\n\t"
205 "movl %%esp,%1;\n\t"
206 "movl $Label,%%eax;\n\t"
207 "movl %%eax,%2;\n\t"
208 : "=r" (Context.Ebp),"=r" (Context.Esp),"=r" (Context.Eip)
209 : //no input
210 : "eax");
211 #else
212 _asm
214 Label:
215 mov [Context.Ebp], ebp;
216 mov [Context.Esp], esp;
217 mov eax, [Label];
218 mov [Context.Eip], eax;
220 #endif
221 #else
222 RtlCaptureContext(&Context);
223 #endif
225 ZeroMemory(&StackFrame, sizeof(STACKFRAME64));
226 #ifdef _M_IX86
227 MachineType = IMAGE_FILE_MACHINE_I386;
228 StackFrame.AddrPC.Offset = Context.Eip;
229 StackFrame.AddrPC.Mode = AddrModeFlat;
230 StackFrame.AddrFrame.Offset = Context.Ebp;
231 StackFrame.AddrFrame.Mode = AddrModeFlat;
232 StackFrame.AddrStack.Offset = Context.Esp;
233 StackFrame.AddrStack.Mode = AddrModeFlat;
234 #elif _M_X64
235 MachineType = IMAGE_FILE_MACHINE_AMD64;
236 StackFrame.AddrPC.Offset = Context.Rip;
237 StackFrame.AddrPC.Mode = AddrModeFlat;
238 StackFrame.AddrFrame.Offset = Context.Rsp;
239 StackFrame.AddrFrame.Mode = AddrModeFlat;
240 StackFrame.AddrStack.Offset = Context.Rsp;
241 StackFrame.AddrStack.Mode = AddrModeFlat;
242 #elif _M_IA64
243 MachineType = IMAGE_FILE_MACHINE_IA64;
244 StackFrame.AddrPC.Offset = Context.StIIP;
245 StackFrame.AddrPC.Mode = AddrModeFlat;
246 StackFrame.AddrFrame.Offset = Context.IntSp;
247 StackFrame.AddrFrame.Mode = AddrModeFlat;
248 StackFrame.AddrBStore.Offset = Context.RsBSP;
249 StackFrame.AddrBStore.Mode = AddrModeFlat;
250 StackFrame.AddrStack.Offset = Context.IntSp;
251 StackFrame.AddrStack.Mode = AddrModeFlat;
252 #else
253 #error "Unsupported platform"
254 #endif
256 QString log;
257 QTextStream logStream(&log);
258 logStream << "```\n";
260 HANDLE hProcess = GetCurrentProcess();
261 HANDLE hThread = GetCurrentThread();
262 SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
264 DWORD64 dwDisplacement;
266 ULONG64 buffer[(sizeof(SYMBOL_INFO) +
267 MAX_SYM_NAME * sizeof(TCHAR) +
268 sizeof(ULONG64) - 1) / sizeof(ULONG64)];
269 auto pSymbol = reinterpret_cast<PSYMBOL_INFO>(buffer);
270 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
271 pSymbol->MaxNameLen = MAX_SYM_NAME;
273 IMAGEHLP_MODULE64 mod;
274 mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
276 IMAGEHLP_STACK_FRAME ihsf;
277 ZeroMemory(&ihsf, sizeof(IMAGEHLP_STACK_FRAME));
279 int i = 0;
281 while(StackWalk64(MachineType, hProcess, hThread, &StackFrame, &Context, NULL, NULL, NULL, NULL))
283 if(i == 128)
284 break;
286 loadHelpStackFrame(ihsf, StackFrame);
287 if(StackFrame.AddrPC.Offset != 0)
288 { // Valid frame.
290 auto fileName = u"???"_qs;
291 if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod))
293 fileName = QString::fromUtf8(mod.ImageName);
294 int slashPos = fileName.lastIndexOf(u'\\');
295 if(slashPos != -1)
296 fileName = fileName.mid(slashPos + 1);
298 QString funcName;
299 QString sourceFile;
300 if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol))
302 funcName = QString::fromUtf8(pSymbol->Name);
303 #ifdef __MINGW32__
304 demangle(funcName);
305 #endif
307 // now ihsf.InstructionOffset points to the instruction that follows CALL instruction
308 // decrease the query address by one byte to point somewhere in the CALL instruction byte sequence
309 sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
311 else
313 funcName = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 8, 16, QLatin1Char('0'));
315 SymSetContext(hProcess, &ihsf, NULL);
316 #ifndef __MINGW32__
317 QStringList params;
318 SymEnumSymbols(hProcess, 0, NULL, EnumSymbolsCB, (PVOID)&params);
319 #endif
321 QString insOffset = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 16, 16, QLatin1Char('0'));
322 auto formatLine = u"#%1 %2 %3 %4"_qs;
323 #ifndef __MINGW32__
324 formatLine += u"(%5)"_qs;
325 #endif
326 QString debugLine = formatLine
327 .arg(i, 3, 10)
328 .arg(fileName, -20)
329 .arg(insOffset, -11)
330 .arg(funcName)
331 #ifndef __MINGW32__
332 .arg(params.join(u", "));
334 if (!sourceFile.isEmpty())
335 debugLine += QString::fromLatin1("[ %1 ]").arg(sourceFile);
336 #else
338 #endif
339 logStream << debugLine << '\n';
340 i++;
342 else
344 break; // we're at the end.
348 //logStream << "\n\nList of linked Modules:\n";
349 //EnumModulesContext modulesContext(hProcess, logStream);
350 //SymEnumerateModules64(hProcess, EnumModulesCB, (PVOID)&modulesContext);
351 SymCleanup(hProcess);
353 logStream << "```";
354 return log;
356 #if defined(_M_IX86) && defined(Q_CC_MSVC)
357 #pragma warning(pop)
358 #pragma optimize("g", on)
359 #endif