Skip to content

Commit

Permalink
Add QgsCrashHandler for single place for all platforms (#4395)
Browse files Browse the repository at this point in the history
Add QgsCrashHandler for single place for all platforms
  • Loading branch information
NathanW2 committed Apr 23, 2017
1 parent d36e47c commit bfcf252
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 133 deletions.
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -181,6 +181,7 @@ SET(QGIS_APP_SRCS
qgssettingstree.cpp
qgsvariantdelegate.cpp
qgscrashdialog.cpp
qgscrashhandler.cpp
)

SET (QGIS_APP_MOC_HDRS
Expand Down
3 changes: 2 additions & 1 deletion src/app/main.cpp
Expand Up @@ -96,6 +96,7 @@ typedef SInt32 SRefCon;
#include "qgsmapthemes.h"
#include "qgsvectorlayer.h"
#include "qgis_app.h"
#include "qgscrashhandler.h"

/** Print usage text
*/
Expand Down Expand Up @@ -468,7 +469,7 @@ int main( int argc, char *argv[] )
#endif

#ifdef Q_OS_WIN
SetUnhandledExceptionFilter( QgisApp::qgisCrashDump );
SetUnhandledExceptionFilter( QgsCrashHandler::handle );
#endif

// initialize random number seed
Expand Down
115 changes: 3 additions & 112 deletions src/app/qgisapp.cpp
Expand Up @@ -111,8 +111,8 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
// QGIS Specific Includes
//

#include "qgscrashdialog.h"
#include "qgscrashreport.h"
#include "qgscrashhandler.h"

#include "qgisapp.h"
#include "qgisappinterface.h"
#include "qgisappstylesheet.h"
Expand Down Expand Up @@ -11753,7 +11753,7 @@ void QgisApp::keyPressEvent( QKeyEvent *e )
#if defined(Q_OS_WIN) && defined(QGISDEBUG)
else if ( e->key() == Qt::Key_Backslash && e->modifiers() & Qt::ControlModifier )
{
qgisCrashDump( 0 );
QgsCrashHandler::handle( 0 );
}
#endif
else
Expand Down Expand Up @@ -12582,112 +12582,3 @@ void QgisApp::transactionGroupCommitError( const QString &error )
{
displayMessage( tr( "Transaction" ), error, QgsMessageBar::CRITICAL );
}

#ifdef Q_OS_WIN
LONG WINAPI QgisApp::qgisCrashDump( struct _EXCEPTION_POINTERS *ExceptionInfo )
{
HANDLE process = GetCurrentProcess();
// TOOD Pull symbols from symbol server.
// TOOD Move this logic to generic stack trace class to handle each
// platform.
SymInitialize( process, NULL, TRUE );

// StackWalk64() may modify context record passed to it, so we will
// use a copy.
CONTEXT context_record = *ExceptionInfo->ContextRecord;
// Initialize stack walking.
STACKFRAME64 stack_frame;
memset( &stack_frame, 0, sizeof( stack_frame ) );
#if defined(_WIN64)
int machine_type = IMAGE_FILE_MACHINE_AMD64;
stack_frame.AddrPC.Offset = context_record.Rip;
stack_frame.AddrFrame.Offset = context_record.Rbp;
stack_frame.AddrStack.Offset = context_record.Rsp;
#else
int machine_type = IMAGE_FILE_MACHINE_I386;
stack_frame.AddrPC.Offset = context_record.Eip;
stack_frame.AddrFrame.Offset = context_record.Ebp;
stack_frame.AddrStack.Offset = context_record.Esp;
#endif
stack_frame.AddrPC.Mode = AddrModeFlat;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrStack.Mode = AddrModeFlat;

SYMBOL_INFO *symbol = ( SYMBOL_INFO * ) qgsMalloc( sizeof( SYMBOL_INFO ) + MAX_SYM_NAME );
symbol->SizeOfStruct = sizeof( SYMBOL_INFO );
symbol->MaxNameLen = MAX_SYM_NAME;

IMAGEHLP_LINE *line = ( IMAGEHLP_LINE * ) qgsMalloc( sizeof( IMAGEHLP_LINE ) );
line->SizeOfStruct = sizeof( IMAGEHLP_LINE );

IMAGEHLP_MODULE *module = ( IMAGEHLP_MODULE * ) qgsMalloc( sizeof( IMAGEHLP_MODULE ) );
module->SizeOfStruct = sizeof( IMAGEHLP_MODULE );

QList<QgsCrashReport::StackLine> stack;
while ( StackWalk64( machine_type,
GetCurrentProcess(),
GetCurrentThread(),
&stack_frame,
&context_record,
NULL,
&SymFunctionTableAccess64,
&SymGetModuleBase64,
NULL ) )
{

DWORD64 displacement = 0;

if ( SymFromAddr( process, ( DWORD64 )stack_frame.AddrPC.Offset, &displacement, symbol ) )
{
DWORD dwDisplacement;
QString fileName;
QString lineNumber;
QString moduleName;
if ( SymGetLineFromAddr( process, ( DWORD )( stack_frame.AddrPC.Offset ), &dwDisplacement, line ) )
{
fileName = QString( line->FileName );
lineNumber = QString::number( line->LineNumber );
}
else
{
fileName = "(unknown file)";
lineNumber = "(unknown line)";
}
if ( SymGetModuleInfo( process, ( DWORD )( stack_frame.AddrPC.Offset ), module ) )
{
moduleName = QString( module->ModuleName );
}
else
{
moduleName = "(unknown module)";
}
QgsCrashReport::StackLine stackline;
stackline.ModuleName = moduleName;
stackline.FileName = fileName;
stackline.LineNumber = lineNumber;
stackline.SymbolName = QString( symbol->Name );
stack.append( stackline );
}
}

qgsFree( symbol );
qgsFree( line );
qgsFree( module );

QgsCrashDialog dlg( QApplication::activeWindow() );
QgsCrashReport report;
report.setStackTrace( stack );
dlg.setBugReport( report.toString() );
if ( dlg.exec() )
{
QStringList arguments;
arguments = QCoreApplication::arguments();
QString path = arguments.at( 0 );
arguments.removeFirst();
arguments << QgsProject::instance()->fileName();
QProcess::startDetached( path, arguments, QDir::toNativeSeparators( QCoreApplication::applicationDirPath() ) );
}

return EXCEPTION_EXECUTE_HANDLER;
}
#endif
4 changes: 0 additions & 4 deletions src/app/qgisapp.h
Expand Up @@ -593,10 +593,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! \since QGIS 2.1
static QString normalizedMenuName( const QString &name ) { return name.normalized( QString::NormalizationForm_KD ).remove( QRegExp( "[^a-zA-Z]" ) ); }

#ifdef Q_OS_WIN
static LONG WINAPI qgisCrashDump( struct _EXCEPTION_POINTERS *ExceptionInfo );
#endif

void parseVersionInfo( QNetworkReply *reply, int &latestVersion, QStringList &versionInfo );

//! Register a new tab in the layer properties dialog
Expand Down
144 changes: 144 additions & 0 deletions src/app/qgscrashhandler.cpp
@@ -0,0 +1,144 @@
/***************************************************************************
qgscrashhandler.cpp - QgsCrashHandler
---------------------
begin : 23.4.2017
copyright : (C) 2017 by Nathan Woodrow
email : woodrow.nathan@gmail.com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "qgscrashhandler.h"

#include <QProcess>
#include <QDir>

#include "qgsproject.h"
#include "qgscrashdialog.h"
#include "qgscrashreport.h"

#ifdef Q_OS_WIN
LONG WINAPI QgsCrashHandler::handle( struct _EXCEPTION_POINTERS *ExceptionInfo )
{
HANDLE process = GetCurrentProcess();
// TOOD Pull symbols from symbol server.
// TOOD Move this logic to generic stack trace class to handle each
// platform.
SymInitialize( process, NULL, TRUE );

// StackWalk64() may modify context record passed to it, so we will
// use a copy.
CONTEXT context_record = *ExceptionInfo->ContextRecord;
// Initialize stack walking.
STACKFRAME64 stack_frame;
memset( &stack_frame, 0, sizeof( stack_frame ) );
#if defined(_WIN64)
int machine_type = IMAGE_FILE_MACHINE_AMD64;
stack_frame.AddrPC.Offset = context_record.Rip;
stack_frame.AddrFrame.Offset = context_record.Rbp;
stack_frame.AddrStack.Offset = context_record.Rsp;
#else
int machine_type = IMAGE_FILE_MACHINE_I386;
stack_frame.AddrPC.Offset = context_record.Eip;
stack_frame.AddrFrame.Offset = context_record.Ebp;
stack_frame.AddrStack.Offset = context_record.Esp;
#endif
stack_frame.AddrPC.Mode = AddrModeFlat;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrStack.Mode = AddrModeFlat;

SYMBOL_INFO *symbol = ( SYMBOL_INFO * ) qgsMalloc( sizeof( SYMBOL_INFO ) + MAX_SYM_NAME );
symbol->SizeOfStruct = sizeof( SYMBOL_INFO );
symbol->MaxNameLen = MAX_SYM_NAME;

IMAGEHLP_LINE *line = ( IMAGEHLP_LINE * ) qgsMalloc( sizeof( IMAGEHLP_LINE ) );
line->SizeOfStruct = sizeof( IMAGEHLP_LINE );

IMAGEHLP_MODULE *module = ( IMAGEHLP_MODULE * ) qgsMalloc( sizeof( IMAGEHLP_MODULE ) );
module->SizeOfStruct = sizeof( IMAGEHLP_MODULE );

QList<QgsCrashReport::StackLine> stack;
while ( StackWalk64( machine_type,
GetCurrentProcess(),
GetCurrentThread(),
&stack_frame,
&context_record,
NULL,
&SymFunctionTableAccess64,
&SymGetModuleBase64,
NULL ) )
{

DWORD64 displacement = 0;

if ( SymFromAddr( process, ( DWORD64 )stack_frame.AddrPC.Offset, &displacement, symbol ) )
{
DWORD dwDisplacement;
QString fileName;
QString lineNumber;
QString moduleName;
if ( SymGetLineFromAddr( process, ( DWORD )( stack_frame.AddrPC.Offset ), &dwDisplacement, line ) )
{
fileName = QString( line->FileName );
lineNumber = QString::number( line->LineNumber );
}
else
{
fileName = "(unknown file)";
lineNumber = "(unknown line)";
}
if ( SymGetModuleInfo( process, ( DWORD )( stack_frame.AddrPC.Offset ), module ) )
{
moduleName = QString( module->ModuleName );
}
else
{
moduleName = "(unknown module)";
}
QgsCrashReport::StackLine stackline;
stackline.moduleName = moduleName;
stackline.fileName = fileName;
stackline.lineNumber = lineNumber;
stackline.symbolName = QString( symbol->Name );
stack.append( stackline );
}
}

qgsFree( symbol );
qgsFree( line );
qgsFree( module );

showCrashDialog( stack );

return EXCEPTION_EXECUTE_HANDLER;
}

void QgsCrashHandler::showCrashDialog( const QList<QgsCrashReport::StackLine> &stack )
{

QgsCrashDialog dlg( QApplication::activeWindow() );
QgsCrashReport report;
report.setStackTrace( stack );
dlg.setBugReport( report.toString() );
if ( dlg.exec() )
{
restartApplication();
}
}

void QgsCrashHandler::restartApplication()
{
QStringList arguments;
arguments = QCoreApplication::arguments();
QString path = arguments.at( 0 );
arguments.removeFirst();
arguments << QgsProject::instance()->fileName();
QProcess::startDetached( path, arguments, QDir::toNativeSeparators( QCoreApplication::applicationDirPath() ) );

}
#endif
61 changes: 61 additions & 0 deletions src/app/qgscrashhandler.h
@@ -0,0 +1,61 @@
/***************************************************************************
qgscrashhandler.h - QgsCrashHandler
---------------------
begin : 23.4.2017
copyright : (C) 2017 by Nathan Woodrow
email : woodrow.nathan@gmail.com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSCRASHHANDLER_H
#define QGSCRASHHANDLER_H

#include "qgis.h"
#include "qgis_app.h"
#include "qgscrashreport.h"

#ifdef WIN32
#include <windows.h>
#include <dbghelp.h>
#endif

/**
* Utility object to handle crashes in QGIS.
*/
class APP_EXPORT QgsCrashHandler
{

public:
#ifdef Q_OS_WIN
static LONG WINAPI handle( struct _EXCEPTION_POINTERS *ExceptionInfo );
#endif

/**
* Show the crash dialog.
* @param stack The current stack of the crash point.
*/
static void showCrashDialog( const QList<QgsCrashReport::StackLine> &stack );

/**
* Restart the application.
* Restores project and arguments used when application was loaded.
*/
static void restartApplication();

private:

/**
* This class doesn't need to be created by anyone as is only used to handle
* crashes in the application.
*/
QgsCrashHandler() {}
};


#endif // QGSCRASHHANDLER_H

0 comments on commit bfcf252

Please sign in to comment.