diff --git a/src/library/main.cpp b/src/library/main.cpp index 1446e9d4..35f5fc3a 100644 --- a/src/library/main.cpp +++ b/src/library/main.cpp @@ -160,6 +160,10 @@ void __attribute__((constructor)) init(void) message = receiveMessage(); } + if (shared_config.sigint_upon_launch) { + raise(SIGINT); + } + /* Set the frame count to the initial frame count */ framecount = shared_config.initial_framecount; diff --git a/src/program/Config.cpp b/src/program/Config.cpp index a0b578f8..2f5893f4 100644 --- a/src/program/Config.cpp +++ b/src/program/Config.cpp @@ -58,6 +58,8 @@ void Config::save(const std::string& gamepath) { } general_settings.endArray(); + general_settings.setValue("debugger", debugger); + /* Open the preferences for the game */ QSettings settings(iniPath(gamepath), QSettings::IniFormat); settings.setFallbacksEnabled(false); @@ -174,6 +176,12 @@ void Config::load(const std::string& gamepath) { } general_settings.endArray(); +#ifdef __unix__ + debugger = general_settings.value("debugger", debugger).toInt(); +#elif defined(__APPLE__) && defined(__MACH__) + debugger = DEBUGGER_LLDB; +#endif + if (gamepath.empty()) return; diff --git a/src/program/Config.h b/src/program/Config.h index d0f7c852..a1daa1ff 100644 --- a/src/program/Config.h +++ b/src/program/Config.h @@ -130,6 +130,17 @@ class Config { /* Load a game-specific config from the config file */ void load(const std::string& gamepath); + enum Debugger { + DEBUGGER_GDB = 0, + DEBUGGER_LLDB = 1, + }; + +#ifdef __unix__ + int debugger = DEBUGGER_GDB; +#elif defined(__APPLE__) && defined(__MACH__) + int debugger = DEBUGGER_LLDB; +#endif + private: QString iniPath(const std::string& gamepath) const; }; diff --git a/src/program/GameThread.cpp b/src/program/GameThread.cpp index 39e7ab2e..1a13820d 100644 --- a/src/program/GameThread.cpp +++ b/src/program/GameThread.cpp @@ -187,11 +187,17 @@ void GameThread::launch(Context *context) } else { if (context->attach_gdb) { -#ifdef __unix__ - std::string cmd = "which gdb"; -#elif defined(__APPLE__) && defined(__MACH__) - std::string cmd = "which lldb"; -#endif + std::string cmd; + + switch (context->config.debugger) { + case Config::DEBUGGER_GDB: + cmd = "which gdb"; + break; + case Config::DEBUGGER_LLDB: + cmd = "which lldb"; + break; + } + FILE *output = popen(cmd.c_str(), "r"); std::array buf; fgets(buf.data(), buf.size(), output); @@ -202,57 +208,68 @@ void GameThread::launch(Context *context) arg_list.push_back(dbgpath); /* Push debugger arguments */ -#ifdef __unix__ - arg_list.push_back("-q"); - arg_list.push_back("-ex"); - - /* LD_PRELOAD must be set inside a gdb - * command to be effective */ - std::string ldpreloadstr = "set exec-wrapper env 'LD_PRELOAD="; - ldpreloadstr += context->libtaspath; - if (!context->old_ld_preload.empty()) { - ldpreloadstr += ":"; - ldpreloadstr += context->old_ld_preload; + switch (context->config.debugger) { + case Config::DEBUGGER_GDB: { + arg_list.push_back("-q"); + arg_list.push_back("-ex"); + + /* LD_PRELOAD must be set inside a gdb + * command to be effective */ + std::string ldpreloadstr = "set exec-wrapper env 'LD_PRELOAD="; + ldpreloadstr += context->libtaspath; + if (!context->old_ld_preload.empty()) { + ldpreloadstr += ":"; + ldpreloadstr += context->old_ld_preload; + } + ldpreloadstr += "'"; + arg_list.push_back(ldpreloadstr); + + /* We are using SIGSYS and SIGXFSZ for savestates, so don't + * print and pause when one signal is sent * + * Signals SIGPWR SIGXCPU SIG35 and SIG36 are used a lot in some games */ + arg_list.push_back("-ex"); + arg_list.push_back("handle SIGSYS SIGXFSZ SIGUSR1 SIGUSR2 SIGPWR SIGXCPU SIG35 SIG36 nostop noprint"); + arg_list.push_back("-ex"); + arg_list.push_back("run"); + arg_list.push_back("--args"); + + break; } - ldpreloadstr += "'"; - arg_list.push_back(ldpreloadstr); - - /* We are using SIGSYS and SIGXFSZ for savestates, so don't - * print and pause when one signal is sent * - * Signals SIGPWR SIGXCPU SIG35 and SIG36 are used a lot in some games */ - arg_list.push_back("-ex"); - arg_list.push_back("handle SIGSYS SIGXFSZ SIGUSR1 SIGUSR2 SIGPWR SIGXCPU SIG35 SIG36 nostop noprint"); - arg_list.push_back("-ex"); - arg_list.push_back("run"); - arg_list.push_back("--args"); + case Config::DEBUGGER_LLDB: { + arg_list.push_back("-o"); + /* LD_PRELOAD/DYLD_INSERT_LIBRARIES must be set inside an lldb + * command to be effective */ +#ifdef __unix__ + std::string ldpreloadstr = "set se target.env-vars 'LD_PRELOAD="; #elif defined(__APPLE__) && defined(__MACH__) + std::string ldpreloadstr = "set se target.env-vars 'DYLD_INSERT_LIBRARIES="; +#endif + ldpreloadstr += context->libtaspath; + if (!context->old_ld_preload.empty()) { + ldpreloadstr += ":"; + ldpreloadstr += context->old_ld_preload; + } + ldpreloadstr += "'"; + arg_list.push_back(ldpreloadstr); - arg_list.push_back("-o"); +#if defined(__APPLE__) && defined(__MACH__) + arg_list.push_back("-o"); + arg_list.push_back("set se target.env-vars 'DYLD_FORCE_FLAT_NAMESPACE=1'"); +#endif - /* DYLD_INSERT_LIBRARIES must be set inside a lldb - * command to be effective */ - std::string ldpreloadstr = "set se target.env-vars 'DYLD_INSERT_LIBRARIES="; - ldpreloadstr += context->libtaspath; - if (!context->old_ld_preload.empty()) { - ldpreloadstr += ":"; - ldpreloadstr += context->old_ld_preload; + /* We are using SIGSYS and SIGXFSZ for savestates, so don't + * print and pause when one signal is sent */ + arg_list.push_back("-o"); + arg_list.push_back("run"); + /* Signal handling cannot be performed in llvm before the process has started */ +// arg_list.push_back("-o"); +// arg_list.push_back("process handle -n false -p false -s false SIGSYS SIGXFSZ SIGUSR1 SIGUSR2 SIGXCPU"); + arg_list.push_back("--"); + + break; + } } - ldpreloadstr += "'"; - arg_list.push_back(ldpreloadstr); - - arg_list.push_back("-o"); - arg_list.push_back("set se target.env-vars 'DYLD_FORCE_FLAT_NAMESPACE=1'"); - - /* We are using SIGSYS and SIGXFSZ for savestates, so don't - * print and pause when one signal is sent */ - arg_list.push_back("-o"); - arg_list.push_back("run"); - /* Signal handling cannot be performed in llvm before the process has started */ -// arg_list.push_back("-o"); -// arg_list.push_back("process handle -n false -p false -s false SIGSYS SIGXFSZ SIGUSR1 SIGUSR2 SIGXCPU"); - arg_list.push_back("--"); -#endif } /* Tell SDL >= 2.0.2 to let us override functions even if it is statically linked. diff --git a/src/program/ui/ErrorChecking.cpp b/src/program/ui/ErrorChecking.cpp index 44d1c53b..817b9e89 100644 --- a/src/program/ui/ErrorChecking.cpp +++ b/src/program/ui/ErrorChecking.cpp @@ -200,19 +200,29 @@ bool ErrorChecking::checkArchType(Context* context) /* Check for gdb presence in case of Start and attach gdb */ if (context->attach_gdb) { - std::string cmd = "which gdb"; + std::string cmd; + + switch (context->config.debugger) { + case Config::DEBUGGER_GDB: + cmd = "which gdb"; + break; + case Config::DEBUGGER_LLDB: + cmd = "which lldb"; + break; + } + FILE *output = popen(cmd.c_str(), "r"); if (output != NULL) { std::array buf; fgets(buf.data(), buf.size(), output); int ret = pclose(output); if (ret != 0) { - critical(QString("Trying to start a game with attached gdb, but gdb cannot be found"), context->interactive); + critical(QString("Trying to start a game with attached debugger, but debugger cannot be found"), context->interactive); return false; } } else { - critical(QString("Coundn't popen to locate gdb"), context->interactive); + critical(QString("Coundn't popen to locate debugger"), context->interactive); return false; } } diff --git a/src/program/ui/MainWindow.cpp b/src/program/ui/MainWindow.cpp index b2fc9ada..87c4191a 100644 --- a/src/program/ui/MainWindow.cpp +++ b/src/program/ui/MainWindow.cpp @@ -209,12 +209,32 @@ MainWindow::MainWindow(Context* c) : QMainWindow(), context(c) /* Buttons */ launchButton = new QPushButton(tr("Start")); - connect(launchButton, &QAbstractButton::clicked, this, &MainWindow::slotLaunch); + connect(launchButton, &QAbstractButton::clicked, this, [this] { MainWindow::slotLaunch(false); }); disabledWidgetsOnStart.append(launchButton); - launchGdbButton = new QPushButton(tr("Start and attach gdb")); - connect(launchGdbButton, &QAbstractButton::clicked, this, &MainWindow::slotLaunch); - disabledWidgetsOnStart.append(launchGdbButton); + launchGdbButton = new QToolButton(); + launchGdbButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + + launchGdbAction = new QAction(tr("Launch with GDB"), this); + launchLldbAction = new QAction(tr("Launch with LLDB"), this); + + connect(launchGdbAction, &QAction::triggered, this, &MainWindow::slotLaunchGdb); + connect(launchLldbAction, &QAction::triggered, this, &MainWindow::slotLaunchLldb); + + /* launchGdbButton is a special case, it's explicitly disabled along with + * all the other widgets on launch + */ + //disabledWidgetsOnStart.append(launchGdbButton); + +#ifdef __unix__ + launchGdbButton->setPopupMode(QToolButton::MenuButtonPopup); + + QMenu *launchGdbButtonMenu = new QMenu(); + launchGdbButton->setMenu(launchGdbButtonMenu); + + launchGdbButtonMenu->addAction(launchGdbAction); + launchGdbButtonMenu->addAction(launchLldbAction); +#endif stopButton = new QPushButton(tr("Stop")); connect(stopButton, &QAbstractButton::clicked, this, &MainWindow::slotStop); @@ -360,7 +380,7 @@ MainWindow::MainWindow(Context* c) : QMainWindow(), context(c) if (!context->interactive) { slotPause(false); slotFastForward(true); - slotLaunch(); + slotLaunch(false); } } @@ -800,6 +820,11 @@ void MainWindow::createMenus() debugMenu->addSeparator(); + sigintAction = debugMenu->addAction(tr("Raise SIGINT upon game launch (if debugging)")); + sigintAction->setCheckable(true); + + debugMenu->addSeparator(); + debugMenu->addActions(loggingOutputGroup->actions()); disabledActionsOnStart.append(loggingOutputGroup->actions()); @@ -897,6 +922,8 @@ void MainWindow::updateStatus() initialTimeSec->setValue(context->config.sc.initial_time_sec); initialTimeNsec->setValue(context->config.sc.initial_time_nsec); + launchGdbButton->setEnabled(true); + if (context->config.sc.av_dumping) { context->config.sc.av_dumping = false; configEncodeAction->setEnabled(true); @@ -929,6 +956,8 @@ void MainWindow::updateStatus() fpsDenField->setEnabled(false); } + launchGdbButton->setEnabled(false); + movieBox->setCheckable(false); if (context->config.sc.recording == SharedConfig::NO_RECORDING) { movieBox->setEnabled(false); @@ -1254,6 +1283,15 @@ void MainWindow::updateUIFromConfig() setRadioFromList(movieEndGroup, context->config.on_movie_end); + switch (context->config.debugger) { + case Config::DEBUGGER_GDB: + launchGdbButton->setDefaultAction(launchGdbAction); + break; + case Config::DEBUGGER_LLDB: + launchGdbButton->setDefaultAction(launchLldbAction); + break; + } + updateStatusBar(); } @@ -1279,12 +1317,25 @@ void MainWindow::updateStatusBar() } } -void MainWindow::slotLaunch() +void MainWindow::slotLaunchGdb() { + context->config.debugger = Config::DEBUGGER_GDB; + launchGdbButton->setDefaultAction(launchGdbAction); + + slotLaunch(true); +} + +void MainWindow::slotLaunchLldb() { + context->config.debugger = Config::DEBUGGER_LLDB; + launchGdbButton->setDefaultAction(launchLldbAction); + + slotLaunch(true); +} + +void MainWindow::slotLaunch(bool attach_gdb) { /* Do we attach gdb ? */ - QPushButton* button = static_cast(sender()); - context->attach_gdb = (button == launchGdbButton); + context->attach_gdb = attach_gdb; if (context->status != Context::INACTIVE) return; @@ -1307,6 +1358,8 @@ void MainWindow::slotLaunch() setListFromRadio(loggingOutputGroup, context->config.sc.logging_status); + context->config.sc.sigint_upon_launch = context->attach_gdb && sigintAction->isChecked(); + context->config.sc.mouse_support = mouseAction->isChecked(); setListFromRadio(joystickGroup, context->config.sc.nb_controllers); diff --git a/src/program/ui/MainWindow.h b/src/program/ui/MainWindow.h index 554014fc..cbb87bf9 100644 --- a/src/program/ui/MainWindow.h +++ b/src/program/ui/MainWindow.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,7 @@ class MainWindow : public QMainWindow QActionGroup *asyncGroup; QActionGroup *debugStateGroup; + QAction *sigintAction; QActionGroup *loggingOutputGroup; QActionGroup *loggingPrintGroup; QActionGroup *loggingExcludeGroup; @@ -169,7 +171,9 @@ class MainWindow : public QMainWindow QSpinBox *initialTimeNsec; QPushButton *launchButton; - QPushButton *launchGdbButton; + QToolButton *launchGdbButton; + QAction *launchGdbAction; + QAction *launchLldbAction; QPushButton *stopButton; QGroupBox *movieBox; @@ -236,7 +240,9 @@ private slots: /* Update framerate values */ void updateFramerate(); - void slotLaunch(); + void slotLaunchGdb(); + void slotLaunchLldb(); + void slotLaunch(bool attach_gdb); void slotStop(); void slotBrowseGamePath(); void slotGamePathChanged(); diff --git a/src/shared/SharedConfig.h b/src/shared/SharedConfig.h index d0c6a620..0dc8e1e7 100644 --- a/src/shared/SharedConfig.h +++ b/src/shared/SharedConfig.h @@ -295,6 +295,9 @@ struct __attribute__((packed, aligned(8))) SharedConfig { /* Send stack traces of all time calls to libTAS program */ bool time_trace = false; + + /* Call raise(SIGINT) in libtas::init */ + bool sigint_upon_launch = false; }; #endif