mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-20 07:27:22 -06:00
Pass application directory as PDB search path in SymInitialize. Otherwise it searches in application working directory so when you run qBittorrent with working directory other than its installation one it can't find qbittorent.pdb file and produces broken stacktrace.
358 lines
12 KiB
C++
358 lines
12 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2005-09 by the Quassel Project *
|
|
* devel@quassel-irc.org *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) version 3. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
#include <windows.h>
|
|
#include <dbghelp.h>
|
|
#include <stdio.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDir>
|
|
#include <QTextStream>
|
|
#ifdef __MINGW32__
|
|
#include <cxxabi.h>
|
|
#endif
|
|
|
|
namespace straceWin
|
|
{
|
|
void loadHelpStackFrame(IMAGEHLP_STACK_FRAME&, const STACKFRAME64&);
|
|
BOOL CALLBACK EnumSymbolsCB(PSYMBOL_INFO, ULONG, PVOID);
|
|
BOOL CALLBACK EnumModulesCB(LPCSTR, DWORD64, PVOID);
|
|
const QString getBacktrace();
|
|
struct EnumModulesContext;
|
|
// Also works for MinGW64
|
|
#ifdef __MINGW32__
|
|
void demangle(QString& str);
|
|
#endif
|
|
|
|
QString getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr);
|
|
bool makeRelativePath(const QString& dir, QString& file);
|
|
}
|
|
|
|
#ifdef __MINGW32__
|
|
void straceWin::demangle(QString& str)
|
|
{
|
|
char const* inStr = qPrintable("_" + str); // Really need that underline or demangling will fail
|
|
int status = 0;
|
|
size_t outSz = 0;
|
|
char* demangled_name = abi::__cxa_demangle(inStr, 0, &outSz, &status);
|
|
if (status == 0)
|
|
{
|
|
str = QString::fromLocal8Bit(demangled_name);
|
|
if (outSz > 0)
|
|
free(demangled_name);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void straceWin::loadHelpStackFrame(IMAGEHLP_STACK_FRAME& ihsf, const STACKFRAME64& stackFrame)
|
|
{
|
|
ZeroMemory(&ihsf, sizeof(IMAGEHLP_STACK_FRAME));
|
|
ihsf.InstructionOffset = stackFrame.AddrPC.Offset;
|
|
ihsf.FrameOffset = stackFrame.AddrFrame.Offset;
|
|
}
|
|
|
|
BOOL CALLBACK straceWin::EnumSymbolsCB(PSYMBOL_INFO symInfo, ULONG size, PVOID user)
|
|
{
|
|
Q_UNUSED(size)
|
|
QStringList* params = (QStringList*)user;
|
|
if (symInfo->Flags & SYMFLAG_PARAMETER)
|
|
params->append(symInfo->Name);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
struct straceWin::EnumModulesContext
|
|
{
|
|
HANDLE hProcess;
|
|
QTextStream& stream;
|
|
EnumModulesContext(HANDLE hProcess, QTextStream& stream): hProcess(hProcess), stream(stream) {}
|
|
};
|
|
|
|
BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext)
|
|
{
|
|
Q_UNUSED(ModuleName)
|
|
IMAGEHLP_MODULE64 mod;
|
|
EnumModulesContext* context = (EnumModulesContext*)UserContext;
|
|
mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
|
|
if(SymGetModuleInfo64(context->hProcess, BaseOfDll, &mod))
|
|
{
|
|
QString moduleBase = QString::fromLatin1("0x%1").arg(BaseOfDll, 16, 16, QLatin1Char('0'));
|
|
QString line = QString::fromLatin1("%1 %2 Image: %3")
|
|
.arg(mod.ModuleName, -25)
|
|
.arg(moduleBase, -13)
|
|
.arg(mod.LoadedImageName);
|
|
context->stream << line << '\n';
|
|
|
|
QString pdbName(mod.LoadedPdbName);
|
|
if(!pdbName.isEmpty())
|
|
{
|
|
QString line2 = QString::fromLatin1("%1 %2")
|
|
.arg("", 35)
|
|
.arg(pdbName);
|
|
context->stream << line2 << '\n';
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Cuts off leading 'dir' path from 'file' path, otherwise leaves it unchanged
|
|
* returns true if 'dir' is an ancestor of 'file', otherwise - false
|
|
*/
|
|
bool straceWin::makeRelativePath(const QString& dir, QString& file)
|
|
{
|
|
QString d = QDir::toNativeSeparators(QDir(dir).absolutePath());
|
|
QString f = QDir::toNativeSeparators(QFileInfo(file).absoluteFilePath());
|
|
|
|
// append separator at the end of dir
|
|
QChar separator = QDir::separator();
|
|
if (!d.isEmpty() && (d[d.length() - 1] != separator))
|
|
d += separator;
|
|
|
|
if (f.startsWith(d, Qt::CaseInsensitive))
|
|
{
|
|
f.remove(0, d.length());
|
|
file.swap(f);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
|
|
{
|
|
IMAGEHLP_LINE64 line {};
|
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
|
DWORD dwDisplacement = 0;
|
|
|
|
if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line))
|
|
{
|
|
QString path(line.FileName);
|
|
|
|
#if defined STACKTRACE_WIN_PROJECT_PATH || defined STACKTRACE_WIN_MAKEFILE_PATH
|
|
|
|
#define STACKTRACE_WIN_QUOTE(x) #x
|
|
#define STACKTRACE_WIN_STRING(x) STACKTRACE_WIN_QUOTE(x)
|
|
|
|
//prune leading project directory path or build target directory path
|
|
|
|
bool success = false;
|
|
#ifdef STACKTRACE_WIN_PROJECT_PATH
|
|
QString projectPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_PROJECT_PATH));
|
|
success = makeRelativePath(projectPath, path);
|
|
#endif
|
|
|
|
#ifdef STACKTRACE_WIN_MAKEFILE_PATH
|
|
if (!success)
|
|
{
|
|
QString targetPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_MAKEFILE_PATH));
|
|
makeRelativePath(targetPath, path);
|
|
}
|
|
#endif
|
|
#endif
|
|
return QString::fromLatin1("%1 : %2").arg(path).arg(line.LineNumber);
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
|
|
#if defined( _M_IX86 ) && defined(Q_CC_MSVC)
|
|
// Disable global optimization and ignore /GS waning caused by
|
|
// inline assembly.
|
|
// not needed with mingw cause we can tell mingw which registers we use
|
|
#pragma optimize("g", off)
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4748)
|
|
#endif
|
|
const QString straceWin::getBacktrace()
|
|
{
|
|
DWORD MachineType;
|
|
CONTEXT Context;
|
|
STACKFRAME64 StackFrame;
|
|
|
|
#ifdef _M_IX86
|
|
ZeroMemory(&Context, sizeof(CONTEXT));
|
|
Context.ContextFlags = CONTEXT_CONTROL;
|
|
|
|
|
|
#ifdef __MINGW32__
|
|
asm ("Label:\n\t"
|
|
"movl %%ebp,%0;\n\t"
|
|
"movl %%esp,%1;\n\t"
|
|
"movl $Label,%%eax;\n\t"
|
|
"movl %%eax,%2;\n\t"
|
|
: "=r" (Context.Ebp),"=r" (Context.Esp),"=r" (Context.Eip)
|
|
: //no input
|
|
: "eax");
|
|
#else
|
|
_asm
|
|
{
|
|
Label:
|
|
mov [Context.Ebp], ebp;
|
|
mov [Context.Esp], esp;
|
|
mov eax, [Label];
|
|
mov [Context.Eip], eax;
|
|
}
|
|
#endif
|
|
#else
|
|
RtlCaptureContext(&Context);
|
|
#endif
|
|
|
|
ZeroMemory(&StackFrame, sizeof(STACKFRAME64));
|
|
#ifdef _M_IX86
|
|
MachineType = IMAGE_FILE_MACHINE_I386;
|
|
StackFrame.AddrPC.Offset = Context.Eip;
|
|
StackFrame.AddrPC.Mode = AddrModeFlat;
|
|
StackFrame.AddrFrame.Offset = Context.Ebp;
|
|
StackFrame.AddrFrame.Mode = AddrModeFlat;
|
|
StackFrame.AddrStack.Offset = Context.Esp;
|
|
StackFrame.AddrStack.Mode = AddrModeFlat;
|
|
#elif _M_X64
|
|
MachineType = IMAGE_FILE_MACHINE_AMD64;
|
|
StackFrame.AddrPC.Offset = Context.Rip;
|
|
StackFrame.AddrPC.Mode = AddrModeFlat;
|
|
StackFrame.AddrFrame.Offset = Context.Rsp;
|
|
StackFrame.AddrFrame.Mode = AddrModeFlat;
|
|
StackFrame.AddrStack.Offset = Context.Rsp;
|
|
StackFrame.AddrStack.Mode = AddrModeFlat;
|
|
#elif _M_IA64
|
|
MachineType = IMAGE_FILE_MACHINE_IA64;
|
|
StackFrame.AddrPC.Offset = Context.StIIP;
|
|
StackFrame.AddrPC.Mode = AddrModeFlat;
|
|
StackFrame.AddrFrame.Offset = Context.IntSp;
|
|
StackFrame.AddrFrame.Mode = AddrModeFlat;
|
|
StackFrame.AddrBStore.Offset = Context.RsBSP;
|
|
StackFrame.AddrBStore.Mode = AddrModeFlat;
|
|
StackFrame.AddrStack.Offset = Context.IntSp;
|
|
StackFrame.AddrStack.Mode = AddrModeFlat;
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
|
|
QString log;
|
|
QTextStream logStream(&log);
|
|
logStream << "```\n";
|
|
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
HANDLE hThread = GetCurrentThread();
|
|
SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
|
|
|
|
DWORD64 dwDisplacement;
|
|
|
|
ULONG64 buffer[(sizeof(SYMBOL_INFO) +
|
|
MAX_SYM_NAME * sizeof(TCHAR) +
|
|
sizeof(ULONG64) - 1) / sizeof(ULONG64)];
|
|
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
|
|
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
pSymbol->MaxNameLen = MAX_SYM_NAME;
|
|
|
|
IMAGEHLP_MODULE64 mod;
|
|
mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
|
|
|
|
IMAGEHLP_STACK_FRAME ihsf;
|
|
ZeroMemory(&ihsf, sizeof(IMAGEHLP_STACK_FRAME));
|
|
|
|
int i = 0;
|
|
|
|
while(StackWalk64(MachineType, hProcess, hThread, &StackFrame, &Context, NULL, NULL, NULL, NULL))
|
|
{
|
|
if(i == 128)
|
|
break;
|
|
|
|
loadHelpStackFrame(ihsf, StackFrame);
|
|
if(StackFrame.AddrPC.Offset != 0)
|
|
{ // Valid frame.
|
|
|
|
QString fileName("???");
|
|
if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod))
|
|
{
|
|
fileName = QString(mod.ImageName);
|
|
int slashPos = fileName.lastIndexOf('\\');
|
|
if(slashPos != -1)
|
|
fileName = fileName.mid(slashPos + 1);
|
|
}
|
|
QString funcName;
|
|
QString sourceFile;
|
|
if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol))
|
|
{
|
|
funcName = QString(pSymbol->Name);
|
|
#ifdef __MINGW32__
|
|
demangle(funcName);
|
|
#endif
|
|
|
|
// now ihsf.InstructionOffset points to the instruction that follows CALL instruction
|
|
// decrease the query address by one byte to point somewhere in the CALL instruction byte sequence
|
|
sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
|
|
}
|
|
else
|
|
{
|
|
funcName = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 8, 16, QLatin1Char('0'));
|
|
}
|
|
SymSetContext(hProcess, &ihsf, NULL);
|
|
#ifndef __MINGW32__
|
|
QStringList params;
|
|
SymEnumSymbols(hProcess, 0, NULL, EnumSymbolsCB, (PVOID)¶ms);
|
|
#endif
|
|
|
|
QString insOffset = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 16, 16, QLatin1Char('0'));
|
|
QString formatLine = "#%1 %2 %3 %4";
|
|
#ifndef __MINGW32__
|
|
formatLine += "(%5)";
|
|
#endif
|
|
QString debugLine = formatLine
|
|
.arg(i, 3, 10)
|
|
.arg(fileName, -20)
|
|
.arg(insOffset, -11)
|
|
.arg(funcName)
|
|
#ifndef __MINGW32__
|
|
.arg(params.join(", "));
|
|
|
|
if (!sourceFile.isEmpty())
|
|
debugLine += QString::fromLatin1("[ %1 ]").arg(sourceFile);
|
|
#else
|
|
;
|
|
#endif
|
|
logStream << debugLine << '\n';
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
break; // we're at the end.
|
|
}
|
|
}
|
|
|
|
//logStream << "\n\nList of linked Modules:\n";
|
|
//EnumModulesContext modulesContext(hProcess, logStream);
|
|
//SymEnumerateModules64(hProcess, EnumModulesCB, (PVOID)&modulesContext);
|
|
SymCleanup(hProcess);
|
|
|
|
logStream << "```";
|
|
return log;
|
|
}
|
|
#if defined(_M_IX86) && defined(Q_CC_MSVC)
|
|
#pragma warning(pop)
|
|
#pragma optimize("g", on)
|
|
#endif
|