Add a crash handler using Breakpad with libcrashreporter-qt

This commit is contained in:
Dmitri Ovodok 2019-02-15 15:26:32 +03:00
parent 49ca1b9026
commit 536e27efe9
11 changed files with 424 additions and 15 deletions

View file

@ -149,6 +149,8 @@ option(BUILD_FOR_WINSTORE "Build for the Windows Store." OFF)
option(COVERAGE "Build with instrumentation to record code coverage." OFF)
option(BUILD_64 "Build 64 bit version of editor" ON)
option(BUILD_AUTOUPDATE "Build with autoupdate support" OFF)
option(BUILD_CRASH_REPORTER "Build with crash reporter" ON)
set(CRASH_REPORT_URL "http://127.0.0.1:1127/post" CACHE STRING "URL where to send crash reports (valid if BUILD_CRASH_REPORTER is set to ON)")
if (APPLE)
set (CMAKE_CXX_COMPILER clang++)
@ -616,14 +618,6 @@ else(USE_SYSTEM_QTSINGLEAPPLICATION)
set(QTSINGLEAPPLICATION_LIBRARIES qtsingleapp)
endif(USE_SYSTEM_QTSINGLEAPPLICATION)
##
## produce config.h file
##
configure_file (
${PROJECT_SOURCE_DIR}/build/config.h.in
${PROJECT_BINARY_DIR}/config.h
)
if (NOT MINGW AND NOT MSVC AND NOT APPLE)
#### PACKAGING for Linux and BSD based systems (more in mscore/CMakeLists.txt) ####
#
@ -750,11 +744,16 @@ endif (NOT MSVC)
## Add subdirs
##
subdirs(
mscore awl bww2mxml share midi audiofile fluid libmscore synthesizer
awl bww2mxml share midi audiofile fluid libmscore synthesizer
effects thirdparty/rtf2html thirdparty/beatroot
thirdparty/qzip thirdparty/kQOAuth
)
if (BUILD_CRASH_REPORTER)
set(ENABLE_CRASH_REPORTER OFF CACHE BOOL "Disable libcrashreporter-qt GUI component")
add_subdirectory(thirdparty/libcrashreporter-qt)
endif (BUILD_CRASH_REPORTER)
string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
if (APPLE AND CMAKE_BUILD_TYPE MATCHES "DEBUG")
# With xcode, we need to have all the targets in the same project
@ -837,6 +836,24 @@ if (OMR)
endif (NOT USE_SYSTEM_POPPLER)
endif (OMR)
add_subdirectory(mscore)
if (BUILD_CRASH_REPORTER)
set (CRASHREPORTER_EXECUTABLE_NAME "${MSCORE_EXECUTABLE_NAME}-crash-reporter")
add_subdirectory(crashreporter)
add_dependencies(mscore mscore-crash-reporter)
set (CRASHREPORTER_EXECUTABLE "${CRASHREPORTER_EXECUTABLE_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
set (MSCORE_EXECUTABLE "${MSCORE_EXECUTABLE_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
endif (BUILD_CRASH_REPORTER)
##
## produce config.h file
##
configure_file (
${PROJECT_SOURCE_DIR}/build/config.h.in
${PROJECT_BINARY_DIR}/config.h
)
##
## Include packaging
##

View file

@ -37,6 +37,10 @@
#cmakedefine HAS_AUDIOFILE
#cmakedefine USE_SSE
#define MSCORE_EXECUTABLE "${MSCORE_EXECUTABLE}"
#cmakedefine BUILD_CRASH_REPORTER
#define CRASHREPORTER_EXECUTABLE "${CRASHREPORTER_EXECUTABLE}"
#define CRASH_REPORT_URL "${CRASH_REPORT_URL}"
#define MUSESCORE_NAME_VERSION "${MUSESCORE_NAME_VERSION}"
#define INSTALL_NAME "${Mscore_INSTALL_NAME}"
#define INSTPREFIX "${CMAKE_INSTALL_PREFIX}"

View file

@ -0,0 +1,41 @@
#=============================================================================
# MuseScore
# Music Composition & Notation
#
# Copyright (C) 2019 Werner Schweer and others
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2.
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#=============================================================================
QT5_WRAP_UI(ui_headers crashreporter.ui)
add_executable(mscore-crash-reporter
${ui_headers}
${_all_h_file}
crashreporter.h
crashreporter.cpp
)
set_target_properties(
mscore-crash-reporter
PROPERTIES
OUTPUT_NAME ${CRASHREPORTER_EXECUTABLE_NAME}
)
target_include_directories(mscore-crash-reporter PRIVATE
${PROJECT_BINARY_DIR} # config.h
)
target_link_libraries(mscore-crash-reporter ${QT_LIBRARIES})
install(TARGETS mscore-crash-reporter RUNTIME DESTINATION bin)

View file

@ -0,0 +1,166 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2019 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "crashreporter.h"
#include "ui_crashreporter.h"
#include "config.h"
#include <QDir>
#include <QFileInfo>
#include <QHttpMultiPart>
#include <QMessageBox>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QProcess>
namespace Ms {
//---------------------------------------------------------
// CrashReporter
//---------------------------------------------------------
CrashReporter::CrashReporter(const QString& miniDumpFile, const QUrl& uploadUrl)
: _ui(new Ui::CrashReporter), _miniDump(miniDumpFile), _uploadUrl(uploadUrl)
{
_ui->setupUi(this);
}
//---------------------------------------------------------
// ~CrashReporter
//---------------------------------------------------------
CrashReporter::~CrashReporter()
{
delete _ui;
}
//---------------------------------------------------------
// CrashReporter::uploadReport
//---------------------------------------------------------
void CrashReporter::uploadReport()
{
if (!_networkManager) {
_networkManager = new QNetworkAccessManager(this);
connect(_networkManager, &QNetworkAccessManager::finished, this, &CrashReporter::onUploadFinished);
}
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart miniDumpPart;
miniDumpPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
const QString filename = QFileInfo(_miniDump).fileName();
const QString contentDisposition(QString("form-data; name=\"upload_file_minidump\"; filename=\"%1\"").arg(filename));
miniDumpPart.setHeader(QNetworkRequest::ContentDispositionHeader, contentDisposition);
QFile* file = new QFile(_miniDump, multiPart);
file->open(QIODevice::ReadOnly);
miniDumpPart.setBodyDevice(file);
multiPart->append(miniDumpPart);
QHttpPart prodPart;
prodPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"prod\""));
prodPart.setBody("MS_EDITOR");
multiPart->append(prodPart);
QHttpPart verPart;
verPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"ver\""));
verPart.setBody(QString("%1.%2").arg(VERSION).arg(BUILD_NUMBER).toLatin1());
multiPart->append(verPart);
QHttpPart commentPart;
commentPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"comments\""));
commentPart.setBody(_ui->commentText->toPlainText().toUtf8());
multiPart->append(commentPart);
QNetworkRequest request(_uploadUrl);
// TODO: custom user-agent?
QNetworkReply* reply = _networkManager->post(request, multiPart);
multiPart->setParent(reply);
}
//---------------------------------------------------------
// CrashReporter::onUploadFinished
//---------------------------------------------------------
void CrashReporter::onUploadFinished(QNetworkReply* reply)
{
bool success = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200;
if (success) {
QMessageBox::information(this, tr("Success!"), tr("Crash report uploaded successfully!"));
if (_ui->restartCheckBox->isChecked()) {
static_assert(sizeof(MSCORE_EXECUTABLE) > 1,
"MSCORE_EXECUTABLE should be defined to make it possible to restart MuseScore"
);
const QDir appDir(qApp->applicationDirPath());
QProcess::startDetached(appDir.filePath(MSCORE_EXECUTABLE));
}
close();
}
else
QMessageBox::warning(this, tr("Error"), tr("Error while uploading crash report:\n%1").arg(reply->errorString()));
reply->deleteLater();
}
//---------------------------------------------------------
// CrashReporter::on_sendReportButton_clicked
//---------------------------------------------------------
void CrashReporter::on_sendReportButton_clicked()
{
uploadReport();
}
//---------------------------------------------------------
// CrashReporter::on_cancelButton_clicked
//---------------------------------------------------------
void CrashReporter::on_cancelButton_clicked()
{
close();
}
} // namespace Ms
//---------------------------------------------------------
// main
//---------------------------------------------------------
int main(int argc, char** argv)
{
if (argc < 2)
qFatal("Usage: %s <dump_file>", argv[0]);
QString miniDump(argv[1]);
if (!QFileInfo(miniDump).exists())
qFatal("minidump file does not exist");
QApplication app(argc, argv);
static_assert(sizeof(CRASH_REPORT_URL) > 1, "Crash report URL must be valid");
const char* crashReportUrl = CRASH_REPORT_URL;
Ms::CrashReporter reporter(miniDump, QUrl(crashReportUrl));
reporter.show();
return app.exec();
}

View file

@ -0,0 +1,61 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2019 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef __CRASHREPORTER_H__
#define __CRASHREPORTER_H__
#include <QNetworkAccessManager>
#include <QWidget>
#include <QUrl>
namespace Ui {
class CrashReporter;
}
namespace Ms {
//---------------------------------------------------------
// CrashReporter
//---------------------------------------------------------
class CrashReporter : public QWidget {
Q_OBJECT
Ui::CrashReporter* _ui;
QString _miniDump;
QUrl _uploadUrl;
QNetworkAccessManager* _networkManager = nullptr;
void uploadReport();
private slots:
void on_sendReportButton_clicked();
void on_cancelButton_clicked();
void onUploadFinished(QNetworkReply*);
public:
CrashReporter(const QString& miniDumpFile, const QUrl& uploadUrl);
CrashReporter(const CrashReporter&) = delete;
CrashReporter& operator=(const CrashReporter&) = delete;
~CrashReporter();
};
} // namespace Ms
#endif

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CrashReporter</class>
<widget class="QWidget" name="CrashReporter">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>MuseScore Crash Reporter</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="text">
<string>Unfortunately MuseScore has crashed. Would you like to send a report to make it possible for us to fix the problem?</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QPlainTextEdit" name="commentText">
<property name="toolTip">
<string>Describe your actions when the crash occurred (optional)</string>
</property>
<property name="accessibleName">
<string>Detailed crash info</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="sendReportButton">
<property name="text">
<string>Send report</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Please describe your actions when the crash occurred (optional):</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>commentText</cstring>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="restartCheckBox">
<property name="toolTip">
<string>Restart MuseScore after this report is sent</string>
</property>
<property name="text">
<string>Restart MuseScore</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>commentText</tabstop>
<tabstop>restartCheckBox</tabstop>
<tabstop>sendReportButton</tabstop>
<tabstop>cancelButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -503,6 +503,10 @@ target_link_libraries(mscore
kqoauth
)
if (BUILD_CRASH_REPORTER)
target_link_libraries(mscore crashreporter-handler)
endif (BUILD_CRASH_REPORTER)
add_dependencies(mscore workspaces)
if (USE_SYSTEM_FREETYPE)
@ -1079,3 +1083,11 @@ endif (NOT MSVC)
target_link_libraries (inspectorplugin ${QT_LIBRARIES})
set_target_properties(inspectorplugin PROPERTIES EXCLUDE_FROM_ALL 1)
if (MSCORE_OUTPUT_NAME)
set (MSCORE_EXECUTABLE_NAME ${MSCORE_OUTPUT_NAME})
else (MSCORE_OUTPUT_NAME)
set (MSCORE_EXECUTABLE_NAME ${ExecutableName})
endif (MSCORE_OUTPUT_NAME)
# Export the executable name to the global context
set (MSCORE_EXECUTABLE_NAME "${MSCORE_EXECUTABLE_NAME}" PARENT_SCOPE)

View file

@ -150,8 +150,16 @@ extern Ms::Synthesizer* createZerberus();
// Q_LOGGING_CATEGORY(undoRedo, "undoRedo")
#endif
#ifdef BUILD_CRASH_REPORTER
#include "thirdparty/libcrashreporter-qt/src/libcrashreporter-handler/Handler.h"
#endif
namespace Ms {
#ifdef BUILD_CRASH_REPORTER
static std::unique_ptr<CrashReporter::Handler> crashHandler;
#endif
MuseScore* mscore;
MasterSynthesizer* synti;
@ -7538,6 +7546,13 @@ int main(int argc, char* av[])
if (settings.value("mixerVisible", false).toBool())
mscore->showMixer(true);
#ifdef BUILD_CRASH_REPORTER
static_assert(sizeof(CRASHREPORTER_EXECUTABLE) > 1,
"CRASHREPORTER_EXECUTABLE should be defined to build with crash reporter"
);
crashHandler.reset(new CrashReporter::Handler(QDir::tempPath(), true, CRASHREPORTER_EXECUTABLE));
#endif
return qApp->exec();
}

View file

@ -121,8 +121,14 @@ IF /I "%1"=="clean" (
@echo on
echo "Building CMake configuration..."
@echo off
REM -DCMAKE_BUILD_NUMBER=%BUILD_NUMBER% -DCMAKE_BUILD_AUTOUPDATE=%BUILD_AUTOUPDATE% are used for CI only
cd %BUILD_FOLDER% & cmake -G %GENERATOR_NAME% -DCMAKE_INSTALL_PREFIX=../%INSTALL_FOLDER% -DCMAKE_BUILD_TYPE=%CONFIGURATION_STR% -DBUILD_FOR_WINSTORE=%BUILD_FOR_WINSTORE% -DBUILD_64=%BUILD_64% -DCMAKE_BUILD_NUMBER=%BUILD_NUMBER% -DBUILD_AUTOUPDATE=%BUILD_AUTOUPDATE% ..
IF "%CRASH_LOG_SERVER_URL%" == "" (
SET CRASH_REPORT_URL_OPT=-DCRASH_REPORT_URL=%CRASH_LOG_SERVER_URL%
)
REM -DCMAKE_BUILD_NUMBER=%BUILD_NUMBER% -DCMAKE_BUILD_AUTOUPDATE=%BUILD_AUTOUPDATE% %CRASH_REPORT_URL_OPT% are used for CI only
cd %BUILD_FOLDER% & cmake -G %GENERATOR_NAME% -DCMAKE_INSTALL_PREFIX=../%INSTALL_FOLDER% -DCMAKE_BUILD_TYPE=%CONFIGURATION_STR% -DBUILD_FOR_WINSTORE=%BUILD_FOR_WINSTORE% -DBUILD_64=%BUILD_64% -DCMAKE_BUILD_NUMBER=%BUILD_NUMBER% -DBUILD_AUTOUPDATE=%BUILD_AUTOUPDATE% %CRASH_REPORT_URL_OPT% ..
@echo on
echo "Running lrelease..."
@echo off

View file

@ -15,4 +15,4 @@ set(TARGET tst_runscripts)
include(${PROJECT_SOURCE_DIR}/mtest/cmake.inc)
add_dependencies(tst_runscripts mscore)
add_definitions(-DMSCORE_EXECUTABLE="$<TARGET_FILE:mscore>")
add_definitions(-DMSCORE_EXECUTABLE_PATH="$<TARGET_FILE:mscore>")

View file

@ -63,9 +63,9 @@ void TestScripts::runTestScripts()
QStringList args({ "--run-test-script" });
args << scripts;
if (!QFileInfo(MSCORE_EXECUTABLE).exists())
qFatal("Cannot find executable: %s", MSCORE_EXECUTABLE);
QVERIFY(QProcess::execute(MSCORE_EXECUTABLE, args) == 0);
if (!QFileInfo(MSCORE_EXECUTABLE_PATH).exists())
qFatal("Cannot find executable: %s", MSCORE_EXECUTABLE_PATH);
QVERIFY(QProcess::execute(MSCORE_EXECUTABLE_PATH, args) == 0);
}
QTEST_MAIN(TestScripts)