forked from qt-creator/qt-creator
CrashHandler: Add "Attach and Debug" button.
This will launch a new instance of Qt Creator attaching to the crashed instance. The 'Restart' functionality is now represented as a check box since it would be confusing to have one button that restarts the app and quits the crash handler and another one that starts a debugger but keeps the crash handler open (which is necessary, otherwise the crashed app will quit). Change-Id: Id88f418ff73ab7bc72b05753ce2b61bbef8f30cf Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
This commit is contained in:
@@ -78,6 +78,16 @@ void BacktraceCollector::run(Q_PID pid)
|
||||
);
|
||||
}
|
||||
|
||||
bool BacktraceCollector::isRunning() const
|
||||
{
|
||||
return d->debugger.state() == QProcess::Running;
|
||||
}
|
||||
|
||||
void BacktraceCollector::kill()
|
||||
{
|
||||
d->debugger.kill();
|
||||
}
|
||||
|
||||
void BacktraceCollector::onDebuggerFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/)
|
||||
{
|
||||
if (d->errorOccurred) {
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
~BacktraceCollector();
|
||||
|
||||
void run(Q_PID pid);
|
||||
bool isRunning() const;
|
||||
void kill();
|
||||
|
||||
signals:
|
||||
void error(const QString &errorMessage);
|
||||
|
||||
@@ -33,13 +33,17 @@
|
||||
#include "backtracecollector.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <utils/environment.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QRegExp>
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -48,9 +52,11 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static const char FileDistroInformation[] = "/etc/lsb-release";
|
||||
static const char FileKernelVersion[] = "/proc/version";
|
||||
static const char QtCreatorExecutable[] = "qtcreator";
|
||||
|
||||
static QString collectLinuxDistributionInfo()
|
||||
{
|
||||
@@ -62,33 +68,40 @@ static QString collectKernelVersionInfo()
|
||||
return QString::fromLatin1(fileContents(QLatin1String(FileKernelVersion)));
|
||||
}
|
||||
|
||||
// Convience class for interacting with exec() family of functions.
|
||||
class CExecList : public QVector<char *>
|
||||
{
|
||||
public:
|
||||
CExecList(const QStringList &list)
|
||||
{
|
||||
foreach (const QString &item, list)
|
||||
append(qstrdup(item.toLatin1().data()));
|
||||
append(0);
|
||||
}
|
||||
|
||||
~CExecList()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i)
|
||||
delete[] value(i);
|
||||
}
|
||||
};
|
||||
|
||||
class CrashHandlerPrivate
|
||||
{
|
||||
public:
|
||||
CrashHandlerPrivate(pid_t pid, CrashHandler *crashHandler)
|
||||
: pid(pid), dialog(crashHandler), argv(0), envp(0) {}
|
||||
: pid(pid),
|
||||
creatorInPath(Utils::Environment::systemEnvironment().searchInPath(QtCreatorExecutable)),
|
||||
dialog(crashHandler) {}
|
||||
|
||||
~CrashHandlerPrivate()
|
||||
{
|
||||
if (argv) {
|
||||
for (int i = 0; argv[i]; ++i)
|
||||
delete[] argv[i];
|
||||
}
|
||||
if (envp) {
|
||||
for (int i = 0; envp[i]; ++i)
|
||||
delete[] envp[i];
|
||||
}
|
||||
free(argv);
|
||||
free(envp);
|
||||
}
|
||||
const pid_t pid;
|
||||
const QString creatorInPath; // Backup debugger.
|
||||
|
||||
pid_t pid;
|
||||
BacktraceCollector backtraceCollector;
|
||||
CrashHandlerDialog dialog;
|
||||
|
||||
// For restarting the process.
|
||||
char **argv;
|
||||
char **envp;
|
||||
QStringList restartAppCommandLine;
|
||||
QStringList restartAppEnvironment;
|
||||
};
|
||||
|
||||
CrashHandler::CrashHandler(pid_t pid, QObject *parent)
|
||||
@@ -100,10 +113,14 @@ CrashHandler::CrashHandler(pid_t pid, QObject *parent)
|
||||
|
||||
d->dialog.appendDebugInfo(collectKernelVersionInfo());
|
||||
d->dialog.appendDebugInfo(collectLinuxDistributionInfo());
|
||||
d->dialog.show();
|
||||
|
||||
if (!collectRestartAppData()) // If we can't restart the app properly, ...
|
||||
d->dialog.disableRestartAppButton();
|
||||
if (!collectRestartAppData()) {
|
||||
d->dialog.disableRestartAppCheckBox();
|
||||
if (d->creatorInPath.isEmpty())
|
||||
d->dialog.disableDebugAppButton();
|
||||
}
|
||||
|
||||
d->dialog.show();
|
||||
}
|
||||
|
||||
CrashHandler::~CrashHandler()
|
||||
@@ -130,8 +147,7 @@ void CrashHandler::onError(const QString &errorMessage)
|
||||
void CrashHandler::onBacktraceChunk(const QString &chunk)
|
||||
{
|
||||
d->dialog.appendDebugInfo(chunk);
|
||||
QTextStream out(stdout);
|
||||
out << chunk;
|
||||
QTextStream(stdout) << chunk;
|
||||
}
|
||||
|
||||
void CrashHandler::onBacktraceFinished(const QString &backtrace)
|
||||
@@ -164,54 +180,40 @@ bool CrashHandler::collectRestartAppData()
|
||||
{
|
||||
const QString procDir = QString::fromLatin1("/proc/%1").arg(d->pid);
|
||||
|
||||
// Construct d->argv.
|
||||
// Get command line.
|
||||
// man 5 proc: /proc/[pid]/cmdline
|
||||
// The command-line arguments appear in this file as a set of strings separated by
|
||||
// null bytes ('\0'), with a further null byte after the last string.
|
||||
const QString procCmdFileName = procDir + QLatin1String("/cmdline");
|
||||
QList<QByteArray> cmdEntries = fileContents(procCmdFileName).split('\0');
|
||||
if (cmdEntries.size() < 2) {
|
||||
QList<QByteArray> commandLine = fileContents(procCmdFileName).split('\0');
|
||||
if (commandLine.size() < 2) {
|
||||
qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procCmdFileName));
|
||||
return false;
|
||||
}
|
||||
cmdEntries.removeLast();
|
||||
char * const executable = qstrdup(qPrintable(cmdEntries.takeFirst()));
|
||||
d->argv = (char **) malloc(sizeof(char*) * (cmdEntries.size() + 2));
|
||||
if (d->argv == 0)
|
||||
qFatal("%s: malloc() failed.\n", Q_FUNC_INFO);
|
||||
d->argv[0] = executable;
|
||||
int i;
|
||||
for (i = 1; i <= cmdEntries.size(); ++i)
|
||||
d->argv[i] = qstrdup(cmdEntries.at(i-1));
|
||||
d->argv[i] = 0;
|
||||
commandLine.removeLast();
|
||||
foreach (const QByteArray &item, commandLine)
|
||||
d->restartAppCommandLine.append(QString::fromLatin1(item));
|
||||
|
||||
// Construct d->envp.
|
||||
// Get environment.
|
||||
// man 5 proc: /proc/[pid]/environ
|
||||
// The entries are separated by null bytes ('\0'), and there may be a null byte at the end.
|
||||
// The entries are separated by null bytes ('\0'), and there may be a null byte at the end.
|
||||
const QString procEnvFileName = procDir + QLatin1String("/environ");
|
||||
QList<QByteArray> envEntries = fileContents(procEnvFileName).split('\0');
|
||||
if (envEntries.isEmpty()) {
|
||||
QList<QByteArray> environment = fileContents(procEnvFileName).split('\0');
|
||||
if (environment.isEmpty()) {
|
||||
qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procEnvFileName));
|
||||
return false;
|
||||
}
|
||||
if (envEntries.last().isEmpty())
|
||||
envEntries.removeLast();
|
||||
d->envp = (char **) malloc(sizeof(char*) * (envEntries.size() + 1));
|
||||
if (d->envp == 0)
|
||||
qFatal("%s: malloc() failed.\n", Q_FUNC_INFO);
|
||||
for (i = 0; i < envEntries.size(); ++i)
|
||||
d->envp[i] = qstrdup(envEntries.at(i));
|
||||
d->envp[i] = 0;
|
||||
if (environment.last().isEmpty())
|
||||
environment.removeLast();
|
||||
foreach (const QByteArray &item, environment)
|
||||
d->restartAppEnvironment.append(QString::fromLatin1(item));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashHandler::restartApplication()
|
||||
void CrashHandler::runCommand(QStringList commandLine, QStringList environment, WaitMode waitMode)
|
||||
{
|
||||
// TODO: If QTBUG-2284 is resolved, use QProcess::startDetached() here.
|
||||
// Close the crash handler and start the process again with same environment and
|
||||
// command line arguments.
|
||||
//
|
||||
// We can't use QProcess::startDetached because of bug
|
||||
//
|
||||
// QTBUG-2284
|
||||
@@ -224,15 +226,19 @@ void CrashHandler::restartApplication()
|
||||
case -1: // error
|
||||
qFatal("%s: fork() failed.", Q_FUNC_INFO);
|
||||
break;
|
||||
case 0: // child
|
||||
qDebug("Restarting Qt Creator with\n");
|
||||
for (int i = 0; d->argv[i]; ++i)
|
||||
qDebug(" %s", d->argv[i]);
|
||||
qDebug("\nand environment\n");
|
||||
for (int i = 0; d->envp[i]; ++i)
|
||||
qDebug(" %s", d->envp[i]);
|
||||
case 0: { // child
|
||||
CExecList argv(commandLine);
|
||||
CExecList envp(environment);
|
||||
qDebug("Running\n");
|
||||
for (int i = 0; argv[i]; ++i)
|
||||
qDebug(" %s", argv[i]);
|
||||
if (!environment.isEmpty()) {
|
||||
qDebug("\nwith environment:\n");
|
||||
for (int i = 0; envp[i]; ++i)
|
||||
qDebug(" %s", envp[i]);
|
||||
}
|
||||
|
||||
// The standards pipes must be open, otherwise the restarted Qt Creator will
|
||||
// The standards pipes must be open, otherwise the application will
|
||||
// receive a SIGPIPE as soon as these are used.
|
||||
if (freopen("/dev/null", "r", stdin) == 0)
|
||||
qFatal("%s: freopen() failed for stdin: %s.\n", Q_FUNC_INFO, strerror(errno));
|
||||
@@ -241,10 +247,68 @@ void CrashHandler::restartApplication()
|
||||
if (freopen("/dev/null", "w", stderr) == 0)
|
||||
qFatal("%s: freopen() failed for stderr: %s.\n.", Q_FUNC_INFO, strerror(errno));
|
||||
|
||||
execve(d->argv[0], d->argv, d->envp);
|
||||
if (environment.isEmpty())
|
||||
execv(argv[0], argv.data());
|
||||
else
|
||||
execve(argv[0], argv.data(), envp.data());
|
||||
_exit(EXIT_FAILURE);
|
||||
default: // parent
|
||||
qApp->quit();
|
||||
} default: // parent
|
||||
if (waitMode == WaitForExit) {
|
||||
while (true) {
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
if (errno == EINTR) // Signal handler of QProcess for SIGCHLD was triggered.
|
||||
continue;
|
||||
perror("waitpid() failed unexpectedly");
|
||||
}
|
||||
if (WIFEXITED(status)) {
|
||||
qDebug("Child exited with exit code %d.", WEXITSTATUS(status));
|
||||
break;
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
qDebug("Child terminated by signal %d.", WTERMSIG(status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandler::restartApplication()
|
||||
{
|
||||
runCommand(d->restartAppCommandLine, d->restartAppEnvironment, DontWaitForExit);
|
||||
}
|
||||
|
||||
void CrashHandler::debugApplication()
|
||||
{
|
||||
// User requested to debug the app while our debugger is running.
|
||||
if (d->backtraceCollector.isRunning()) {
|
||||
if (!d->dialog.runDebuggerWhileBacktraceNotFinished())
|
||||
return;
|
||||
if (d->backtraceCollector.isRunning()) {
|
||||
d->backtraceCollector.disconnect();
|
||||
d->backtraceCollector.kill();
|
||||
d->dialog.setToFinalState();
|
||||
d->dialog.appendDebugInfo(tr("\n\nCollecting backtrace aborted by user."));
|
||||
QCoreApplication::processEvents(); // Show the last appended output immediately.
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare command.
|
||||
QString executable = d->creatorInPath;
|
||||
if (!d->restartAppCommandLine.isEmpty())
|
||||
executable = d->restartAppCommandLine.at(0);
|
||||
const QStringList commandLine = QStringList()
|
||||
<< executable
|
||||
<< QLatin1String("-debug")
|
||||
<< QString::number(d->pid);
|
||||
|
||||
QStringList environment;
|
||||
if (!d->restartAppEnvironment.isEmpty())
|
||||
environment = d->restartAppEnvironment;
|
||||
|
||||
// The UI is blocked/frozen anyway, so hide the dialog while debugging.
|
||||
d->dialog.hide();
|
||||
runCommand(commandLine, environment, WaitForExit);
|
||||
d->dialog.show();
|
||||
}
|
||||
|
||||
@@ -55,10 +55,14 @@ public slots:
|
||||
|
||||
void openBugTracker();
|
||||
void restartApplication();
|
||||
void debugApplication();
|
||||
|
||||
private:
|
||||
bool collectRestartAppData();
|
||||
|
||||
enum WaitMode { WaitForExit, DontWaitForExit };
|
||||
static void runCommand(QStringList commandLine, QStringList environment, WaitMode waitMode);
|
||||
|
||||
CrashHandlerPrivate *d;
|
||||
};
|
||||
|
||||
|
||||
@@ -34,9 +34,15 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <app/app_version.h>
|
||||
#include <utils/checkablemessagebox.h>
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QIcon>
|
||||
#include <QSettings>
|
||||
|
||||
static const char SettingsApplication[] = "QtCreator";
|
||||
static const char SettingsKeySkipWarningAbortingBacktrace[]
|
||||
= "CrashHandler/SkipWarningAbortingBacktrace";
|
||||
|
||||
CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
@@ -59,8 +65,8 @@ CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) :
|
||||
|
||||
connect(m_ui->copyToClipBoardButton, SIGNAL(clicked()), this, SLOT(copyToClipboardClicked()));
|
||||
connect(m_ui->reportBugButton, SIGNAL(clicked()), m_crashHandler, SLOT(openBugTracker()));
|
||||
connect(m_ui->restartAppButton, SIGNAL(clicked()), m_crashHandler, SLOT(restartApplication()));
|
||||
connect(m_ui->closeButton, SIGNAL(clicked()), qApp, SLOT(quit()));
|
||||
connect(m_ui->debugAppButton, SIGNAL(clicked()), m_crashHandler, SLOT(debugApplication()));
|
||||
connect(m_ui->closeButton, SIGNAL(clicked()), this, SLOT(close()));
|
||||
|
||||
setApplicationInfo();
|
||||
}
|
||||
@@ -70,6 +76,34 @@ CrashHandlerDialog::~CrashHandlerDialog()
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
bool CrashHandlerDialog::runDebuggerWhileBacktraceNotFinished()
|
||||
{
|
||||
// Check settings.
|
||||
QSettings settings(QSettings::IniFormat, QSettings::UserScope,
|
||||
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR),
|
||||
QLatin1String(SettingsApplication));
|
||||
if (settings.value(QLatin1String(SettingsKeySkipWarningAbortingBacktrace), false).toBool())
|
||||
return true;
|
||||
|
||||
// Ask user.
|
||||
const QString title = tr("Run Debugger And Abort Collecting Backtrace?");
|
||||
const QString message = tr(
|
||||
"<html><head/><body>"
|
||||
"<p><b>Run the debugger and abort collecting backtrace?</b></p>"
|
||||
"<p>You have requested to run the debugger while collecting the backtrace was not "
|
||||
"finished.</p>"
|
||||
"</body></html>");
|
||||
const QString checkBoxText = tr("Do not &ask again.");
|
||||
bool checkBoxSetting = false;
|
||||
const QDialogButtonBox::StandardButton button = Utils::CheckableMessageBox::question(this,
|
||||
title, message, checkBoxText, &checkBoxSetting,
|
||||
QDialogButtonBox::Yes|QDialogButtonBox::No, QDialogButtonBox::No);
|
||||
if (checkBoxSetting)
|
||||
settings.setValue(QLatin1String(SettingsKeySkipWarningAbortingBacktrace), checkBoxSetting);
|
||||
|
||||
return button == QDialogButtonBox::Yes;
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::setToFinalState()
|
||||
{
|
||||
m_ui->progressBar->hide();
|
||||
@@ -77,9 +111,14 @@ void CrashHandlerDialog::setToFinalState()
|
||||
m_ui->reportBugButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::disableRestartAppButton()
|
||||
void CrashHandlerDialog::disableRestartAppCheckBox()
|
||||
{
|
||||
m_ui->restartAppButton->setDisabled(true);
|
||||
m_ui->restartAppCheckBox->setDisabled(true);
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::disableDebugAppButton()
|
||||
{
|
||||
m_ui->debugAppButton->setDisabled(true);
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::setApplicationInfo()
|
||||
@@ -130,3 +169,10 @@ void CrashHandlerDialog::copyToClipboardClicked()
|
||||
{
|
||||
QApplication::clipboard()->setText(m_ui->debugInfoEdit->toPlainText());
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::close()
|
||||
{
|
||||
if (m_ui->restartAppCheckBox->isEnabled() && m_ui->restartAppCheckBox->isChecked())
|
||||
m_crashHandler->restartApplication();
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
@@ -50,14 +50,17 @@ public:
|
||||
~CrashHandlerDialog();
|
||||
|
||||
public:
|
||||
void disableRestartAppButton();
|
||||
void setApplicationInfo();
|
||||
void setToFinalState();
|
||||
void appendDebugInfo(const QString &chunk);
|
||||
void selectLineWithContents(const QString &text);
|
||||
void setToFinalState();
|
||||
void disableRestartAppCheckBox();
|
||||
void disableDebugAppButton();
|
||||
bool runDebuggerWhileBacktraceNotFinished();
|
||||
|
||||
private slots:
|
||||
void copyToClipboardClicked();
|
||||
void close();
|
||||
|
||||
private:
|
||||
CrashHandler *m_crashHandler;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<width>500</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
@@ -64,6 +64,16 @@
|
||||
<item>
|
||||
<widget class="QTextEdit" name="debugInfoEdit"/>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QCheckBox" name="restartAppCheckBox">
|
||||
<property name="text">
|
||||
<string>&Restart Qt Creator on close</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
@@ -71,6 +81,9 @@
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Copy the whole contents to clipboard.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>C&opy to clipboard</string>
|
||||
</property>
|
||||
@@ -81,6 +94,9 @@
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the bug tracker web site.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Report this &bug</string>
|
||||
</property>
|
||||
@@ -100,14 +116,23 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restartAppButton">
|
||||
<widget class="QPushButton" name="debugAppButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Debug the application with a new instance of Qt Creator. During debugging the crash handler will be hidden.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Restart Qt Creator</string>
|
||||
<string>Attach and &Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="toolTip">
|
||||
<string>Quit the handler and the crashed application.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Close</string>
|
||||
</property>
|
||||
|
||||
@@ -11,13 +11,18 @@ SOURCES += \
|
||||
backtracecollector.cpp \
|
||||
crashhandlerdialog.cpp \
|
||||
crashhandler.cpp \
|
||||
utils.cpp
|
||||
utils.cpp \
|
||||
../../libs/utils/checkablemessagebox.cpp \
|
||||
../../libs/utils/environment.cpp
|
||||
|
||||
|
||||
HEADERS += \
|
||||
backtracecollector.h \
|
||||
crashhandlerdialog.h \
|
||||
crashhandler.h \
|
||||
utils.h
|
||||
utils.h \
|
||||
../../libs/utils/checkablemessagebox.h \
|
||||
../../libs/utils/environment.h
|
||||
|
||||
FORMS += \
|
||||
crashhandlerdialog.ui
|
||||
|
||||
@@ -6,7 +6,8 @@ QtcTool {
|
||||
condition: qbs.targetOS == "linux" && qbs.buildVariant == "debug"
|
||||
|
||||
cpp.includePaths: [
|
||||
buildDirectory
|
||||
buildDirectory,
|
||||
"../../libs"
|
||||
]
|
||||
|
||||
Depends { name: "cpp" }
|
||||
@@ -14,6 +15,10 @@ QtcTool {
|
||||
Depends { name: "app_version_header" }
|
||||
|
||||
files: [
|
||||
"../../libs/utils/checkablemessagebox.cpp",
|
||||
"../../libs/utils/checkablemessagebox.h",
|
||||
"../../libs/utils/environment.cpp",
|
||||
"../../libs/utils/environment.h",
|
||||
"backtracecollector.cpp",
|
||||
"backtracecollector.h",
|
||||
"crashhandler.cpp",
|
||||
@@ -23,6 +28,6 @@ QtcTool {
|
||||
"crashhandlerdialog.ui",
|
||||
"main.cpp",
|
||||
"utils.cpp",
|
||||
"utils.h",
|
||||
"utils.h"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user