From 1cb314d674e057af9d8408f160f2df871cb249c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Tue, 9 Apr 2019 09:44:44 +0200 Subject: [PATCH] Add crash handling system using Breakpad (#1439) --- .appveyor.yml | 3 +- .gitignore | 2 + .travis.yml | 18 +- build.bat | 10 +- build.sh | 13 +- docs/source/building.rst | 42 +- docs/source/crash-handling-system.rst | 32 + docs/source/images/crash-dialog.png | Bin 0 -> 29528 bytes docs/source/images/success-dump-dialog.png | Bin 0 -> 14629 bytes scripts/breakpad_client.gyp | 34 + scripts/breakpad_macos.patch | 1377 ++++++++++++++++++++ scripts/dump_syms.gyp | 17 + scripts/prepare_breakpad.bat | 29 + scripts/prepare_breakpad_linux.sh | 9 + scripts/prepare_breakpad_macos.sh | 22 + src/CMakeLists.txt | 20 + src/Cutter.pro | 36 + src/Main.cpp | 3 + src/cmake/FindBreakpad.cmake | 46 + src/common/BugReporting.cpp | 45 + src/common/BugReporting.h | 10 + src/common/CrashHandler.cpp | 229 ++++ src/common/CrashHandler.h | 13 + src/core/CutterDescriptions.h | 1 + src/core/MainWindow.cpp | 29 +- 25 files changed, 2001 insertions(+), 39 deletions(-) create mode 100644 docs/source/crash-handling-system.rst create mode 100644 docs/source/images/crash-dialog.png create mode 100644 docs/source/images/success-dump-dialog.png create mode 100644 scripts/breakpad_client.gyp create mode 100644 scripts/breakpad_macos.patch create mode 100644 scripts/dump_syms.gyp create mode 100644 scripts/prepare_breakpad.bat create mode 100755 scripts/prepare_breakpad_linux.sh create mode 100755 scripts/prepare_breakpad_macos.sh create mode 100644 src/cmake/FindBreakpad.cmake create mode 100644 src/common/BugReporting.cpp create mode 100644 src/common/BugReporting.h create mode 100644 src/common/CrashHandler.cpp create mode 100644 src/common/CrashHandler.h diff --git a/.appveyor.yml b/.appveyor.yml index a78f2c49..dd08ac72 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -45,10 +45,11 @@ install: before_build: - cmd: git submodule update --init + - scripts\prepare_breakpad.bat # Build config build_script: - - cmd: if defined QMAKE ( call prepare_r2.bat && call build.bat CUTTER_APPVEYOR_R2DEC=true CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true SHIBOKEN_EXECUTABLE="%CUTTER_DEPS_DIR%\pyside\bin\shiboken2.exe" SHIBOKEN_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/shiboken2" SHIBOKEN_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/shiboken2.lib" PYSIDE_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/PySide2" PYSIDE_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/pyside2.lib" PYSIDE_TYPESYSTEMS="%CUTTER_DEPS_DIR%/pyside/share/PySide2/typesystems") + - cmd: if defined QMAKE ( call prepare_r2.bat && call build.bat CUTTER_APPVEYOR_R2DEC=true CUTTER_ENABLE_CRASH_REPORTS=true CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true SHIBOKEN_EXECUTABLE="%CUTTER_DEPS_DIR%\pyside\bin\shiboken2.exe" SHIBOKEN_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/shiboken2" SHIBOKEN_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/shiboken2.lib" PYSIDE_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/PySide2" PYSIDE_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/pyside2.lib" PYSIDE_TYPESYSTEMS="%CUTTER_DEPS_DIR%/pyside/share/PySide2/typesystems") - cmd: if defined MESON ( python meson.py --release --dist=%ARTIFACT_PATH% --backend=%BACKEND% --python ) after_build: diff --git a/.gitignore b/.gitignore index 96c3ef29..6a515e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ compile_commands.json # cutter-deps /cutter-deps + +/breakpad diff --git a/.travis.yml b/.travis.yml index b824e936..52ccd8e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,8 +68,14 @@ addons: install: - scripts/fetch_deps.sh - source cutter-deps/env.sh - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=/usr/local/opt/llvm/bin:$PATH; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export LD_LIBRARY_PATH="`llvm-config --libdir`:$LD_LIBRARY_PATH"; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + export PATH=/usr/local/opt/llvm/bin:$PATH; + source scripts/prepare_breakpad_macos.sh; + fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + export LD_LIBRARY_PATH="`llvm-config --libdir`:$LD_LIBRARY_PATH"; + source scripts/prepare_breakpad_linux.sh; + fi before_script: - git submodule init ; git submodule update @@ -84,11 +90,13 @@ before_script: script: - mkdir build - cd build + - export PKG_CONFIG_PATH="$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig:$CUSTOM_PYTHON_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH" - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then if [[ "$BUILD_SYSTEM" == "qmake" ]]; then qmake CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true + CUTTER_ENABLE_CRASH_REPORTS=true PREFIX=/usr APPIMAGE=1 ../src && @@ -101,6 +109,7 @@ script: -DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" -DCUTTER_ENABLE_PYTHON=ON -DCUTTER_ENABLE_PYTHON_BINDINGS=ON + -DCUTTER_ENABLE_CRASH_REPORTS=ON ../src && make -j4; fi @@ -110,7 +119,9 @@ script: CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true CUTTER_BUNDLE_R2_APPBUNDLE=true + CUTTER_ENABLE_CRASH_REPORTS=true PYTHON_FRAMEWORK_DIR=$CUTTER_DEPS_PYTHON_FRAMEWORK_DIR + BREAKPAD_FRAMEWORK_DIR=$BREAKPAD_FRAMEWORK_DIR ../src && make -j4; elif [[ "$BUILD_SYSTEM" == "cmake" ]]; then @@ -121,6 +132,8 @@ script: -DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3" -DCUTTER_ENABLE_PYTHON=ON -DCUTTER_ENABLE_PYTHON_BINDINGS=ON + -DCUTTER_ENABLE_CRASH_REPORTS=ON + -DBREAKPAD_FRAMEWORK_DIR="$BREAKPAD_FRAMEWORK_DIR" ../src && make -j4; fi @@ -135,6 +148,7 @@ after_success: macdeployqt Cutter.app -executable=Cutter.app/Contents/MacOS/Cutter -libpath="../Frameworks" && macdeployqt Cutter.app -executable=Cutter.app/Contents/MacOS/Cutter -libpath="../Frameworks" && cp -a "$QTDIR/lib/QtDBus.framework" "$QTDIR/lib/QtPrintSupport.framework" Cutter.app/Contents/Frameworks && + cp -a "$BREAKPAD_FRAMEWORK_DIR/Breakpad.framework" Cutter.app/Contents/Frameworks && "$TRAVIS_BUILD_DIR/scripts/appbundle_embed_python.sh" "$CUTTER_DEPS_PYTHON_FRAMEWORK_DIR/Python.framework" Cutter.app Cutter.app/Contents/MacOS/Cutter && mv Cutter.app/Contents/MacOS/Cutter Cutter.app/Contents/MacOS/Cutter.bin && cp ../src/macos/Cutter.sh Cutter.app/Contents/MacOS/Cutter && diff --git a/build.bat b/build.bat index 90ab71ed..d76e9e5e 100644 --- a/build.bat +++ b/build.bat @@ -1,5 +1,6 @@ @ECHO off SETLOCAL ENABLEDELAYEDEXPANSION +SETLOCAL ENABLEEXTENSIONS IF "%VisualStudioVersion%" == "14.0" ( IF NOT DEFINED Platform SET "Platform=X86" ) FOR /F %%i IN ('powershell -c "\"%Platform%\".toLower()"') DO SET PLATFORM=%%i @@ -11,6 +12,7 @@ IF !ERRORLEVEL! NEQ 0 ( SET "R2DIST=r2_dist_%PLATFORM%" SET "BUILDDIR=build_%PLATFORM%" +SET "BREAKPAD_SOURCE_DIR=%CD%\src\breakpad\src\src" ECHO Preparing directory RMDIR /S /Q %BUILDDIR% @@ -21,8 +23,12 @@ FOR %%i in (src\translations\*.ts) DO lrelease %%i CD %BUILDDIR% +IF NOT DEFINED CUTTER_ENABLE_CRASH_REPORTS ( +SET "CUTTER_ENABLE_CRASH_REPORTS=false" +) + ECHO Building cutter -qmake %* ..\src\cutter.pro -config release +qmake BREAKPAD_SOURCE_DIR=%BREAKPAD_SOURCE_DIR% CUTTER_ENABLE_CRASH_REPORTS=%CUTTER_ENABLE_CRASH_REPORTS% %* ..\src\cutter.pro -config release IF !ERRORLEVEL! NEQ 0 EXIT /B 1 nmake IF !ERRORLEVEL! NEQ 0 EXIT /B 1 @@ -34,3 +40,5 @@ XCOPY /S /I ..\%R2DIST%\radare2 cutter\radare2 COPY ..\%R2DIST%\*.dll cutter\ windeployqt cutter\cutter.exe FOR %%i in (..\src\translations\*.qm) DO MOVE "%%~fi" cutter\translations + +ENDLOCAL diff --git a/build.sh b/build.sh index d65b9414..5e8c3f59 100755 --- a/build.sh +++ b/build.sh @@ -6,7 +6,7 @@ ERR=0 #### User variables #### -BUILD="build" +BUILD="`pwd`/build" QMAKE_CONF="" ROOT_DIR=`pwd` @@ -63,6 +63,16 @@ find_gmake() { echo "$gmakepath" } +prepare_breakpad() { + if [[ $OSTYPE == "linux-gnu" ]]; then + source $ROOT_DIR/scripts/prepare_breakpad_linux.sh + export PKG_CONFIG_PATH="$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH" + elif [[ $OSTYPE == "darwin" ]]; then + source $ROOT_DIR/scripts/prepare_breakpad_macos.sh + fi + return 1 +} + # Build radare2 check_r2 if [ $? -eq 1 ]; then @@ -86,6 +96,7 @@ fi $(find_lrelease) ./src/Cutter.pro # Build +prepare_breakpad mkdir -p "$BUILD" cd "$BUILD" || exit 1 $(find_qmake) ../src/Cutter.pro $QMAKE_CONF diff --git a/docs/source/building.rst b/docs/source/building.rst index 5c1ef9c2..02bc2afb 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -11,9 +11,11 @@ The “official” way to build Cutter is by using qmake, but there are two alternatives – cmake and meson. In any case, there are obviously some requirements: -- Radare2 installed from submodule -- Qt 5.9 or above -- Python3.6 + +* Radare2 installed from submodule +* Qt 5.9 or above +* Python3.6 +* Breakpad installed using script (optional, disabled by default) Before compiling, note that we also provide binaries available for windows/linux/MacOS `here `_. @@ -23,9 +25,33 @@ windows/linux/MacOS `here `_. Building options ---------------- -Note that there are two major building options available: -- ``CUTTER_ENABLE_PYTHON`` compile with Python support -- ``CUTTER_ENABLE_PYTHON_BINDINGS`` automatically generate Python Bindings with Shiboken2, required for Python plugins! +Note that there are three major building options available: + +* ``CUTTER_ENABLE_PYTHON`` compile with Python support +* ``CUTTER_ENABLE_PYTHON_BINDINGS`` automatically generate Python Bindings with Shiboken2, required for Python plugins! +* ``CUTTER_ENABLE_CRASH_REPORTS`` is used to compile Cutter with crash handling system enabled (Breakpad) + +-------------- + +Preparing Breakpad +------------------- + +If you want to build Cutter with crash handling system, you want prepare Breakpad before. +For this simply run one of the scripts (according to your OS) from root Cutter directory: + +.. code:: sh + + source scripts/prepare_breakpad_linux.sh # Linux + source scripts/prepare_breakpad_macos.sh # MacOS + scripts/prepare_breakpad.bat # Windows + +Then if you are building on Linux you want to change ``PKG_CONFIG_PATH`` environment variable +so it contains ``$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig``. For this simply run + +.. code:: sh + + export PKG_CONFIG_PATH="$CUSTOM_BREAKPAD_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH" + -------------- @@ -73,12 +99,12 @@ Building on Linux The root for CMake is in src/. In-source builds are **not allowed**, so you **must** run CMake from a separate directory: -:: +.. code:: sh cd src mkdir build cd build - cmake .. + cmake .. # Don't forget to provide build options If all went well, you should now have a working Makefile in your build directory: diff --git a/docs/source/crash-handling-system.rst b/docs/source/crash-handling-system.rst new file mode 100644 index 00000000..c6e1da3f --- /dev/null +++ b/docs/source/crash-handling-system.rst @@ -0,0 +1,32 @@ +Crash Handling System +===================== + +Cutter uses `Breakpad `__ as backend +for crash handling. + +Crash Handling System is disabled by default to do not interfere developers from debugging. +To enable this system there is building option ``CUTTER_ENABLE_CRASH_REPORTS``. + +Solution description +-------------------- + +There are only 2 source files: + +* ``CrashHandler.h`` +* ``CrashHandler.cpp`` + +And API is very simple: only one function - ``initCrashHandler()`` that enables Crash Handling System if +``CUTTER_ENABLE_CRASH_REPORTS`` is true, otherwise does nothing. + +As soon as signal is raised ``crashHandler(int signum)`` is called with signal's code as argument. +This function first of all writes crash dump to OS's Temp directory to catch core and memory state +as it was at the crash moment. + +Then crash dialog is shown: + +.. image :: images/crash-dialog.png + +If user chose to create crash dump, prepared dump is moved to directory specified by user. +And then success dialog is shown: + +.. image :: images/success-dump-dialog.png diff --git a/docs/source/images/crash-dialog.png b/docs/source/images/crash-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e50eb26a75f75b0d771b6a2a47487c90107950 GIT binary patch literal 29528 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

m53y>Rp=rgCTRm$}3(R5^&Rw^_e>IIHQ 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 DaC6T? literal 0 HcmV?d00001 diff --git a/docs/source/images/success-dump-dialog.png b/docs/source/images/success-dump-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..94144c85718a97814131f5b28a4b1ba996daabd5 GIT binary patch literal 14629 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 +Date: Thu, 15 Nov 2018 13:56:01 -0500 +Subject: [PATCH] Update Breakpad.xib minimum target to OSX10.11 + +Bug: https://bugs.chromium.org/p/google-breakpad/issues/detail?id=778 +Change-Id: I32a36e47d0aab92e5ac40e7fbaa3c5caaaff2318 +--- + +diff --git a/src/client/mac/sender/Breakpad.xib b/src/client/mac/sender/Breakpad.xib +index 7966f89..1ecd27e 100644 +--- a/src/client/mac/sender/Breakpad.xib ++++ b/src/client/mac/sender/Breakpad.xib +@@ -1,1140 +1,224 @@ + +- +- +- 1050 +- 10F569 +- 762 +- 1038.29 +- 461.00 +- +- YES +- +- YES +- +- +- YES +- +- +- +- YES +- +- +- +- YES +- +- +- YES +- +- +- +- YES +- +- Reporter +- +- +- FirstResponder +- +- +- NSApplication +- +- +- 1 +- 2 +- {{72, 251}, {490, 489}} +- 536871936 +- +- NSWindow +- +- {1.79769e+308, 1.79769e+308} +- {72, 5} +- +- +- 264 +- +- YES +- +- +- 272 +- +- YES +- +- +- 256 +- +- YES +- +- +- 290 +- {{17, 36}, {456, 70}} +- +- YES +- +- 67239424 +- 272760832 +- Providing your email address is optional and will allow us contact you in case we need more details. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque. +- +- LucidaGrande +- 11 +- 3100 +- +- +- +- 6 +- System +- controlColor +- +- 3 +- MC42NjY2NjY2NjY3AA +- +- +- +- 6 +- System +- controlTextColor +- +- 3 +- MAA +- +- +- +- +- +- +- 290 +- {{87, 9}, {195, 19}} +- +- YES +- +- -1804468671 +- 272761856 +- +- +- optional +- +- YES +- +- 6 +- System +- textBackgroundColor +- +- 3 +- MQA +- +- +- +- 6 +- System +- textColor +- +- +- +- +- +- +- 292 +- {{17, 11}, {65, 14}} +- +- YES +- +- 68288064 +- 71435264 +- EmailLabel: +- +- +- +- +- +- +- +- +- 289 +- {{456, 10}, {16, 17}} +- +- YES +- +- -2080244224 +- 0 +- Privacy Policy +- +- LucidaGrande +- 13 +- 1044 +- +- +- -2040250113 +- 36 +- +- NSImage +- goArrow +- +- +- +- 400 +- 75 +- +- +- +- +- 289 +- {{355, 11}, {100, 14}} +- +- YES +- +- 68288064 +- 4326400 +- PrivacyPolicyLabel +- +- +- +- +- +- +- +- {490, 114} +- +- +- +- {{0, 51}, {490, 114}} +- +- {0, 0} +- +- 67239424 +- 0 +- Title +- +- LucidaGrande +- 11 +- 16 +- +- +- +- 3 +- MCAwLjgwMDAwMDAxAA +- +- +- +- 0 +- 3 +- 0 +- NO +- +- +- +- 289 +- {{330, 12}, {146, 32}} +- +- YES +- +- 67239424 +- 134217728 +- SendReportLabel +- +- +- -2038284033 +- 129 +- +- +- DQ +- 200 +- 25 +- +- +- +- +- 289 +- {{214, 12}, {116, 32}} +- +- YES +- +- 67239424 +- 134217728 +- CancelLabel +- +- +- -2038284033 +- 129 +- +- +- Gw +- 200 +- 25 +- +- +- +- +- 256 +- +- YES +- +- +- 256 +- +- YES +- +- +- 266 +- {{17, 83}, {456, 154}} +- +- YES +- +- 67239424 +- 272760832 +- VGhlIHN5c3RlbSBhbmQgb3RoZXIgYXBwbGljYXRpb25zIGhhdmUgbm90IGJlZW4gYWZmZWN0ZWQuIEEg +-cmVwb3J0IGhhcyBiZWVuIGNyZWF0ZWQgdGhhdCB5b3UgY2FuIHNlbmQgdG8gPFJlYWxseSBMb25nIENv +-bXBhbnkgTmFtZT4gdG8gaGVscCBpZGVudGlmeSB0aGUgcHJvYmxlbS4gTG9yZW0gaXBzdW0gZG9sb3Ig +-c2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGFyY3UgdXJuYSwgcHVsdmlu +-YXIgc2l0IGFtZXQsIHRpbmNpZHVudCBhYywgZmVybWVudHVtIHV0LCBsaWd1bGEuIFF1aXNxdWUgbWku +-IER1aXMgbGVjdHVzLiBWZXN0aWJ1bHVtIHZlbGl0LiBNb3JiaSB0dXJwaXMuIE51bmMgYXQgZGlhbSBj +-b25zZWN0ZXR1ciB0dXJwaXMgdm9sdXRwYXQgdHJpc3RpcXVlLiBEb25lYyBxdWlzIGRpYW0uIFN1c3Bl +-bmRpc3NlIHNjZWxlcmlzcXVlLiBRdWlzcXVlIHB1bHZpbmFyIG1pIGlkIHB1cnVzLiBFdGlhbSB2aXRh +-ZSB0dXJwaXMgdml0YWUgbmVxdWUgcG9ydGEgY29uZ3VlLgoKUGxlYXNlIGhlbHAgdXMgZml4IHRoZSBw +-cm9ibGVtIGJ5IGRlc2NyaWJpbmcgd2hhdCBoYXBwZW5lZCBiZWZvcmUgdGhlIGNyYXNoLiBMb3JlbSBp +-cHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LiBTZWQgYXJjdSB1 +-cm5hLCBwdWx2aW5hciBzaXQgYW1ldCwgdGluY2lkdW50IGFjLCBmZXJtZW50dW0gdXQsIGxpZ3VsYS4g +-UXVpc3F1ZSBtaS4gRHVpcyBsZWN0dXMuA +- +- +- +- +- +- +- +- +- 274 +- {{20, 14}, {450, 61}} +- +- YES +- +- 341966337 +- 272760832 +- Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 +- +- +- YES +- +- +- +- +- +- +- 256 +- +- YES +- +- +- 256 +- +- YES +- +- +- 266 +- {{85, 10}, {381, 54}} +- +- YES +- +- 67239424 +- 272629760 +- The application <Really Long App Name Here> has quit unexpectedly. +- +- LucidaGrande-Bold +- 14 +- 16 +- +- +- +- +- +- +- +- +- 268 +- +- YES +- +- YES +- Apple PDF pasteboard type +- Apple PICT pasteboard type +- Apple PNG pasteboard type +- NSFilenamesPboardType +- NeXT Encapsulated PostScript v1.2 pasteboard type +- NeXT TIFF v4.0 pasteboard type +- +- +- {{16, 0}, {64, 64}} +- +- YES +- +- 130560 +- 33554432 +- +- NSImage +- NSApplicationIcon +- +- 0 +- 0 +- 0 +- NO +- +- YES +- +- +- {482, 70} +- +- +- +- {{4, 245}, {482, 70}} +- +- {0, 0} +- +- 67239424 +- 0 +- Title +- +- +- +- 3 +- MCAwLjgwMDAwMDAxAA +- +- +- +- 0 +- 3 +- 0 +- NO +- +- +- {490, 325} +- +- +- +- {{0, 160}, {490, 325}} +- +- {0, 0} +- +- 67239424 +- 0 +- Title +- +- +- +- 3 +- MCAwLjgwMDAwMDAxAA +- +- +- +- 0 +- 3 +- 0 +- NO +- +- +- +- 268 +- {{17, 20}, {163, 14}} +- +- YES +- +- 68288064 +- 272630784 +- xx seconds. +- +- +- +- +- +- +- +- {490, 489} +- +- {{0, 0}, {2560, 1578}} +- {72, 27} +- {1.79769e+308, 1.79769e+308} +- +- +- YES +- +- +- +- +- YES +- +- +- sendReport: +- +- +- +- 45 +- +- +- +- cancel: +- +- +- +- 46 +- +- +- +- showPrivacyPolicy: +- +- +- +- 53 +- +- +- +- value: emailValue +- +- +- +- +- +- value: emailValue +- value +- emailValue +- +- NSNullPlaceholder +- optional +- +- 2 +- +- +- 90 +- +- +- +- initialFirstResponder +- +- +- +- 91 +- +- +- +- value: commentsValue +- +- +- +- +- +- value: commentsValue +- value +- commentsValue +- +- NSNullPlaceholder +- optional comments +- +- 2 +- +- +- 124 +- +- +- +- nextKeyView +- +- +- +- 125 +- +- +- +- nextKeyView +- +- +- +- 126 +- +- +- +- nextKeyView +- +- +- +- 127 +- +- +- +- delegate +- +- +- +- 128 +- +- +- +- alertWindow_ +- +- +- +- 142 +- +- +- +- preEmailBox_ +- +- +- +- 150 +- +- +- +- headerBox_ +- +- +- +- 151 +- +- +- +- emailSectionBox_ +- +- +- +- 152 +- +- +- +- privacyLinkLabel_ +- +- +- +- 153 +- +- +- +- commentMessage_ +- +- +- +- 154 +- +- +- +- dialogTitle_ +- +- +- +- 155 +- +- +- +- emailLabel_ +- +- +- +- 156 +- +- +- +- cancelButton_ +- +- +- +- 158 +- +- +- +- sendButton_ +- +- +- +- 159 +- +- +- +- emailEntryField_ +- +- +- +- 161 +- +- +- +- privacyLinkArrow_ +- +- +- +- 162 +- +- +- +- emailMessage_ +- +- +- +- 163 +- +- +- +- commentsEntryField_ +- +- +- +- 176 +- +- +- +- value: countdownMessage +- +- +- +- +- +- value: countdownMessage +- value +- countdownMessage +- 2 +- +- +- 194 +- +- +- +- countdownLabel_ +- +- +- +- 208 +- +- +- +- +- YES +- +- 0 +- +- +- +- +- +- -2 +- +- +- File's Owner +- +- +- -1 +- +- +- First Responder +- +- +- -3 +- +- +- Application +- +- +- 1 +- +- +- YES +- +- +- +- Window +- +- +- 2 +- +- +- YES +- +- +- +- +- +- +- +- +- +- 12 +- +- +- YES +- +- +- +- +- +- 14 +- +- +- YES +- +- +- +- +- +- 132 +- +- +- YES +- +- +- +- +- +- +- +- +- +- 145 +- +- +- YES +- +- +- +- +- +- +- +- 189 +- +- +- YES +- +- +- +- +- +- 191 +- +- +- Shared User Defaults Controller +- +- +- 210 +- +- +- +- +- 211 +- +- +- +- +- 221 +- +- +- +- +- 58 +- +- +- YES +- +- +- +- +- +- 215 +- +- +- +- +- 18 +- +- +- YES +- +- +- +- +- +- 212 +- +- +- +- +- 20 +- +- +- YES +- +- +- +- +- +- 213 +- +- +- +- +- 48 +- +- +- YES +- +- +- +- +- +- 214 +- +- +- +- +- 66 +- +- +- YES +- +- +- +- +- +- 216 +- +- +- +- +- 8 +- +- +- YES +- +- +- +- +- +- 217 +- +- +- +- +- 116 +- +- +- YES +- +- +- +- +- +- 218 +- +- +- +- +- 147 +- +- +- YES +- +- +- +- +- +- +- 3 +- +- +- YES +- +- +- +- +- +- 219 +- +- +- +- +- 6 +- +- +- YES +- +- +- +- +- +- 220 +- +- +- +- +- +- +- YES +- +- YES +- -3.ImportedFromIB2 +- 1.IBEditorWindowLastContentRect +- 1.IBWindowTemplateEditedContentRect +- 1.ImportedFromIB2 +- 1.windowTemplate.hasMinSize +- 1.windowTemplate.minSize +- 116.CustomClassName +- 116.ImportedFromIB2 +- 12.ImportedFromIB2 +- 132.ImportedFromIB2 +- 14.ImportedFromIB2 +- 145.ImportedFromIB2 +- 147.ImportedFromIB2 +- 18.CustomClassName +- 18.ImportedFromIB2 +- 189.ImportedFromIB2 +- 191.ImportedFromIB2 +- 2.ImportedFromIB2 +- 20.ImportedFromIB2 +- 3.ImportedFromIB2 +- 48.ImportedFromIB2 +- 58.ImportedFromIB2 +- 6.ImportedFromIB2 +- 66.ImportedFromIB2 +- 8.ImportedFromIB2 +- +- +- YES +- +- {{0, 656}, {490, 489}} +- {{0, 656}, {490, 489}} +- +- +- {72, 5} +- LengthLimitingTextField +- +- +- +- +- +- +- LengthLimitingTextField +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- YES +- +- +- YES +- +- +- +- +- YES +- +- +- YES +- +- +- +- 221 +- +- +- +- YES +- +- LengthLimitingTextField +- NSTextField +- +- IBUserSource +- +- +- +- +- Reporter +- NSObject +- +- YES +- +- YES +- cancel: +- sendReport: +- showPrivacyPolicy: +- +- +- YES +- id +- id +- id +- +- +- +- YES +- +- YES +- alertWindow_ +- cancelButton_ +- commentMessage_ +- commentsEntryField_ +- countdownLabel_ +- dialogTitle_ +- emailEntryField_ +- emailLabel_ +- emailMessage_ +- emailSectionBox_ +- headerBox_ +- preEmailBox_ +- privacyLinkArrow_ +- privacyLinkLabel_ +- sendButton_ +- +- +- YES +- NSWindow +- NSButton +- NSTextField +- LengthLimitingTextField +- NSTextField +- NSTextField +- LengthLimitingTextField +- NSTextField +- NSTextField +- NSBox +- NSBox +- NSBox +- NSView +- NSTextField +- NSButton +- +- +- +- IBUserSource +- +- +- +- +- +- 0 +- IBCocoaFramework +- +- com.apple.InterfaceBuilder.CocoaPlugin.macosx +- +- +- +- com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 +- +- +- YES +- ../Breakpad.xcodeproj +- 3 +- +- YES +- +- YES +- NSApplicationIcon +- goArrow +- +- +- YES +- {128, 128} +- {128, 128} +- +- +- +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Providing your email address is optional and will allow us contact you in case we need more details. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ optional ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ The system and other applications have not been affected. A report has been created that you can send to <Really Long Company Name> to help identify the problem. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque. Quisque pulvinar mi id purus. Etiam vitae turpis vitae neque porta congue. ++ ++Please help us fix the problem by describing what happened before the crash. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 ++ ++ ++ ++ ++ ++ ++ optional comments ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/scripts/dump_syms.gyp b/scripts/dump_syms.gyp new file mode 100644 index 00000000..3e54f7b9 --- /dev/null +++ b/scripts/dump_syms.gyp @@ -0,0 +1,17 @@ +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'dump_syms', + 'type': 'executable', + 'sources': [ + 'dump_syms.cc', + ], + 'dependencies': [ + '../../../common/windows/common_windows.gyp:common_windows_lib', + ], + }, + ], +} diff --git a/scripts/prepare_breakpad.bat b/scripts/prepare_breakpad.bat new file mode 100644 index 00000000..ee46ac0d --- /dev/null +++ b/scripts/prepare_breakpad.bat @@ -0,0 +1,29 @@ +@ECHO OFF +SET ROOT_DIR=%CD% + +powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; wget https://storage.googleapis.com/chrome-infra/depot_tools.zip -OutFile depot_tools.zip " +7z -bd x %ROOT_DIR%\depot_tools.zip -odepot_tools +powershell -Command "depot_tools\update_depot_tools" +SET BUFF_PATH=%PATH% +SET DEPOT_TOOLS=%ROOT_DIR%\depot_tools +set PATH=%DEPOT_TOOLS%;%BUFF_PATH% + +mkdir %ROOT_DIR%\src\breakpad +CD %ROOT_DIR%\src\breakpad +powershell -Command "fetch breakpad" +powershell -Command "gclient sync" + +CD %ROOT_DIR%\src\breakpad\src\src\client\windows +DEL %CD%\breakpad_client.gyp +DEL %CD%\breakpad_client.sln +DEL %CD%\common.vcxproj +DEL %CD%\common.vcxproj.filters +DEL %CD%\build_all.vcxproj +COPY %ROOT_DIR%\scripts\breakpad_client.gyp %CD% + +CD %ROOT_DIR%\src\breakpad\src\src +powershell -Command "tools\gyp\gyp.bat --no-circular-check client\windows\breakpad_client.gyp -Dwin_release_RuntimeLibrary=2 -Dwin_debug_RuntimeLibrary=2 -Dplatform=%ARCH% -Dconfiguration=release" + +set PATH=%BUFF_PATH% +msbuild /m %CD%\client\windows\breakpad_client.sln /p:Configuration=release /p:Platform=%ARCH% +CD %ROOT_DIR% diff --git a/scripts/prepare_breakpad_linux.sh b/scripts/prepare_breakpad_linux.sh new file mode 100755 index 00000000..31e2ffec --- /dev/null +++ b/scripts/prepare_breakpad_linux.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +git clone https://github.com/google/breakpad.git +cd breakpad +git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss +CFLAGS=-w CXXFLAGS=-w ./configure --prefix=`pwd`/prefix && make -j4 && make install || exit 1 + +export CUSTOM_BREAKPAD_PREFIX="`pwd`/prefix" +cd .. diff --git a/scripts/prepare_breakpad_macos.sh b/scripts/prepare_breakpad_macos.sh new file mode 100755 index 00000000..284ccf16 --- /dev/null +++ b/scripts/prepare_breakpad_macos.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +SCRIPTPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")") + +DIR="$SCRIPTPATH/.." +cd "$DIR" || exit 1 +BREAKPAD_FRAMEWORK_DIR="$DIR/breakpad/framework" +BREAKPAD_DUMP_SYMS_DIR="$DIR/breakpad/bin" +git clone https://github.com/google/breakpad.git || exit 1 +mkdir $BREAKPAD_FRAMEWORK_DIR +mkdir $BREAKPAD_DUMP_SYMS_DIR +cd breakpad || exit 1 +git checkout 4d550cceca107f36c4bc1ea1126b7d32cc50f424 || exit 1 +git apply "$SCRIPTPATH/breakpad_macos.patch" || exit 1 +cd src/client/mac/ && xcodebuild -sdk macosx || exit 1 +cp -R build/Release/Breakpad.framework "$BREAKPAD_FRAMEWORK_DIR" || exit 1 + +cd $DIR/breakpad || exit 1 +cp -R src/. framework/Breakpad.framework/Headers || exit 1 + +export BREAKPAD_FRAMEWORK_DIR=$BREAKPAD_FRAMEWORK_DIR +cd $DIR diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index edc58d88..fa1a6be1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ set(CUTTER_PYTHON_MIN 3.5) option(CUTTER_ENABLE_PYTHON "Enable Python integration. Requires Python >= ${CUTTER_PYTHON_MIN}." OFF) option(CUTTER_ENABLE_PYTHON_BINDINGS "Enable generating Python bindings with Shiboken2. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF) +option(CUTTER_ENABLE_CRASH_REPORTS "Enable crash report system. Unused if CUTTER_ENABLE_CRASH_REPORTS=OFF" OFF) if(NOT CUTTER_ENABLE_PYTHON) set(CUTTER_ENABLE_PYTHON_BINDINGS OFF) @@ -93,6 +94,7 @@ message(STATUS "Building Cutter version ${CUTTER_VERSION_FULL}") message(STATUS "Options:") message(STATUS "- Python: ${CUTTER_ENABLE_PYTHON}") message(STATUS "- Python Bindings: ${CUTTER_ENABLE_PYTHON_BINDINGS}") +message(STATUS "- Crash Handling: ${CUTTER_ENABLE_CRASH_REPORTS}") message(STATUS "") @@ -134,6 +136,24 @@ endif() add_executable(Cutter MACOSX_BUNDLE ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES} ${BINDINGS_SOURCE}) set_target_properties(Cutter PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist") +if(CUTTER_ENABLE_CRASH_REPORTS) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + target_link_libraries(Cutter Threads::Threads) + + add_definitions(-DCUTTER_ENABLE_CRASH_REPORTS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g ") + if(DEFINED BREAKPAD_FRAMEWORK_DIR) + include_directories("${BREAKPAD_FRAMEWORK_DIR}/Breakpad.framework/Headers") + set_target_properties(Cutter PROPERTIES LINK_FLAGS "-Wl,-F${BREAKPAD_FRAMEWORK_DIR}") + target_link_libraries(Cutter "-framework Breakpad") + else() + find_package(Breakpad REQUIRED) + include_directories(${BREAKPAD_INCLUDE_DIRS}) + target_link_libraries(Cutter ${BREAKPAD_LINK_LIBRARIES}) + endif() +endif() + target_link_libraries(Cutter Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Svg Qt5::Network) target_link_libraries(Cutter ${RADARE2_LIBRARIES}) if(CUTTER_ENABLE_PYTHON) diff --git a/src/Cutter.pro b/src/Cutter.pro index 85134534..a09b6349 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -32,6 +32,8 @@ QT += core gui widgets svg network QT_CONFIG -= no-pkg-config CONFIG += c++11 +!defined(CUTTER_ENABLE_CRASH_REPORTS, var) CUTTER_ENABLE_CRASH_REPORTS=false +equals(CUTTER_ENABLE_CRASH_REPORTS, true) CONFIG += CUTTER_ENABLE_CRASH_REPORTS !defined(CUTTER_ENABLE_PYTHON, var) CUTTER_ENABLE_PYTHON=false equals(CUTTER_ENABLE_PYTHON, true) CONFIG += CUTTER_ENABLE_PYTHON @@ -49,6 +51,13 @@ equals(CUTTER_BUNDLE_R2_APPBUNDLE, true) CONFIG += CUTTER_BUNDLE_R2_APPBU !defined(CUTTER_APPVEYOR_R2DEC, var) CUTTER_APPVEYOR_R2DEC=false equals(CUTTER_APPVEYOR_R2DEC, true) CONFIG += CUTTER_APPVEYOR_R2DEC +CUTTER_ENABLE_CRASH_REPORTS { + message("Crash report support enabled.") + DEFINES += CUTTER_ENABLE_CRASH_REPORTS +} else { + message("Crash report support disabled.") +} + CUTTER_ENABLE_PYTHON { message("Python enabled.") DEFINES += CUTTER_ENABLE_PYTHON @@ -188,6 +197,29 @@ CUTTER_ENABLE_PYTHON { } } +CUTTER_ENABLE_CRASH_REPORTS { +QMAKE_CXXFLAGS += -g + defined(BREAKPAD_FRAMEWORK_DIR, var)|defined(BREAKPAD_SOURCE_DIR, var) { + defined(BREAKPAD_FRAMEWORK_DIR, var) { + INCLUDEPATH += $$BREAKPAD_FRAMEWORK_DIR/Breakpad.framework/Headers + LIBS += -F$$BREAKPAD_FRAMEWORK_DIR -framework Breakpad + } + defined(BREAKPAD_SOURCE_DIR, var) { + INCLUDEPATH += $$BREAKPAD_SOURCE_DIR + win32 { + LIBS += -L$$quote($$BREAKPAD_SOURCE_DIR\\client\\windows\\release\\lib) -lexception_handler -lcrash_report_sender -lcrash_generation_server -lcrash_generation_client -lcommon + } + unix:LIBS += -L$$BREAKPAD_SOURCE_DIR/client/linux -lbreakpad-client + macos:error("Please use scripts\prepare_breakpad_macos.sh script to provide breakpad framework.") + } + } else { + CONFIG += link_pkgconfig + !packagesExist(breakpad-client) { + error("ERROR: Breakpad could not be found. Make sure it is available to pkg-config.") + } + PKGCONFIG += breakpad-client + } +} macx:CUTTER_BUNDLE_R2_APPBUNDLE { message("Using r2 rom AppBundle") @@ -310,6 +342,8 @@ SOURCES += \ dialogs/LinkTypeDialog.cpp \ common/UpdateWorker.cpp \ widgets/MemoryDockWidget.cpp \ + common/CrashHandler.cpp \ + common/BugReporting.cpp \ common/HighDpiPixmap.cpp \ widgets/GraphGridLayout.cpp @@ -421,6 +455,7 @@ HEADERS += \ common/RunScriptTask.h \ common/Json.h \ dialogs/EditMethodDialog.h \ + common/CrashHandler.h \ dialogs/LoadNewTypesDialog.h \ widgets/SdbWidget.h \ common/PythonManager.h \ @@ -429,6 +464,7 @@ HEADERS += \ common/UpdateWorker.h \ dialogs/LinkTypeDialog.h \ widgets/MemoryDockWidget.h \ + common/BugReporting.h \ common/HighDpiPixmap.h \ widgets/GraphLayout.h \ widgets/GraphGridLayout.h diff --git a/src/Main.cpp b/src/Main.cpp index ddf1e433..e177ca2b 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -3,6 +3,7 @@ #include "core/MainWindow.h" #include "common/UpdateWorker.h" #include "CutterConfig.h" +#include "common/CrashHandler.h" /** * @brief Migrate Settings used before Cutter 1.8 @@ -20,6 +21,8 @@ static void migrateSettings(QSettings &newSettings) int main(int argc, char *argv[]) { + initCrashHandler(); + qRegisterMetaType>(); qRegisterMetaType>(); diff --git a/src/cmake/FindBreakpad.cmake b/src/cmake/FindBreakpad.cmake new file mode 100644 index 00000000..fc38e479 --- /dev/null +++ b/src/cmake/FindBreakpad.cmake @@ -0,0 +1,46 @@ +# - Find Breakpad +# +# BREAKPAD_FOUND - True if Breakpad has been found. +# BREAKPAD_INCLUDE_DIRS - Breakpad include directory +# BREAKPAD_LIBRARIES - List of libraries when using Breakpad. +# BREAKPAD_LIBRARY_DIRS - Breakpad library directories + +if(WIN32) + find_path(BREAKPAD_INCLUDE_DIRS + HINTS + "${CMAKE_CURRENT_SOURCE_DIR}/breakpad/prefix/include/breakpad") + + set(BREAKPAD_LIBRARY_NAMES + BREAKPAD_CLIENT + BREAKPAD) + + set(BREAKPAD_LIBRARIES "") + set(BREAKPAD_LIBRARIES_VARS "") + foreach(libname ${BREAKPAD_LIBRARY_NAMES}) + find_library(BREAKPAD_LIBRARY_${libname} + ${libname} + HINTS + "${CMAKE_CURRENT_SOURCE_DIR}/breakpad/prefix/lib") + + list(APPEND BREAKPAD_LIBRARIES ${BREAKPAD_LIBRARY_${libname}}) + list(APPEND BREAKPAD_LIBRARIES_VARS "BREAKPAD_LIBRARY_${libname}") + endforeach() + + set(BREAKPAD_LIBRARY_DIRS "") +else() + set(BREAKPAD_CMAKE_PREFIX_PATH_TEMP ${CMAKE_PREFIX_PATH}) + list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/breakpad/prefix") + + find_package(PkgConfig REQUIRED) + pkg_search_module(BREAKPAD REQUIRED breakpad-client) + + # reset CMAKE_PREFIX_PATH + set(CMAKE_PREFIX_PATH ${BREAKPAD_CMAKE_PREFIX_PATH_TEMP}) + mark_as_advanced(BREAKPAD_CMAKE_PREFIX_PATH_TEMP) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BREAKPAD REQUIRED_VARS BREAKPAD_LIBRARIES BREAKPAD_INCLUDE_DIRS) + +mark_as_advanced(BREAKPAD_LIBRARIES_VARS) + diff --git a/src/common/BugReporting.cpp b/src/common/BugReporting.cpp new file mode 100644 index 00000000..d860c2cc --- /dev/null +++ b/src/common/BugReporting.cpp @@ -0,0 +1,45 @@ +#include "BugReporting.h" + +#include "Cutter.h" +#include +#include +#include "CutterConfig.h" +#include + +void openIssue() +{ + QString url, osInfo, format, arch, type; + //Pull in info needed for git issue + osInfo = QSysInfo::productType() + " " + + (QSysInfo::productVersion() == "unknown" + ? "" + : QSysInfo::productVersion()); + QJsonDocument docu = Core()->getFileInfo(); + QJsonObject coreObj = docu.object()["core"].toObject(); + QJsonObject binObj = docu.object()["bin"].toObject(); + if (!binObj.QJsonObject::isEmpty()) { + format = coreObj["format"].toString(); + arch = binObj["arch"].toString(); + if (!binObj["type"].isUndefined()) { + type = coreObj["type"].toString(); + } else { + type = "N/A"; + } + } else { + format = coreObj["format"].toString(); + arch = "N/A"; + type = "N/A"; + } + url = + "https://github.com/radareorg/cutter/issues/new?&body=**Environment information**\n* Operating System: " + + osInfo + "\n* Cutter version: " + CUTTER_VERSION_FULL + + "\n* File format: " + format + "\n * Arch: " + arch + "\n * Type: " + type + + "\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\n" + "Steps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n" + "4. See error\n\n**Expected behavior**\n" + "A clear and concise description of what you expected to happen.\n\n" + "**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n" + "**Additional context**\nAdd any other context about the problem here."; + + QDesktopServices::openUrl(QUrl(url, QUrl::TolerantMode)); +} diff --git a/src/common/BugReporting.h b/src/common/BugReporting.h new file mode 100644 index 00000000..85c11eb8 --- /dev/null +++ b/src/common/BugReporting.h @@ -0,0 +1,10 @@ +#ifndef CRASHREPORTING_H +#define CRASHREPORTING_H + +/** + * @brief Opens issue on Cutter's github page + * with current file and system information. + */ +void openIssue(); + +#endif // CRASHREPORTING_H diff --git a/src/common/CrashHandler.cpp b/src/common/CrashHandler.cpp new file mode 100644 index 00000000..c69cfccb --- /dev/null +++ b/src/common/CrashHandler.cpp @@ -0,0 +1,229 @@ +#include "CrashHandler.h" + +#ifdef CUTTER_ENABLE_CRASH_REPORTS +#include "BugReporting.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (Q_OS_LINUX) +#include "client/linux/handler/exception_handler.h" +#elif defined (Q_OS_WIN32) +#include "client/windows/handler/exception_handler.h" +#elif defined (Q_OS_MACOS) +#include "client/mac/handler/exception_handler.h" +#endif // Q_OS + + + +// Here will be placed crash dump at the first place +// and then moved if needed +#if defined (Q_OS_LINUX) || defined (Q_OS_MACOS) +static std::string tmpLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString(); +#else +static std::wstring tmpLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdWString(); +#endif + +static const QMap sigNumDescription = { + #ifdef SIGSEGV + { SIGSEGV, "SIGSEGV" }, + #endif // SIGSEGV + #ifdef SIGILL + { SIGILL, "SIGILL" }, + #endif // SIGILL + #ifdef SIGFPE + { SIGFPE, "SIGFPE" }, + #endif // SIGFPE + #ifdef SIGABRT + { SIGABRT, "SIGABRT" }, + #endif // SIGABRT + #ifdef SIGBUS + { SIGBUS, "SIGBUS" }, + #endif // SIGBUS + #ifdef SIGPIPE + { SIGPIPE, "SIGPIPE" }, + #endif // SIGPIPE + #ifdef SIGSYS + { SIGSYS, "SIGSYS" } + #endif // SIGSYS +}; + +static QString dumpFileFullPath = ""; + +#ifdef Q_OS_WIN32 +// Called if crash dump was successfully created +// Saves path to file +bool callback(const wchar_t *_dump_dir, + const wchar_t *_minidump_id, + void *context, EXCEPTION_POINTERS *exinfo, + MDRawAssertionInfo *assertion, + bool success) +{ + QString dir = QString::fromWCharArray(_dump_dir); + QString id = QString::fromWCharArray(_minidump_id); + dumpFileFullPath = QDir(dir).filePath(id + ".dmp"); + return true; +} +#elif defined (Q_OS_LINUX) +// Called if crash dump was successfully created +// Saves path to file +bool callback(const google_breakpad::MinidumpDescriptor &md, void *context, bool b) +{ + dumpFileFullPath = md.path(); + return true; +} +#elif defined (Q_OS_MACOS) +// Called if crash dump was successfully created +// Saves path to file +bool callback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded) +{ + QString dir = QString::fromUtf8(dump_dir); + QString id = QString::fromUtf8(minidump_id); + dumpFileFullPath = QDir(dir).filePath(id + ".dmp"); + return true; +} +#endif // Q_OS + + +/** + * @brief Writes minidump and put its name in dumpFileFullPath. + * @return true on succes + */ +bool writeMinidump() +{ + bool ok; +#if defined (Q_OS_LINUX) || defined (Q_OS_MACOS) + ok = google_breakpad::ExceptionHandler::WriteMinidump(tmpLocation, + callback, + nullptr); +#elif defined (Q_OS_WIN32) + ok = google_breakpad::ExceptionHandler::WriteMinidump(tmpLocation, + callback, + nullptr); +#endif // Q_OS + return ok; +} + +[[noreturn]] void crashHandler(int signum) +{ + // As soon as Cutter crashed, crash dump is created, so core and memory state + // is not changed by all stuff with user interation going on below. + bool ok = writeMinidump(); + + QString err = sigNumDescription.contains(signum) ? + sigNumDescription[signum] : + QObject::tr("undefined"); + + + QMessageBox mb; + mb.setWindowTitle(QObject::tr("Cutter encountered a problem")); + mb.setText(QObject::tr("Cutter received a %1 it can't handle and will close.
" + "Would you like to create a crash dump for bug report?" + ).arg(err)); + mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + mb.button(QMessageBox::Yes)->setText(QObject::tr("Create a crash dump")); + mb.button(QMessageBox::No)->setText(QObject::tr("Do not report")); + mb.setDefaultButton(QMessageBox::Yes); + + int ret = mb.exec(); + if (ret == QMessageBox::Yes) { + QString dumpSaveFileName; + int placementFailCounter = 0; + do { + placementFailCounter++; + if (placementFailCounter == 4) { + ok = false; + break; + } + dumpSaveFileName = QFileDialog::getSaveFileName(nullptr, + QObject::tr("Choose a directory to save the crash dump in"), + QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + + QDir::separator() + + "Cutter_crash_dump_" + + QDate().currentDate().toString("dd.MM.yy") + "_" + + QTime().currentTime().toString() + ".dmp", + QObject::tr("Dump files (*.dmp)")); + + if (dumpSaveFileName.isEmpty()) { + exit(3); + } + if (QFile::rename(dumpFileFullPath, dumpSaveFileName)) { + break; + } + QMessageBox::critical(nullptr, + QObject::tr("Error"), + QObject::tr("Error occured during writing to the %1.
" + "Please, make sure you have access to that directory " + "and try again.").arg(QFileInfo(dumpSaveFileName).dir().path())); + } while (true); + + if (ok) { + QMessageBox info; + info.setWindowTitle(QObject::tr("Success")); + info.setText(QObject::tr("Crash dump was successfully created.") + .arg(QFileInfo(dumpSaveFileName).dir().path())); + info.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + info.button(QMessageBox::Yes)->setText(QObject::tr("Open an issue")); + info.button(QMessageBox::No)->setText(QObject::tr("Exit Cutter")); + info.setDefaultButton(QMessageBox::Yes); + + int ret = info.exec(); + if (ret == QMessageBox::Yes) { + openIssue(); + } + } else { + QMessageBox::critical(nullptr, + QObject::tr("Error!"), + QObject::tr("Error occured during crash dump creation.")); + } + } else { + QFile f(dumpFileFullPath); + f.remove(); + } + + _exit(3); +} + +void initCrashHandler() +{ +#ifdef SIGSEGV + signal(SIGSEGV, crashHandler); +#endif // SIGSEGV +#ifdef SIGILL + signal(SIGILL, crashHandler); +#endif // SIGILL +#ifdef SIGFPE + signal(SIGFPE, crashHandler); +#endif // SIGFPE +#ifdef SIGABRT + signal(SIGABRT, crashHandler); +#endif // SIGABRT +#ifdef SIGBUS + signal(SIGBUS, crashHandler); +#endif // SIGBUS +#ifdef SIGPIPE + signal(SIGPIPE, crashHandler); +#endif // SIGPIPE +#ifdef SIGSYS + signal(SIGSYS, crashHandler); +#endif // SIGSYS +} + +#else // CUTTER_ENABLE_CRASH_REPORTS + +void initCrashHandler() +{ + +} + +#endif // CUTTER_ENABLE_CRASH_REPORTS diff --git a/src/common/CrashHandler.h b/src/common/CrashHandler.h new file mode 100644 index 00000000..4c62d903 --- /dev/null +++ b/src/common/CrashHandler.h @@ -0,0 +1,13 @@ +#ifndef CRASH_HANDLER_H +#define CRASH_HANDLER_H + +/** + * @fn void initCrashHandler() + * + * If CUTTER_ENABLE_CRASH_REPORTS is true, initializes + * crash handling and reporting, otherwise does nothing. +*/ +void initCrashHandler(); + + +#endif // CRASH_HANDLER_H diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index 138f907d..7abba2b9 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -9,6 +9,7 @@ #include #include #include +#include "core/CutterCommon.h" struct FunctionDescription { RVA offset; diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 02ef1fd2..6fea506f 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -2,6 +2,7 @@ #include "ui_MainWindow.h" // Common Headers +#include "common/BugReporting.h" #include "common/Highlighter.h" #include "common/HexAsciiHighlighter.h" #include "common/Helpers.h" @@ -1112,34 +1113,10 @@ void MainWindow::on_actionAbout_triggered() a->setAttribute(Qt::WA_DeleteOnClose); a->open(); } + void MainWindow::on_actionIssue_triggered() { - QString url, osInfo, format, arch, type; - //Pull in info needed for git issue - osInfo = QString(QSysInfo::productType()) + " " + QString(QSysInfo::productVersion()); - QJsonDocument docu = Core()->getFileInfo(); - QJsonObject coreObj = docu.object()["core"].toObject(); - QJsonObject binObj = docu.object()["bin"].toObject(); - if (!binObj.QJsonObject::isEmpty()) { - format = coreObj["format"].toString(); - arch = binObj["arch"].toString(); - if (!binObj["type"].isUndefined()) { - type = coreObj["type"].toString(); - } else { - type = "N/A"; - } - } else { - format = coreObj["format"].toString(); - arch = "N/A"; - type = "N/A"; - } - url = - "https://github.com/radareorg/cutter/issues/new?&body=**Environment information**\n* Operating System: " - + osInfo + "\n* Cutter version: " + CUTTER_VERSION_FULL + - "\n* File format: " + format + "\n * Arch: " + arch + "\n * Type: " + type + - "\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here."; - - QDesktopServices::openUrl(QUrl(url, QUrl::TolerantMode)); + openIssue(); } void MainWindow::on_actionRefresh_Panels_triggered()