diff --git a/CMakeLists.txt b/CMakeLists.txt index 0aede208..b0d721aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,43 +1,87 @@ cmake_minimum_required(VERSION 3.0) project(drkonqi) set(PROJECT_VERSION "5.13.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.9.0") set(KF5_MIN_VERSION "5.42.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) include(ECMMarkAsTest) include(CheckFunctionExists) include(FeatureSummary) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets Test DBus Concurrent) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS I18n CoreAddons Service ConfigWidgets JobWidgets KIO Crash Completion XmlRpcClient WidgetsAddons Wallet Notifications IdleTime) find_package(Qt5X11Extras ${QT_MIN_VERSION} CONFIG) set_package_properties(Qt5X11Extras PROPERTIES TYPE RECOMMENDED PURPOSE "Recommended for better integration on X11.") +if (MINGW) + find_package(ZLIB REQUIRED) + find_library(INTL_LIBRARY NAMES intl) + find_library(IBERTY_LIBRARY NAMES iberty) + find_library(BFD_LIBRARY NAMES bfd) + if (IBERTY_LIBRARY) + set(iberty_FOUND 1) + else() + set(msg "iberty") + endif() + if (BFD_LIBRARY) + set(bfd_FOUND 1) + else() + set(msg "${msg} bfd") + endif() + if (INTL_LIBRARY) + set(intl_FOUND 1) + else() + set(msg "${msg} intl") + endif() + if (msg) + message(FATAL_ERROR "could not find ${msg}") + endif() + add_library(z STATIC IMPORTED) + set_target_properties(z PROPERTIES + IMPORTED_LOCATION ${ZLIB_LIBRARIES} + IMPORTED_INCLUDE_DIRECTORIES ${ZLIB_INCLUDE_DIRS} + ) + add_library(intl SHARED IMPORTED) + set_target_properties(intl PROPERTIES + IMPORTED_IMPLIB ${INTL_LIBRARY} + ) + add_library(iberty STATIC IMPORTED) + set_target_properties(iberty PROPERTIES + IMPORTED_LOCATION ${IBERTY_LIBRARY} + ) + add_library(bfd STATIC IMPORTED) + set_target_properties(bfd PROPERTIES + IMPORTED_LOCATION ${BFD_LIBRARY} + # bfd header requires this to be defined + INTERFACE_COMPILE_DEFINITIONS "PACKAGE;PACKAGE_VERSION" + ) +endif() + include_directories("${CMAKE_CURRENT_BINARY_DIR}") configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_definitions(-DQT_USE_QSTRINGBUILDER) add_definitions(-DQT_NO_CAST_FROM_ASCII) add_definitions(-DQT_NO_CAST_TO_ASCII) add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_subdirectory(src) install( FILES drkonqi.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 83f6f6f8..0f4cc51a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,119 +1,126 @@ include (CheckFunctionExists) check_function_exists("strsignal" HAVE_STRSIGNAL) check_function_exists("uname" HAVE_UNAME) if (NOT DEBUG_PACKAGE_INSTALLER_NAME) set (DEBUG_PACKAGE_INSTALLER_NAME "installdbgsymbols.sh") endif () configure_file (config-drkonqi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-drkonqi.h ) add_subdirectory( data ) add_subdirectory( parser ) if ( WIN32 ) + find_package(KDEWin REQUIRED) + # for finding drkonqi_debug.h + include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory( kdbgwin ) endif () set(drkonqi_SRCS main.cpp drkonqidialog.cpp statuswidget.cpp aboutbugreportingdialog.cpp backtraceratingwidget.cpp backtracewidget.cpp backtracegenerator.cpp drkonqi.cpp drkonqibackends.cpp detachedprocessmonitor.cpp debugpackageinstaller.cpp systeminformation.cpp crashedapplication.cpp debugger.cpp debuggerlaunchers.cpp debuggermanager.cpp applicationdetailsexamples.cpp gdbhighlighter.cpp statusnotifier.cpp ) ki18n_wrap_ui(drkonqi_SRCS ui/maindialog.ui ui/backtracewidget.ui ) # if BACKTRACE_PARSER_DEBUG is enabled, it will show both the # parsed and the unparsed backtrace in the backtrace widget. # Comment this out for release. #add_definitions(-DBACKTRACE_PARSER_DEBUG) set(drkonqi_SRCS ${drkonqi_SRCS} bugzillaintegration/bugzillalib.cpp bugzillaintegration/reportassistantdialog.cpp bugzillaintegration/reportassistantpage.cpp bugzillaintegration/reportassistantpages_base.cpp bugzillaintegration/reportassistantpages_bugzilla.cpp bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp bugzillaintegration/reportinterface.cpp bugzillaintegration/productmapping.cpp bugzillaintegration/parsebugbacktraces.cpp # Requires kxmlrpcclient bugzillaintegration/duplicatefinderjob.cpp ) ki18n_wrap_ui(drkonqi_SRCS bugzillaintegration/ui/assistantpage_introduction.ui bugzillaintegration/ui/assistantpage_bugawareness.ui bugzillaintegration/ui/assistantpage_conclusions.ui bugzillaintegration/ui/assistantpage_conclusions_dialog.ui bugzillaintegration/ui/assistantpage_bugzilla_login.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates_dialog.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates_dialog_confirmation.ui bugzillaintegration/ui/assistantpage_bugzilla_information.ui bugzillaintegration/ui/assistantpage_bugzilla_preview.ui bugzillaintegration/ui/assistantpage_bugzilla_send.ui ) ecm_qt_declare_logging_category(drkonqi_SRCS HEADER drkonqi_debug.h IDENTIFIER DRKONQI_LOG CATEGORY_NAME org.kde.drkonqi) add_executable(drkonqi ${drkonqi_SRCS}) ecm_mark_nongui_executable(drkonqi) target_compile_definitions(drkonqi PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") target_link_libraries(drkonqi KF5::I18n KF5::CoreAddons KF5::Service KF5::ConfigWidgets KF5::JobWidgets KF5::KIOCore KF5::Crash KF5::Completion Qt5::DBus KF5::XmlRpcClient KF5::WidgetsAddons KF5::Wallet KF5::Notifications # for status notifier KF5::IdleTime # hide status notifier only if user saw it drkonqi_backtrace_parser ) if (${Qt5X11Extras_FOUND}) target_link_libraries(drkonqi Qt5::X11Extras ) endif() +if (WIN32) + target_link_libraries(drkonqi kdewin) +endif() + install(TARGETS drkonqi DESTINATION ${KDE_INSTALL_LIBEXECDIR}) configure_file(org.kde.drkonqi.desktop.cmake ${CMAKE_BINARY_DIR}/src/org.kde.drkonqi.desktop) install(PROGRAMS ${CMAKE_BINARY_DIR}/src/org.kde.drkonqi.desktop DESTINATION ${KDE_INSTALL_APPDIR}) # Only go into tests once we have a drkonqi target so the tests can reference # it. add_subdirectory( tests ) diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp index 9f06985c..c9746a99 100644 --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -1,233 +1,233 @@ /* Copyright (C) 2009 George Kiagiadakis 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) any later version. 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, see . */ #include "drkonqibackends.h" #include #include #include #include #include #include #include #include #include #include #include "drkonqi_debug.h" #include "crashedapplication.h" #include "debugger.h" #include "debuggermanager.h" #include "backtracegenerator.h" #include "drkonqi.h" AbstractDrKonqiBackend::~AbstractDrKonqiBackend() { } bool AbstractDrKonqiBackend::init() { m_crashedApplication = constructCrashedApplication(); m_debuggerManager = constructDebuggerManager(); return true; } KCrashBackend::KCrashBackend() : QObject(), AbstractDrKonqiBackend(), m_state(ProcessRunning) { } KCrashBackend::~KCrashBackend() { continueAttachedProcess(); } bool KCrashBackend::init() { AbstractDrKonqiBackend::init(); //check whether the attached process exists and whether we have permissions to inspect it if (crashedApplication()->pid() <= 0) { qCWarning(DRKONQI_LOG) << "Invalid pid specified"; return false; } #if !defined(Q_OS_WIN32) if (::kill(crashedApplication()->pid(), 0) < 0) { switch (errno) { case EPERM: qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process"; break; case ESRCH: qCWarning(DRKONQI_LOG) << "The specified process does not exist."; break; default: break; } return false; } //--keeprunning means: generate backtrace instantly and let the process continue execution if(DrKonqi::isKeepRunning()) { stopAttachedProcess(); debuggerManager()->backtraceGenerator()->start(); connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess); } else { connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting); connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished); //stop the process to avoid high cpu usage by other threads (bug 175362). //if the process was started by kdeinit, we need to wait a bit for KCrash //to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else //if we stop it before this call, pending alarm signals will kill the //process when we try to continue it. QTimer::singleShot(2000, this, &KCrashBackend::stopAttachedProcess); } #endif //Handle drkonqi crashes s_pid = crashedApplication()->pid(); //copy pid for use by the crash handler, so that it is safer KCrash::setEmergencySaveFunction(emergencySaveFunction); return true; } CrashedApplication *KCrashBackend::constructCrashedApplication() { CrashedApplication *a = new CrashedApplication(this); a->m_datetime = QDateTime::currentDateTime(); a->m_name = DrKonqi::programName(); a->m_version = DrKonqi::appVersion(); a->m_reportAddress = BugReportAddress(DrKonqi::bugAddress()); a->m_pid = DrKonqi::pid(); a->m_signalNumber = DrKonqi::signal(); a->m_restarted = DrKonqi::isRestarted(); a->m_thread = DrKonqi::thread(); //try to determine the executable that crashed if ( QFileInfo(QStringLiteral("/proc/%1/exe").arg(a->m_pid)).exists() ) { //on linux, the fastest and most reliable way is to get the path from /proc qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path"; a->m_executable.setFile(QFile::symLinkTarget(QStringLiteral("/proc/%1/exe").arg(a->m_pid))); if (DrKonqi::isKdeinit() || a->m_executable.fileName().startsWith(QLatin1String("python")) ) { a->m_fakeBaseName = DrKonqi::appName(); } } else { if ( DrKonqi::isKdeinit() ) { a->m_executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5"))); a->m_fakeBaseName = DrKonqi::appName(); } else { QFileInfo execPath(DrKonqi::appName()); if ( execPath.isAbsolute() ) { a->m_executable = execPath; } else if ( !DrKonqi::appPath().isEmpty() ) { QDir execDir(DrKonqi::appPath()); a->m_executable = execDir.absoluteFilePath(execPath.fileName()); } else { a->m_executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName())); } } } qCDebug(DRKONQI_LOG) << "Executable is:" << a->m_executable.absoluteFilePath(); qCDebug(DRKONQI_LOG) << "Executable exists:" << a->m_executable.exists(); return a; } DebuggerManager *KCrashBackend::constructDebuggerManager() { QList internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash")); KConfigGroup config(KSharedConfig::openConfig(), "DrKonqi"); #ifndef Q_OS_WIN QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb")); #else - QString defaultDebuggerName = config.readEntry("Debugger", QString("kdbgwin")); + QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("kdbgwin")); #endif Debugger firstKnownGoodDebugger, preferredDebugger; foreach (const Debugger & debugger, internalDebuggers) { if (!firstKnownGoodDebugger.isValid() && debugger.isInstalled()) { firstKnownGoodDebugger = debugger; } if (debugger.codeName() == defaultDebuggerName) { preferredDebugger = debugger; } if (firstKnownGoodDebugger.isValid() && preferredDebugger.isValid()) { break; } } if (!preferredDebugger.isInstalled()) { if (firstKnownGoodDebugger.isValid()) { preferredDebugger = firstKnownGoodDebugger; } else { qCWarning(DRKONQI_LOG) << "Unable to find an internal debugger that can work with the KCrash backend"; } } return new DebuggerManager(preferredDebugger, Debugger::availableExternalDebuggers(QStringLiteral("KCrash")), this); } void KCrashBackend::stopAttachedProcess() { if (m_state == ProcessRunning) { qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process"; ::kill(crashedApplication()->pid(), SIGSTOP); m_state = ProcessStopped; } } void KCrashBackend::continueAttachedProcess() { if (m_state == ProcessStopped) { qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process"; ::kill(crashedApplication()->pid(), SIGCONT); m_state = ProcessRunning; } } void KCrashBackend::onDebuggerStarting() { continueAttachedProcess(); m_state = DebuggerRunning; } void KCrashBackend::onDebuggerFinished() { m_state = ProcessRunning; stopAttachedProcess(); } //static qint64 KCrashBackend::s_pid = 0; //static void KCrashBackend::emergencySaveFunction(int signal) { // In case drkonqi itself crashes, we need to get rid of the process being debugged, // so we kill it, no matter what its state was. Q_UNUSED(signal); ::kill(s_pid, SIGKILL); } diff --git a/src/kdbgwin/CMakeLists.txt b/src/kdbgwin/CMakeLists.txt index 82196350..f504ef33 100644 --- a/src/kdbgwin/CMakeLists.txt +++ b/src/kdbgwin/CMakeLists.txt @@ -1,27 +1,28 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + set(kdbgwin_SRCS common.h main.cpp process.h process.cpp abstract_generator.h abstract_generator.cpp callbacks.h callbacks.cpp outputters.h outputters.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../drkonqi_debug.cpp ) set(COMMON_LIBS dbghelp psapi shlwapi) if ( MINGW ) - # FIXME this should be handled in a higher level cmake with proper find - # scripts and configuration add_executable(kdbgwin ${kdbgwin_SRCS} mingw_generator.h mingw_generator.cpp) - target_link_libraries(kdbgwin ${COMMON_LIBS} bfd iberty intl z) + target_link_libraries(kdbgwin PRIVATE ${COMMON_LIBS} bfd iberty intl z Qt5::Core) endif () if ( MSVC OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) add_executable(kdbgwin ${kdbgwin_SRCS} msvc_generator.h msvc_generator.cpp) target_link_libraries(kdbgwin ${COMMON_LIBS}) endif () install(TARGETS kdbgwin DESTINATION ${KDE_INSTALL_LIBEXECDIR}) diff --git a/src/kdbgwin/abstract_generator.cpp b/src/kdbgwin/abstract_generator.cpp index 13863d94..dcb1f09f 100644 --- a/src/kdbgwin/abstract_generator.cpp +++ b/src/kdbgwin/abstract_generator.cpp @@ -1,240 +1,240 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #include "abstract_generator.h" #include "callbacks.h" #include AbstractBTGenerator::AbstractBTGenerator(const Process& process) : m_process(process) { assert(process.IsValid()); } AbstractBTGenerator::~AbstractBTGenerator() { } QString AbstractBTGenerator::GetModuleName() { IMAGEHLP_MODULE64 module; ZeroMemory(&module, sizeof(module)); module.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); if (!SymGetModuleInfo64(m_process.GetHandle(), m_currentFrame.AddrPC.Offset, &module)) { qCCritical(DRKONQI_LOG) << "SymGetModuleInfo64 failed: " << GetLastError(); return QLatin1String(DEFAULT_MODULE); } - QStringList list = QString(module.ImageName).split("\\"); + QStringList list = QString(QLatin1String(module.ImageName)).split(QStringLiteral("\\")); return list[list.size() - 1]; } QString AbstractBTGenerator::GetModulePath() { IMAGEHLP_MODULE64 module; ZeroMemory(&module, sizeof(module)); module.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); if (!SymGetModuleInfo64(m_process.GetHandle(), m_currentFrame.AddrPC.Offset, &module)) { qCCritical(DRKONQI_LOG) << "SymGetModuleInfo64 failed: " << GetLastError(); return QLatin1String(DEFAULT_MODULE); } - return QString(module.ImageName); + return QString(QLatin1String(module.ImageName)); } void AbstractBTGenerator::Run(HANDLE hThread, bool bFaultingThread) { assert(m_process.IsValid()); assert(hThread); if (!Init()) { assert(false); return; } //HANDLE hFile = CreateFile(L"C:\\test\\test.dmp", FILE_ALL_ACCESS, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); //if (!MiniDumpWriteDump(m_process.GetHandle(), m_process.GetId(), hFile, // MiniDumpNormal, NULL, NULL, NULL)) //{ // HRESULT hres = (HRESULT) GetLastError(); // printf("%08X\n\n", hres); //} //SafeCloseHandle(hFile); DWORD dw = SuspendThread(hThread); assert(dw != DWORD(-1)); if (dw == DWORD(-1)) { qCCritical(DRKONQI_LOG) << "SuspendThread() failed: " << GetLastError(); return; } CONTEXT context; ZeroMemory(&context, sizeof(context)); if (!bFaultingThread) { // if it's not the faulting thread, get its context context.ContextFlags = CONTEXT_FULL; if (!GetThreadContext(hThread, &context)) { ResumeThread(hThread); assert(false); qCCritical(DRKONQI_LOG) << "GetThreadContext() failed: " << GetLastError(); return; } } else { // if it is, get it from KCrash HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Local\\KCrashShared"); if (hMapFile == NULL) { qCCritical(DRKONQI_LOG) << "OpenFileMapping() failed: " << GetLastError(); return; } CONTEXT *othercontext = (CONTEXT*) MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CONTEXT)); if (othercontext == NULL) { qCCritical(DRKONQI_LOG) << "MapViewOfFile() failed: " << GetLastError(); SafeCloseHandle(hMapFile); return; } CopyMemory(&context, othercontext, sizeof(CONTEXT)); UnmapViewOfFile(othercontext); // continue even if it fails SafeCloseHandle(hMapFile); } // some of this stuff is taken from StackWalker ZeroMemory(&m_currentFrame, sizeof(m_currentFrame)); DWORD machineType = IMAGE_FILE_MACHINE_UNKNOWN; #if defined(_M_IX86) machineType = IMAGE_FILE_MACHINE_I386; m_currentFrame.AddrPC.Offset = context.Eip; m_currentFrame.AddrFrame.Offset = context.Ebp; m_currentFrame.AddrStack.Offset = context.Esp; #elif defined(_M_X64) machineType = IMAGE_FILE_MACHINE_AMD64; m_currentFrame.AddrPC.Offset = context.Rip; m_currentFrame.AddrFrame.Offset = context.Rbp; m_currentFrame.AddrStack.Offset = context.Rsp; #else # error This architecture is not supported. #endif m_currentFrame.AddrPC.Mode = AddrModeFlat; m_currentFrame.AddrFrame.Mode = AddrModeFlat; m_currentFrame.AddrStack.Mode = AddrModeFlat; SymSetOptions(SymGetOptions() | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES); SymInitialize(m_process.GetHandle(), NULL, FALSE); LoadSymbols(); for (int i = 0; /*nothing*/; i++) { SetLastError(0); if (!StackWalk64( machineType, m_process.GetHandle(), hThread, &m_currentFrame, &context, &Callbacks::ReadProcessMemory, &Callbacks::SymFunctionTableAccess64, &Callbacks::SymGetModuleBase64, NULL)) { emit Finished(); qCDebug(DRKONQI_LOG) << "Stackwalk finished; GetLastError=" << GetLastError(); break; } FrameChanged(); QString modulename = GetModuleName(); QString functionname = GetFunctionName(); QString file = GetFile(); int line = GetLine(); QString address = QString::number(m_currentFrame.AddrPC.Offset, 16); QString debugLine = QString::fromLatin1(BACKTRACE_FORMAT). arg(modulename).arg(functionname).arg(file).arg(line).arg(address); emit DebugLine(debugLine); } // Resume the target thread now, or else the crashing process will not // be terminated ResumeThread(hThread); SymCleanup(m_process.GetHandle()); } bool AbstractBTGenerator::IsSymbolLoaded(const QString& module) { if (m_symbolsMap.contains(module)) { return m_symbolsMap[module]; } return false; } void AbstractBTGenerator::LoadSymbols() { TModulesMap modules = m_process.GetModules(); for (TModulesMap::iterator i = modules.begin(); i != modules.end(); i++) { MODULEINFO modInfo; ZeroMemory(&modInfo, sizeof(modInfo)); QString strModule = i.key(); GetModuleInformation(m_process.GetHandle(), i.value(), &modInfo, sizeof(modInfo)); SymLoadModuleEx( m_process.GetHandle(), NULL, (CHAR*) i.key().toLatin1().constData(), (CHAR*) i.key().toLatin1().constData(), (DWORD64) modInfo.lpBaseOfDll, modInfo.SizeOfImage, NULL, 0); LoadSymbol(strModule, (DWORD64) modInfo.lpBaseOfDll); if (!IsSymbolLoaded(strModule)) { emit MissingSymbol(strModule); } } emit DebugLine(QString()); emit DebugLine(QString()); } diff --git a/src/kdbgwin/abstract_generator.h b/src/kdbgwin/abstract_generator.h index 271b6e0d..cf9b26dd 100644 --- a/src/kdbgwin/abstract_generator.h +++ b/src/kdbgwin/abstract_generator.h @@ -1,121 +1,123 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #pragma once #include "common.h" #include "process.h" +#include + const static char* BACKTRACE_FORMAT = "%1!%2() [%3 @ %4] at 0x%5"; // module.dll!KClass::function() [c:\file.cpp @ 10] at 0x0001000 const static char* DEFAULT_MODULE = "[unknown]"; const static char* DEFAULT_FUNC = "[unknown]"; const static char* DEFAULT_FILE = "[unknown]"; const static int DEFAULT_LINE = -1; /** * \brief Base generator class * * This class gives the definition of a backtrace generator. There are 2 subclasses: one for * MSVC and one for MinGW. The reason why implementation differs is the fact that executables * use different debugging formats: PDBs (for MSVC) and Dwarf-2/Stabs and possibly more I don't * know too much about for MinGW, which are embedded in the executable itself. */ class AbstractBTGenerator : public QObject { Q_OBJECT protected: /// A Process instance, corresponding to the process for which we generate the backtrace Process m_process; /// The current stack frame. It is kept as a member STACKFRAME64 m_currentFrame; /// The definition of a map of symbols typedef QMap TSymbolsMap; /// A map of symbols (the full path to the module, and a bool which specifies if /// symbols were loaded for it) TSymbolsMap m_symbolsMap; public: /// Constructor AbstractBTGenerator(const Process& process); virtual ~AbstractBTGenerator(); /// Abstract virtual: Initialize this generator virtual bool Init() = 0; /// Abstract virtual: Uninitialize this generator virtual void UnInit() = 0; /// Start generating the backtrace virtual void Run(HANDLE hTread, bool bFaultingThread); /// This method acts like a callback and will be called when the stack frame changed virtual void FrameChanged() = 0; /// Abstract virtual: get current module name /// @return the name of the current module (eg: kdecore.dll) virtual QString GetModuleName(); /// Abstract virtual: get current module path /// @return the full path to the current module (eg: N:\\kde\\bin\\kdecore.dll) virtual QString GetModulePath(); /// Abstract virtual: get current function/method name. The name is undecorated internally by this method. /// @return the current function or method (eg: KCmdLineArgs::args) virtual QString GetFunctionName() = 0; /// Abstract virtual: get the full path to the current file /// @return the path to the file (eg: N:\\kde\\svn\\KDE\\trunk\\kdelibs\\kcmdlineargs\\kcmdlineargs.cpp) virtual QString GetFile() = 0; /// Abstract virtual: get current line /// @return the current line in the file virtual int GetLine() = 0; /// Checks if symbols are loaded for the specified module /// @return true if symbols are loaded virtual bool IsSymbolLoaded(const QString& module); /// Tries to load symbols for all loaded modules virtual void LoadSymbols(); /// Tries to load a symbol file for a module loaded at dwBaseAddr virtual void LoadSymbol(const QString& module, DWORD64 dwBaseAddr) = 0; signals: /// This will be emitted whenever the generator wishes to output information. It can either be /// module information (in the form: "Loaded C:\path\to.dll (symbols loaded)", a stack frame line, /// or newlines void DebugLine(const QString&); /// This signal is emitted when a module is loaded, and its symbols are missing. This is /// caught by the PackageSuggester void MissingSymbol(const QString&); /// Will be emitted when the generation finishes void Finished(); }; diff --git a/src/kdbgwin/common.h b/src/kdbgwin/common.h index 6c5a5cb0..cd79c4c9 100644 --- a/src/kdbgwin/common.h +++ b/src/kdbgwin/common.h @@ -1,73 +1,75 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #pragma once // the compiler only provides UNICODE. tchar.h checks for the _UNICODE macro -#if defined(UNICODE) +#if defined(MSC_VER) && defined(UNICODE) #define _UNICODE #endif // first: windows & compiler includes -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include "drkonqi_debug.h" // second: Qt includes +#include #include -#include +#include // third: KDE includes // common defines #define SafeCloseHandle(h) \ CloseHandle(h); \ h = NULL; #define ArrayCount(x) (sizeof(x) / sizeof(x[0])) // Documentation /** \mainpage KDbgWin KDbgWin (KDE Debugger for Windows) is a helper application for DrKonqi. Because KDE-Windows supports 2 compilers (MSVC and MinGW), and there is no debugger that supports them both, a simple debugger was needed to make DrKonqi able to generate backtraces - Windows only. MSVC generates .pdb files for its binaries, and GNU GCC embeds debugging information in executables. However, with MinGW, debugging information can be stripped into external files and then loaded on demand. So the only difference between the two is how symbols are handled. DbgHelp and LibBfd were used for manipulating and getting the required information from each debugging format. */ diff --git a/src/kdbgwin/main.cpp b/src/kdbgwin/main.cpp index 727718cf..840dbcae 100644 --- a/src/kdbgwin/main.cpp +++ b/src/kdbgwin/main.cpp @@ -1,73 +1,75 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #include "msvc_generator.h" #include "mingw_generator.h" #include "outputters.h" #include "process.h" +#include "common.h" + #include int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); - QCoreApplication::setApplicationName("kdbgwin"); + QCoreApplication::setApplicationName(QStringLiteral("kdbgwin")); if (argc != 3) { qCCritical(DRKONQI_LOG) << "Parameters are incorrect"; return -1; } if (!Process::EnableDebugPrivilege()) { qCCritical(DRKONQI_LOG) << "Cannot enable debug privilege, exiting"; return -1; } // ok, argv[1] is the pid of the failing process, // and argv[2] the current thread id - let's get the info we need Process proc; if (!proc.GetInfo(argv[1], argv[2])) { qCCritical(DRKONQI_LOG) << "Cannot attach to process, exiting"; return -1; } #if defined(Q_CC_MSVC) MsvcGenerator generator(proc); #elif defined(Q_CC_GNU) MingwGenerator generator(proc); #endif Outputter outputter; QObject::connect(&generator, &MingwGenerator::DebugLine, &outputter, &Outputter::OnDebugLine); TThreadsMap::const_iterator it; for (it = proc.GetThreads().constBegin(); it != proc.GetThreads().constEnd(); it++) { generator.Run(it.value(), (it.key() == proc.GetThreadId())? true : false); } } diff --git a/src/kdbgwin/mingw_generator.cpp b/src/kdbgwin/mingw_generator.cpp index 34752268..1133c9e8 100644 --- a/src/kdbgwin/mingw_generator.cpp +++ b/src/kdbgwin/mingw_generator.cpp @@ -1,168 +1,168 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #include "mingw_generator.h" #include #include MingwGenerator::MingwGenerator(const Process& process) : AbstractBTGenerator(process), file(NULL), func(NULL), line(0) {} struct MyBFD { QString module; bfd* abfd; asymbol** syms; MyBFD() : abfd(0), syms(0) {} MyBFD(const QString& module, bfd* abfd, asymbol** syms) { this->module = module; this->abfd = abfd; this->syms = syms; } bool operator==(const MyBFD& other) { return module == other.module; } }; typedef QList TBFDList; TBFDList bfds; asection* text = NULL; bool MingwGenerator::Init() { bfd_init(); return true; } void MingwGenerator::UnInit() { } void MingwGenerator::FrameChanged() { QString modPath = GetModulePath(); bool existsSymbol = false; TSymbolsMap::const_iterator i = m_symbolsMap.find(modPath); if (i == m_symbolsMap.end()) { return; } MyBFD dummy(modPath, NULL, NULL); int pos = bfds.indexOf(dummy); if (pos == -1) { return; } MyBFD bfd = bfds[pos]; text = bfd_get_section_by_name(bfd.abfd, ".text"); long offset = m_currentFrame.AddrPC.Offset - text->vma; file = DEFAULT_FILE; func = DEFAULT_FUNC; line = DEFAULT_LINE; if (offset > 0) { bfd_find_nearest_line(bfd.abfd, text, bfd.syms, offset, &file, &func, (unsigned int*) &line); } } QString MingwGenerator::GetFunctionName() { if (func != NULL) { char* realname = abi::__cxa_demangle(func, NULL, NULL, NULL); if (realname != NULL) { QString strReturn = QString::fromLatin1(realname); free(realname); return strReturn; } else { return QString::fromLatin1(func); } } return QString::fromLatin1(DEFAULT_FUNC); } QString MingwGenerator::GetFile() { if (file != NULL) { return QString::fromLatin1(file); } return QString::fromLatin1(DEFAULT_FILE); } int MingwGenerator::GetLine() { if (line > 0) { return line; } return -1; } void MingwGenerator::LoadSymbol(const QString& module, DWORD64 dwBaseAddr) { QString symbolFile = module; symbolFile.truncate(symbolFile.length() - 4); - symbolFile.append(".sym"); + symbolFile.append(QStringLiteral(".sym")); m_symbolsMap[module] = false; // default QString symbolType; do { bfd* abfd = bfd_openr(symbolFile.toLatin1(), NULL); if (abfd == NULL) { symbolType = QString::fromLatin1("no symbols loaded"); break; } bfd_check_format(abfd, bfd_object); unsigned storage_needed = bfd_get_symtab_upper_bound(abfd); assert(storage_needed > 4); if (storage_needed <= 4) { // i don't know why the minimum value for this var is 4... symbolType = QString::fromLatin1("no symbols loaded"); break; } asymbol** syms = (asymbol **) malloc(storage_needed); assert(syms); if (syms == NULL) { symbolType = QString::fromLatin1("no symbols loaded"); break; } symbolType = QString::fromLatin1("symbols loaded"); m_symbolsMap[module] = true; bfds.push_back(MyBFD(module, abfd, syms)); } while (0); QString strOutput = QString::fromLatin1("Loaded %1 (%2)") .arg(module).arg(symbolType); emit DebugLine(strOutput); } diff --git a/src/kdbgwin/process.h b/src/kdbgwin/process.h index ff5d1572..77102283 100644 --- a/src/kdbgwin/process.h +++ b/src/kdbgwin/process.h @@ -1,107 +1,110 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #pragma once #include "common.h" +#include +#include + typedef QMap TThreadsMap; typedef QMap TModulesMap; /** * \brief Describes a process. * * This is a helper class when dealing with another process. When kdbgwin starts, * it attaches to the crashing process and tries to retrieve useful information: * pid, threads, modules, image path. These will be used later. */ class Process { private: /// Flag to check if the information about this process is valid and can be used BOOL m_bValid; /// Process ID DWORD m_dwPid; /// Failing thread ID - I need this because for the crashing thread, I need to get /// the CONTEXT from a piece of shared memory in KCrash DWORD m_dwThread; /// A handle to the process HANDLE m_hProcess; /// A QMap which associates thread IDs with opened handles for each /// of them TThreadsMap m_threads; /// The full path to the executable file which started this process QString m_path; /// A QMap which contains the paths to the loaded modules and /// handles to each of them TModulesMap m_modules; public: Process(); public: /// kdbgwin needs to enable the debug privilege in order to read from /// another process's memory. static BOOL EnableDebugPrivilege(); public: /// Attaches to the process and gets all required information /// @return TRUE if operation succeeds BOOL GetInfo(const char* pid, const char* threadId); /// Checks if the information is valid BOOL IsValid() const { assert(m_bValid); return m_bValid; } /// Get the process ID DWORD GetId() const { assert(m_dwPid); return m_dwPid; } /// Returns an open handle to the process (opened with PROCESS_ALL_ACCESS) HANDLE GetHandle() const { assert(m_hProcess); return m_hProcess; } /// Returns the thread ID of the thread that caused the exception DWORD GetThreadId() const { assert(m_dwThread); return m_dwThread; } /// Returns the threads map const TThreadsMap& GetThreads() const { return m_threads; } /// Returns the full path to the executable on the disk const QString& GetPath() const { return m_path; } /// Returns a map of all the loaded modules const TModulesMap& GetModules() const { return m_modules; } };