Kaynağa Gözat

Applications: Add CrashReporter :^)

This is a simple application that can read a coredump file and display
information regarding the crash, like the application's name and icon
and a backtrace. It will be launched by CrashDaemon whenever a new
coredump is available.
Also, it's mostly written in GML! :^)

Closes #400, but note that, unlike mentioned in that issue, this
implementation doesn't ignore applications that "have been started in
the terminal". That's just overcomplicating things, IMO. When my js(1)
REPL segfaults, I want to see a backtrace!
Linus Groh 4 yıl önce
ebeveyn
işleme
b13b27b4d2

+ 1 - 0
Applications/CMakeLists.txt

@@ -3,6 +3,7 @@ add_subdirectory(Browser)
 add_subdirectory(Calculator)
 add_subdirectory(Calendar)
 add_subdirectory(CrashDaemon)
+add_subdirectory(CrashReporter)
 add_subdirectory(Debugger)
 add_subdirectory(DisplaySettings)
 add_subdirectory(FileManager)

+ 15 - 0
Applications/CrashDaemon/main.cpp

@@ -28,6 +28,8 @@
 #include <LibCore/DirectoryWatcher.h>
 #include <LibCoreDump/Backtrace.h>
 #include <LibCoreDump/Reader.h>
+#include <serenity.h>
+#include <spawn.h>
 #include <sys/stat.h>
 #include <time.h>
 #include <unistd.h>
@@ -58,6 +60,18 @@ static void print_backtrace(const String& coredump_path)
         dbgln("{}", entry.to_string(true));
 }
 
+static void launch_crash_reporter(const String& coredump_path)
+{
+    pid_t child;
+    const char* argv[] = { "CrashReporter", coredump_path.characters(), nullptr, nullptr };
+    if ((errno = posix_spawn(&child, "/bin/CrashReporter", nullptr, nullptr, const_cast<char**>(argv), environ))) {
+        perror("posix_spawn");
+    } else {
+        if (disown(child) < 0)
+            perror("disown");
+    }
+}
+
 int main()
 {
     static constexpr const char* coredumps_dir = "/tmp/coredump";
@@ -72,5 +86,6 @@ int main()
         dbgln("New coredump file: {}", coredump_path);
         wait_until_coredump_is_ready(coredump_path);
         print_backtrace(coredump_path);
+        launch_crash_reporter(coredump_path);
     }
 }

+ 10 - 0
Applications/CrashReporter/CMakeLists.txt

@@ -0,0 +1,10 @@
+compile_gml(CrashReporterWindow.gml CrashReporterWindowGML.h crash_reporter_window_gml)
+
+
+set(SOURCES
+    main.cpp
+    CrashReporterWindowGML.h
+)
+
+serenity_app(CrashReporter ICON app-crash-reporter)
+target_link_libraries(CrashReporter LibCore LibCoreDump LibDesktop LibGUI)

+ 108 - 0
Applications/CrashReporter/CrashReporterWindow.gml

@@ -0,0 +1,108 @@
+@GUI::Widget {
+    fill_with_background_color: true
+
+    layout: @GUI::VerticalBoxLayout {
+        margins: [5, 5, 5, 5]
+    }
+
+    @GUI::Widget {
+        vertical_size_policy: "Fixed"
+        preferred_height: 44
+
+        layout: @GUI::HorizontalBoxLayout {
+            spacing: 10
+        }
+
+        @GUI::ImageWidget {
+            name: "icon"
+        }
+
+        @GUI::Label {
+            name: "description"
+            text_alignment: "CenterLeft"
+        }
+    }
+
+    @GUI::Widget {
+        vertical_size_policy: "Fixed"
+        preferred_height: 18
+
+        layout: @GUI::HorizontalBoxLayout {
+        }
+
+        @GUI::Label {
+            text: "Executable path:"
+            text_alignment: "CenterLeft"
+            horizontal_size_policy: "Fixed"
+            preferred_width: 90
+        }
+
+        @GUI::LinkLabel {
+            name: "executable_link"
+            text_alignment: "CenterLeft"
+        }
+    }
+
+    @GUI::Widget {
+        vertical_size_policy: "Fixed"
+        preferred_height: 18
+
+        layout: @GUI::HorizontalBoxLayout {
+        }
+
+        @GUI::Label {
+            text: "Coredump path:"
+            text_alignment: "CenterLeft"
+            horizontal_size_policy: "Fixed"
+            preferred_width: 90
+        }
+
+        @GUI::LinkLabel {
+            name: "coredump_link"
+            text_alignment: "CenterLeft"
+        }
+    }
+
+    @GUI::Widget {
+        vertical_size_policy: "Fixed"
+        preferred_height: 18
+
+        layout: @GUI::HorizontalBoxLayout {
+        }
+
+        @GUI::Label {
+            text: "Backtrace:"
+            text_alignment: "CenterLeft"
+        }
+    }
+
+    @GUI::TextEditor {
+        name: "backtrace_text_editor"
+        mode: "ReadOnly"
+    }
+
+    @GUI::Widget {
+        vertical_size_policy: "Fixed"
+        preferred_height: 32
+
+        layout: @GUI::HorizontalBoxLayout {
+        }
+
+        // HACK: We need something like Layout::add_spacer() in GML! :^)
+        @GUI::Widget {
+            horizontal_size_policy: "Fixed"
+            vertical_size_policy: "Fill"
+            preferred_width: 378
+            preferred_height: 0
+        }
+
+        @GUI::Button {
+            name: "close_button"
+            text: "Close"
+            horizontal_size_policy: "Fixed"
+            vertical_size_policy: "Fixed"
+            preferred_width: 70
+            preferred_height: 22
+        }
+    }
+}

+ 165 - 0
Applications/CrashReporter/main.cpp

@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <AK/URL.h>
+#include <Applications/CrashReporter/CrashReporterWindowGML.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCoreDump/Backtrace.h>
+#include <LibCoreDump/Reader.h>
+#include <LibDesktop/AppFile.h>
+#include <LibDesktop/Launcher.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/ImageWidget.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Layout.h>
+#include <LibGUI/LinkLabel.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/Window.h>
+
+int main(int argc, char** argv)
+{
+    if (pledge("stdio shared_buffer accept cpath rpath unix fattr", nullptr) < 0) {
+        perror("pledge");
+        return 1;
+    }
+
+    const char* coredump_path = nullptr;
+
+    Core::ArgsParser args_parser;
+    args_parser.set_general_help("Show information from an application crash coredump.");
+    args_parser.add_positional_argument(coredump_path, "Coredump path", "coredump-path");
+    args_parser.parse(argc, argv);
+
+    auto coredump = CoreDump::Reader::create(coredump_path);
+    if (!coredump) {
+        warnln("Could not open coredump '{}'", coredump_path);
+        return 1;
+    }
+
+    auto app = GUI::Application::construct(argc, argv);
+
+    if (pledge("stdio shared_buffer accept rpath unix", nullptr) < 0) {
+        perror("pledge");
+        return 1;
+    }
+
+    auto backtrace = coredump->backtrace();
+
+    String executable_path;
+    // FIXME: Maybe we should just embed the process's executable path
+    // in the coredump by itself so we don't have to extract it from the backtrace.
+    // Such a process section could also include the PID, which currently we'd have
+    // to parse from the filename.
+    if (!backtrace.entries().is_empty()) {
+        executable_path = backtrace.entries().last().object_name;
+    } else {
+        warnln("Could not determine executable path from coredump");
+        return 1;
+    }
+
+    if (unveil(executable_path.characters(), "r") < 0) {
+        perror("unveil");
+        return 1;
+    }
+
+    if (unveil("/res", "r") < 0) {
+        perror("unveil");
+        return 1;
+    }
+
+    if (unveil("/tmp/portal/launch", "rw") < 0) {
+        perror("unveil");
+        return 1;
+    }
+
+    if (unveil(nullptr, nullptr) < 0) {
+        perror("unveil");
+        return 1;
+    }
+
+    auto app_icon = GUI::Icon::default_icon("app-crash-reporter");
+
+    auto window = GUI::Window::construct();
+    window->set_title("Crash Reporter");
+    window->set_icon(app_icon.bitmap_for_size(16));
+    window->set_resizable(false);
+    window->resize(460, 340);
+    window->center_on_screen();
+
+    auto& widget = window->set_main_widget<GUI::Widget>();
+    widget.load_from_gml(crash_reporter_window_gml);
+
+    auto& icon_image_widget = static_cast<GUI::ImageWidget&>(*widget.find_descendant_by_name("icon"));
+    icon_image_widget.set_bitmap(GUI::FileIconProvider::icon_for_executable(executable_path).bitmap_for_size(32));
+
+    auto app_name = LexicalPath(executable_path).basename();
+    auto af = Desktop::AppFile::get_for_app(app_name);
+    if (af->is_valid())
+        app_name = af->name();
+
+    auto& description_label = static_cast<GUI::Label&>(*widget.find_descendant_by_name("description"));
+    description_label.set_text(String::formatted("\"{}\" has crashed!", app_name));
+
+    auto& executable_link_label = static_cast<GUI::LinkLabel&>(*widget.find_descendant_by_name("executable_link"));
+    executable_link_label.set_text(LexicalPath::canonicalized_path(executable_path));
+    executable_link_label.on_click = [&] {
+        Desktop::Launcher::open(URL::create_with_file_protocol(LexicalPath(executable_path).dirname()));
+    };
+
+    auto& coredump_link_label = static_cast<GUI::LinkLabel&>(*widget.find_descendant_by_name("coredump_link"));
+    coredump_link_label.set_text(LexicalPath::canonicalized_path(coredump_path));
+    coredump_link_label.on_click = [&] {
+        Desktop::Launcher::open(URL::create_with_file_protocol(LexicalPath(coredump_path).dirname()));
+    };
+
+    StringBuilder backtrace_builder;
+    auto first = true;
+    for (auto& entry : backtrace.entries()) {
+        if (first)
+            first = false;
+        else
+            backtrace_builder.append('\n');
+        backtrace_builder.append(entry.to_string());
+    }
+
+    auto& backtrace_text_editor = static_cast<GUI::TextEditor&>(*widget.find_descendant_by_name("backtrace_text_editor"));
+    backtrace_text_editor.set_text(backtrace_builder.build());
+
+    auto& close_button = static_cast<GUI::Button&>(*widget.find_descendant_by_name("close_button"));
+    close_button.on_click = [&](auto) {
+        app->quit();
+    };
+
+    window->show();
+
+    return app->exec();
+}

BIN
Base/res/icons/16x16/app-crash-reporter.png


BIN
Base/res/icons/32x32/app-crash-reporter.png