Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 88a9073

Browse files
committedAug 20, 2024
crash: add crash reporter
1 parent 5040f37 commit 88a9073

23 files changed

+1142
-343
lines changed
 

‎.github/ISSUE_TEMPLATE/config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blank_issues_enabled: true

‎.github/ISSUE_TEMPLATE/crash.yml

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Crash Report
2+
description: Quickshell has crashed
3+
labels: ["bug", "crash"]
4+
body:
5+
- type: textarea
6+
id: crashinfo
7+
attributes:
8+
label: General crash information
9+
description: |
10+
Paste the contents of the `info.txt` file in your crash folder here.
11+
value: "<details><summary>General information</summary>
12+
13+
```
14+
<Paste the contents of the file here inside of the triple backticks>
15+
```
16+
17+
</details>"
18+
validations:
19+
required: true
20+
- type: textarea
21+
id: userinfo
22+
attributes:
23+
label: What caused the crash
24+
description: |
25+
Any information likely to help debug the crash. What were you doing when the crash occurred,
26+
what changes did you make, can you get it to happen again?
27+
- type: textarea
28+
id: dump
29+
attributes:
30+
label: Minidump
31+
description: |
32+
Attach `minidump.dmp` here. If it is too big to upload, compress it.
33+
34+
You may skip this step if quickshell crashed while processing a password
35+
or other sensitive information. If you skipped it write why instead.
36+
validations:
37+
required: true
38+
- type: textarea
39+
id: logs
40+
attributes:
41+
label: Log file
42+
description: |
43+
Attach `log.qslog` here. If it is too big to upload, compress it.
44+
45+
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
46+
validations:
47+
required: true
48+
- type: textarea
49+
id: config
50+
attributes:
51+
label: Configuration
52+
description: |
53+
Attach your configuration here, preferrably in full (not just one file).
54+
Compress it into a zip, tar, etc.
55+
56+
This will help us reproduce the crash ourselves.
57+
- type: textarea
58+
id: bt
59+
attributes:
60+
label: Backtrace
61+
description: |
62+
If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
63+
we would appreciate one. (You may have gdb installed without knowing it)
64+
65+
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
66+
in the crash reporter.
67+
2. Once it loads, type `bt -full` (then enter)
68+
3. Copy the output and attach it as a file or in a spoiler.

‎CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ option(BUILD_TESTING "Build tests" OFF)
99
option(ASAN "Enable ASAN" OFF) # note: better output with gcc than clang
1010
option(FRAME_POINTERS "Always keep frame pointers" ${ASAN})
1111

12+
option(CRASH_REPORTER "Enable the crash reporter" ON)
1213
option(USE_JEMALLOC "Use jemalloc over the system malloc implementation" ON)
1314
option(SOCKETS "Enable unix socket support" ON)
1415
option(WAYLAND "Enable wayland support" ON)
@@ -29,6 +30,7 @@ option(SERVICE_UPOWER "UPower service" ON)
2930
option(SERVICE_NOTIFICATIONS "Notification server" ON)
3031

3132
message(STATUS "Quickshell configuration")
33+
message(STATUS " Crash reporter: ${CRASH_REPORTER}")
3234
message(STATUS " Jemalloc: ${USE_JEMALLOC}")
3335
message(STATUS " Build tests: ${BUILD_TESTING}")
3436
message(STATUS " Sockets: ${SOCKETS}")

‎default.nix

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
ninja,
1010
qt6,
1111
cli11,
12+
breakpad,
1213
jemalloc,
1314
wayland,
1415
wayland-protocols,
@@ -28,6 +29,7 @@
2829
else "unknown"),
2930

3031
debug ? false,
32+
withCrashReporter ? true,
3133
withJemalloc ? true, # masks heap fragmentation
3234
withQtSvg ? true,
3335
withWayland ? true,
@@ -55,6 +57,7 @@
5557
qt6.qtdeclarative
5658
cli11
5759
]
60+
++ (lib.optional withCrashReporter breakpad)
5861
++ (lib.optional withJemalloc jemalloc)
5962
++ (lib.optional withQtSvg qt6.qtsvg)
6063
++ (lib.optionals withWayland [ qt6.qtwayland wayland ])
@@ -67,6 +70,7 @@
6770
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
6871

6972
cmakeFlags = [ "-DGIT_REVISION=${gitRev}" ]
73+
++ lib.optional (!withCrashReporter) "-DCRASH_REPORTER=OFF"
7074
++ lib.optional (!withJemalloc) "-DUSE_JEMALLOC=OFF"
7175
++ lib.optional (!withWayland) "-DWAYLAND=OFF"
7276
++ lib.optional (!withPipewire) "-DSERVICE_PIPEWIRE=OFF"

‎src/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
55
add_subdirectory(core)
66
add_subdirectory(io)
77

8+
if (CRASH_REPORTER)
9+
add_subdirectory(crash)
10+
endif()
11+
812
if (DBUS)
913
add_subdirectory(dbus)
1014
endif()

‎src/core/CMakeLists.txt

+11-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,19 @@ qt_add_library(quickshell-core STATIC
4141
clock.cpp
4242
logging.cpp
4343
paths.cpp
44+
crashinfo.cpp
45+
common.cpp
4446
)
4547

46-
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
48+
if (CRASH_REPORTER)
49+
set(CRASH_REPORTER_DEF 1)
50+
endif()
51+
52+
add_library(quickshell-build INTERFACE)
53+
configure_file(build.hpp.in build.hpp)
54+
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
55+
target_link_libraries(quickshell-core PRIVATE quickshell-build)
56+
4757
qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1)
4858

4959
target_link_libraries(quickshell-core PRIVATE ${QT_DEPS} Qt6::QuickPrivate CLI11::CLI11)

‎src/core/build.hpp.in

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#pragma once
2+
3+
// NOLINTBEGIN
4+
#define GIT_REVISION "@GIT_REVISION@"
5+
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
6+
// NOLINTEND

‎src/core/common.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "common.hpp"
2+
3+
#include <qdatetime.h>
4+
5+
namespace qs {
6+
7+
const QDateTime Common::LAUNCH_TIME = QDateTime::currentDateTime();
8+
9+
}

‎src/core/common.hpp

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include <qdatetime.h>
4+
5+
namespace qs {
6+
7+
struct Common {
8+
static const QDateTime LAUNCH_TIME;
9+
};
10+
11+
} // namespace qs

‎src/core/crashinfo.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "crashinfo.hpp"
2+
3+
#include <qdatastream.h>
4+
5+
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
6+
stream << info.configPath << info.shellId << info.launchTime << info.noColor;
7+
return stream;
8+
}
9+
10+
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
11+
stream >> info.configPath >> info.shellId >> info.launchTime >> info.noColor;
12+
return stream;
13+
}
14+
15+
namespace qs::crash {
16+
17+
CrashInfo CrashInfo::INSTANCE = {}; // NOLINT
18+
19+
}

‎src/core/crashinfo.hpp

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <qdatetime.h>
4+
#include <qstring.h>
5+
6+
struct InstanceInfo {
7+
QString configPath;
8+
QString shellId;
9+
QString initialWorkdir;
10+
QDateTime launchTime;
11+
bool noColor = false;
12+
bool sparseLogsOnly = false;
13+
};
14+
15+
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info);
16+
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info);
17+
18+
namespace qs::crash {
19+
20+
struct CrashInfo {
21+
int logFd = -1;
22+
23+
static CrashInfo INSTANCE; // NOLINT
24+
};
25+
26+
} // namespace qs::crash

‎src/core/logging.cpp

+9-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <sys/mman.h>
2525
#include <sys/sendfile.h>
2626

27+
#include "crashinfo.hpp"
2728
#include "logging_p.hpp"
2829
#include "logging_qtprivate.cpp" // NOLINT
2930
#include "paths.hpp"
@@ -198,14 +199,16 @@ void ThreadLogging::init() {
198199

199200
if (logMfd != -1) {
200201
this->file = new QFile();
201-
this->file->open(logMfd, QFile::WriteOnly, QFile::AutoCloseHandle);
202+
this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle);
202203
this->fileStream.setDevice(this->file);
203204
}
204205

205206
if (dlogMfd != -1) {
207+
crash::CrashInfo::INSTANCE.logFd = dlogMfd;
208+
206209
this->detailedFile = new QFile();
207210
// buffered by WriteBuffer
208-
this->detailedFile->open(dlogMfd, QFile::WriteOnly | QFile::Unbuffered, QFile::AutoCloseHandle);
211+
this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle);
209212
this->detailedWriter.setDevice(this->detailedFile);
210213

211214
if (!this->detailedWriter.writeHeader()) {
@@ -245,7 +248,7 @@ void ThreadLogging::initFs() {
245248
auto* file = new QFile(path);
246249
auto* detailedFile = new QFile(detailedPath);
247250

248-
if (!file->open(QFile::WriteOnly | QFile::Truncate)) {
251+
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
249252
qCCritical(logLogging
250253
) << "Could not start filesystem logger as the log file could not be created:"
251254
<< path;
@@ -256,7 +259,7 @@ void ThreadLogging::initFs() {
256259
}
257260

258261
// buffered by WriteBuffer
259-
if (!detailedFile->open(QFile::WriteOnly | QFile::Truncate | QFile::Unbuffered)) {
262+
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
260263
qCCritical(logLogging
261264
) << "Could not start detailed filesystem logger as the log file could not be created:"
262265
<< detailedPath;
@@ -287,6 +290,8 @@ void ThreadLogging::initFs() {
287290
sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
288291
}
289292

293+
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
294+
290295
this->detailedFile = detailedFile;
291296
this->detailedWriter.setDevice(detailedFile);
292297

‎src/core/main.cpp

+451-335
Large diffs are not rendered by default.

‎src/core/paths.cpp

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <qtenvironmentvariables.h>
1010
#include <unistd.h>
1111

12+
#include "common.hpp"
13+
1214
Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg);
1315

1416
QsPaths* QsPaths::instance() {
@@ -18,6 +20,15 @@ QsPaths* QsPaths::instance() {
1820

1921
void QsPaths::init(QString shellId) { QsPaths::instance()->shellId = std::move(shellId); }
2022

23+
QDir QsPaths::crashDir(const QString& shellId, const QDateTime& launchTime) {
24+
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
25+
dir = QDir(dir.filePath("crashes"));
26+
dir = QDir(dir.filePath(shellId));
27+
dir = QDir(dir.filePath(QString("run-%1").arg(launchTime.toMSecsSinceEpoch())));
28+
29+
return dir;
30+
}
31+
2132
QDir* QsPaths::cacheDir() {
2233
if (this->cacheState == DirState::Unknown) {
2334
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
@@ -73,7 +84,7 @@ QDir* QsPaths::instanceRunDir() {
7384
this->instanceRunState = DirState::Failed;
7485
} else {
7586
this->mInstanceRunDir =
76-
runtimeDir->filePath(QString("run-%1").arg(QDateTime::currentMSecsSinceEpoch()));
87+
runtimeDir->filePath(QString("run-%1").arg(qs::Common::LAUNCH_TIME.toMSecsSinceEpoch()));
7788

7889
qCDebug(logPaths) << "Initialized instance runtime path:" << this->mInstanceRunDir.path();
7990

‎src/core/paths.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#pragma once
2+
#include <qdatetime.h>
23
#include <qdir.h>
34

45
class QsPaths {
56
public:
67
static QsPaths* instance();
78
static void init(QString shellId);
9+
static QDir crashDir(const QString& shellId, const QDateTime& launchTime);
810

911
QDir* cacheDir();
1012
QDir* runDir();

‎src/crash/CMakeLists.txt

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
qt_add_library(quickshell-crash STATIC
2+
main.cpp
3+
interface.cpp
4+
handler.cpp
5+
)
6+
7+
qs_pch(quickshell-crash)
8+
9+
find_package(PkgConfig REQUIRED)
10+
pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
11+
# only need client?? take only includes from pkg config todo
12+
target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
13+
14+
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt6::Widgets)
15+
16+
target_link_libraries(quickshell-core PRIVATE quickshell-crash)

‎src/crash/handler.cpp

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#include "handler.hpp"
2+
#include <array>
3+
#include <cstdio>
4+
#include <cstring>
5+
6+
#include <bits/types/sigset_t.h>
7+
#include <breakpad/client/linux/handler/exception_handler.h>
8+
#include <breakpad/client/linux/handler/minidump_descriptor.h>
9+
#include <breakpad/common/linux/linux_libc_support.h>
10+
#include <qdatastream.h>
11+
#include <qfile.h>
12+
#include <qlogging.h>
13+
#include <qloggingcategory.h>
14+
#include <sys/mman.h>
15+
#include <unistd.h>
16+
17+
#include "../core/crashinfo.hpp"
18+
19+
extern char** environ; // NOLINT
20+
21+
using namespace google_breakpad;
22+
23+
namespace qs::crash {
24+
25+
Q_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
26+
27+
struct CrashHandlerPrivate {
28+
ExceptionHandler* exceptionHandler = nullptr;
29+
int minidumpFd = -1;
30+
int infoFd = -1;
31+
32+
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
33+
};
34+
35+
CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
36+
37+
void CrashHandler::init() {
38+
// MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
39+
auto createHandler = [this](const MinidumpDescriptor& desc) {
40+
this->d->exceptionHandler = new ExceptionHandler(
41+
desc,
42+
nullptr,
43+
&CrashHandlerPrivate::minidumpCallback,
44+
this->d,
45+
true,
46+
-1
47+
);
48+
};
49+
50+
qCDebug(logCrashHandler) << "Starting crash handler...";
51+
52+
this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
53+
54+
if (this->d->minidumpFd == -1) {
55+
qCCritical(logCrashHandler
56+
) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
57+
createHandler(MinidumpDescriptor("."));
58+
} else {
59+
qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
60+
<< "for holding possible minidumps.";
61+
createHandler(MinidumpDescriptor(this->d->minidumpFd));
62+
}
63+
64+
qCInfo(logCrashHandler) << "Crash handler initialized.";
65+
}
66+
67+
void CrashHandler::setInstanceInfo(const InstanceInfo& info) {
68+
this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
69+
70+
if (this->d->infoFd == -1) {
71+
qCCritical(logCrashHandler
72+
) << "Failed to allocate instance info memfd, crash recovery will not work.";
73+
return;
74+
}
75+
76+
QFile file;
77+
file.open(this->d->infoFd, QFile::ReadWrite);
78+
79+
QDataStream ds(&file);
80+
ds << info;
81+
file.flush();
82+
83+
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
84+
}
85+
86+
CrashHandler::~CrashHandler() {
87+
delete this->d->exceptionHandler;
88+
delete this->d;
89+
}
90+
91+
bool CrashHandlerPrivate::minidumpCallback(
92+
const MinidumpDescriptor& /*descriptor*/,
93+
void* context,
94+
bool /*success*/
95+
) {
96+
// A fork that just dies to ensure the coredump is caught by the system.
97+
auto coredumpPid = fork();
98+
99+
if (coredumpPid == 0) {
100+
return false;
101+
}
102+
103+
auto* self = static_cast<CrashHandlerPrivate*>(context);
104+
105+
auto exe = std::array<char, 4096>();
106+
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
107+
perror("Failed to find crash reporter executable.\n");
108+
_exit(-1);
109+
}
110+
111+
auto arg = std::array<char*, 2> {exe.data(), nullptr};
112+
113+
auto env = std::array<char*, 4096>();
114+
auto envi = 0;
115+
116+
auto infoFd = dup(self->infoFd);
117+
auto infoFdStr = std::array<char, 38>();
118+
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
119+
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
120+
env[envi++] = infoFdStr.data();
121+
122+
auto corePidStr = std::array<char, 39>();
123+
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
124+
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
125+
env[envi++] = corePidStr.data();
126+
127+
auto populateEnv = [&]() {
128+
auto senvi = 0;
129+
while (envi < 4095) {
130+
env[envi++] = environ[senvi++]; // NOLINT
131+
}
132+
133+
env[envi] = nullptr;
134+
};
135+
136+
sigset_t sigset;
137+
sigemptyset(&sigset); // NOLINT (include)
138+
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
139+
140+
auto pid = fork();
141+
142+
if (pid == -1) {
143+
perror("Failed to fork and launch crash reporter.\n");
144+
return false;
145+
} else if (pid == 0) {
146+
// dup to remove CLOEXEC
147+
// if already -1 will return -1
148+
auto dumpFd = dup(self->minidumpFd);
149+
auto logFd = dup(CrashInfo::INSTANCE.logFd);
150+
151+
// allow up to 10 digits, which should never happen
152+
auto dumpFdStr = std::array<char, 38>();
153+
auto logFdStr = std::array<char, 37>();
154+
155+
memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
156+
memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
157+
158+
if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
159+
if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
160+
161+
env[envi++] = dumpFdStr.data();
162+
env[envi++] = logFdStr.data();
163+
164+
populateEnv();
165+
execve(exe.data(), arg.data(), env.data());
166+
167+
perror("Failed to launch crash reporter.\n");
168+
_exit(-1);
169+
} else {
170+
populateEnv();
171+
execve(exe.data(), arg.data(), env.data());
172+
173+
perror("Failed to relaunch quickshell.\n");
174+
_exit(-1);
175+
}
176+
177+
return false; // should make sure it hits the system coredump handler
178+
}
179+
180+
} // namespace qs::crash

‎src/crash/handler.hpp

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <qtclasshelpermacros.h>
4+
5+
#include "../core/crashinfo.hpp"
6+
namespace qs::crash {
7+
8+
struct CrashHandlerPrivate;
9+
10+
class CrashHandler {
11+
public:
12+
explicit CrashHandler();
13+
~CrashHandler();
14+
Q_DISABLE_COPY_MOVE(CrashHandler);
15+
16+
void init();
17+
void setInstanceInfo(const InstanceInfo& info);
18+
19+
private:
20+
CrashHandlerPrivate* d;
21+
};
22+
23+
} // namespace qs::crash

‎src/crash/interface.cpp

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include "interface.hpp"
2+
#include <utility>
3+
4+
#include <qapplication.h>
5+
#include <qboxlayout.h>
6+
#include <qdesktopservices.h>
7+
#include <qfont.h>
8+
#include <qfontinfo.h>
9+
#include <qlabel.h>
10+
#include <qnamespace.h>
11+
#include <qobject.h>
12+
#include <qpushbutton.h>
13+
#include <qwidget.h>
14+
15+
#include "build.hpp"
16+
17+
class ReportLabel: public QWidget {
18+
public:
19+
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
20+
auto* layout = new QHBoxLayout(this);
21+
layout->setContentsMargins(0, 0, 0, 0);
22+
layout->addWidget(new QLabel(label, this));
23+
24+
auto* cl = new QLabel(content, this);
25+
cl->setTextInteractionFlags(Qt::TextSelectableByMouse);
26+
layout->addWidget(cl);
27+
28+
layout->addStretch();
29+
}
30+
};
31+
32+
CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
33+
: reportFolder(std::move(reportFolder)) {
34+
this->setWindowFlags(Qt::Window);
35+
36+
auto textHeight = QFontInfo(QFont()).pixelSize();
37+
38+
auto* mainLayout = new QVBoxLayout(this);
39+
40+
mainLayout->addWidget(new QLabel(
41+
"<u>Quickshell has crashed. Please submit a bug report to help us fix it.</u>",
42+
this
43+
));
44+
45+
mainLayout->addSpacing(textHeight);
46+
47+
mainLayout->addWidget(new QLabel("General information", this));
48+
mainLayout->addWidget(new ReportLabel("Git Revision:", GIT_REVISION, this));
49+
mainLayout->addWidget(new ReportLabel("Crashed process ID:", QString::number(pid), this));
50+
mainLayout->addWidget(new ReportLabel("Crash report folder:", this->reportFolder, this));
51+
mainLayout->addSpacing(textHeight);
52+
53+
mainLayout->addWidget(new QLabel("Please open a bug report for this issue via github or email."));
54+
55+
mainLayout->addWidget(new ReportLabel(
56+
"Github:",
57+
"https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml",
58+
this
59+
));
60+
61+
mainLayout->addWidget(new ReportLabel("Email:", "quickshell-bugs@outfoxxed.me", this));
62+
63+
auto* buttons = new QWidget(this);
64+
buttons->setMinimumWidth(900);
65+
auto* buttonLayout = new QHBoxLayout(buttons);
66+
buttonLayout->setContentsMargins(0, 0, 0, 0);
67+
68+
auto* reportButton = new QPushButton("Open report page", buttons);
69+
reportButton->setDefault(true);
70+
QObject::connect(reportButton, &QPushButton::clicked, this, &CrashReporterGui::openReportUrl);
71+
buttonLayout->addWidget(reportButton);
72+
73+
auto* openFolderButton = new QPushButton("Open crash folder", buttons);
74+
QObject::connect(openFolderButton, &QPushButton::clicked, this, &CrashReporterGui::openFolder);
75+
buttonLayout->addWidget(openFolderButton);
76+
77+
auto* cancelButton = new QPushButton("Exit", buttons);
78+
QObject::connect(cancelButton, &QPushButton::clicked, this, &CrashReporterGui::cancel);
79+
buttonLayout->addWidget(cancelButton);
80+
81+
mainLayout->addWidget(buttons);
82+
83+
mainLayout->addStretch();
84+
this->setFixedSize(this->sizeHint());
85+
}
86+
87+
void CrashReporterGui::openFolder() {
88+
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
89+
}
90+
91+
void CrashReporterGui::openReportUrl() {
92+
QDesktopServices::openUrl(
93+
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
94+
);
95+
}
96+
97+
void CrashReporterGui::cancel() { QApplication::quit(); }

‎src/crash/interface.hpp

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <qwidget.h>
4+
5+
class CrashReporterGui: public QWidget {
6+
public:
7+
CrashReporterGui(QString reportFolder, int pid);
8+
9+
private slots:
10+
void openFolder();
11+
12+
static void openReportUrl();
13+
static void cancel();
14+
15+
private:
16+
QString reportFolder;
17+
};

‎src/crash/main.cpp

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#include "main.hpp"
2+
#include <cerrno>
3+
#include <cstdlib>
4+
5+
#include <qapplication.h>
6+
#include <qconfig.h>
7+
#include <qdatastream.h>
8+
#include <qdatetime.h>
9+
#include <qdir.h>
10+
#include <qfile.h>
11+
#include <qlogging.h>
12+
#include <qloggingcategory.h>
13+
#include <qtenvironmentvariables.h>
14+
#include <qtextstream.h>
15+
#include <sys/sendfile.h>
16+
#include <sys/types.h>
17+
18+
#include "../core/crashinfo.hpp"
19+
#include "../core/logging.hpp"
20+
#include "../core/paths.hpp"
21+
#include "build.hpp"
22+
#include "interface.hpp"
23+
24+
Q_LOGGING_CATEGORY(logCrashReporter, "quickshell.crashreporter", QtWarningMsg);
25+
26+
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instanceInfo);
27+
28+
void qsCheckCrash(int argc, char** argv) {
29+
auto fd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD");
30+
if (fd.isEmpty()) return;
31+
auto app = QApplication(argc, argv);
32+
33+
InstanceInfo instance;
34+
35+
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
36+
37+
{
38+
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
39+
40+
QFile file;
41+
file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
42+
file.seek(0);
43+
44+
auto ds = QDataStream(&file);
45+
ds >> instance;
46+
}
47+
48+
LogManager::init(!instance.noColor, false);
49+
auto crashDir = QsPaths::crashDir(instance.shellId, instance.launchTime);
50+
51+
qCInfo(logCrashReporter) << "Starting crash reporter...";
52+
53+
recordCrashInfo(crashDir, instance);
54+
55+
auto gui = CrashReporterGui(crashDir.path(), crashProc);
56+
gui.show();
57+
exit(QApplication::exec()); // NOLINT
58+
}
59+
60+
int tryDup(int fd, const QString& path) {
61+
QFile sourceFile;
62+
if (!sourceFile.open(fd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
63+
qCCritical(logCrashReporter) << "Failed to open source fd for duplication.";
64+
return 1;
65+
}
66+
67+
auto destFile = QFile(path);
68+
if (!destFile.open(QFile::WriteOnly)) {
69+
qCCritical(logCrashReporter) << "Failed to open dest file for duplication.";
70+
return 2;
71+
}
72+
73+
auto size = sourceFile.size();
74+
off_t offset = 0;
75+
ssize_t count = 0;
76+
77+
sourceFile.seek(0);
78+
79+
while (count != size) {
80+
auto r = sendfile(destFile.handle(), sourceFile.handle(), &offset, sourceFile.size());
81+
if (r == -1) {
82+
qCCritical(logCrashReporter).nospace()
83+
<< "Failed to duplicate fd " << fd << " with error code " << errno
84+
<< ". Error: " << qt_error_string();
85+
return 3;
86+
} else {
87+
count += r;
88+
}
89+
}
90+
91+
return 0;
92+
}
93+
94+
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
95+
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
96+
97+
if (!crashDir.mkpath(".")) {
98+
qCCritical(logCrashReporter) << "Failed to create folder" << crashDir
99+
<< "to save crash information.";
100+
return;
101+
}
102+
103+
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
104+
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
105+
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
106+
107+
qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
108+
auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp"));
109+
if (dumpDupStatus != 0) {
110+
qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
111+
}
112+
113+
qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
114+
auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog"));
115+
if (logDupStatus != 0) {
116+
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
117+
}
118+
119+
{
120+
auto extraInfoFile = QFile(crashDir.filePath("info.txt"));
121+
if (!extraInfoFile.open(QFile::WriteOnly)) {
122+
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
123+
} else {
124+
auto stream = QTextStream(&extraInfoFile);
125+
stream << "===== Quickshell Crash =====\n";
126+
stream << "Git Revision: " << GIT_REVISION << '\n';
127+
stream << "Crashed process ID: " << crashProc << '\n';
128+
stream << "Run ID: " << QString("run-%1").arg(instance.launchTime.toMSecsSinceEpoch())
129+
<< '\n';
130+
131+
stream << "\n===== Shell Information =====\n";
132+
stream << "Shell ID: " << instance.shellId << '\n';
133+
stream << "Config Path: " << instance.configPath << '\n';
134+
135+
stream << "\n===== Report Integrity =====\n";
136+
stream << "Minidump save status: " << dumpDupStatus << '\n';
137+
stream << "Log save status: " << logDupStatus << '\n';
138+
139+
stream << "\n===== System Information =====\n";
140+
stream << "Qt Version: " << QT_VERSION_STR << "\n\n";
141+
142+
stream << "/etc/os-release:";
143+
auto osReleaseFile = QFile("/etc/os-release");
144+
if (osReleaseFile.open(QFile::ReadOnly)) {
145+
stream << '\n' << osReleaseFile.readAll() << '\n';
146+
osReleaseFile.close();
147+
} else {
148+
stream << "FAILED TO OPEN\n";
149+
}
150+
151+
stream << "/etc/lsb-release:";
152+
auto lsbReleaseFile = QFile("/etc/lsb-release");
153+
if (lsbReleaseFile.open(QFile::ReadOnly)) {
154+
stream << '\n' << lsbReleaseFile.readAll() << '\n';
155+
lsbReleaseFile.close();
156+
} else {
157+
stream << "FAILED TO OPEN\n";
158+
}
159+
160+
extraInfoFile.close();
161+
}
162+
}
163+
164+
qCDebug(logCrashReporter) << "Recorded crash information.";
165+
}

‎src/crash/main.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
void qsCheckCrash(int argc, char** argv);

‎src/services/status_notifier/host.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <qtmetamacros.h>
1212
#include <unistd.h>
1313

14+
#include "../../core/common.hpp"
1415
#include "../../dbus/properties.hpp"
1516
#include "dbus_watcher_interface.h"
1617
#include "item.hpp"
@@ -31,7 +32,10 @@ StatusNotifierHost::StatusNotifierHost(QObject* parent): QObject(parent) {
3132
return;
3233
}
3334

34-
this->hostId = QString("org.kde.StatusNotifierHost-") + QString::number(getpid());
35+
this->hostId = QString("org.kde.StatusNotifierHost-%1-%2")
36+
.arg(QString::number(getpid()))
37+
.arg(QString::number(qs::Common::LAUNCH_TIME.toMSecsSinceEpoch()));
38+
3539
auto success = bus.registerService(this->hostId);
3640

3741
if (!success) {
@@ -98,7 +102,7 @@ void StatusNotifierHost::connectToWatcher() {
98102
[this](QStringList value, QDBusError error) { // NOLINT
99103
if (error.isValid()) {
100104
qCWarning(logStatusNotifierHost).noquote()
101-
<< "Error reading \"RegisteredStatusNotifierITems\" property of watcher"
105+
<< "Error reading \"RegisteredStatusNotifierItems\" property of watcher"
102106
<< this->watcher->service();
103107

104108
qCWarning(logStatusNotifierHost) << error;

0 commit comments

Comments
 (0)
Please sign in to comment.