diff --git a/docs/source/user-docs/command-line.rst b/docs/source/user-docs/command-line.rst index 727632c7..aaa2de88 100644 --- a/docs/source/user-docs/command-line.rst +++ b/docs/source/user-docs/command-line.rst @@ -61,3 +61,15 @@ Options Disable output redirection. Some of the output in console widget will not be visible. Use this option when debuging a crash or freeze and output redirection is causing some messages to be lost. + +.. option:: --no-plugins + + Start cutter with all plugins disabled. Implies :option:`--no-cutter-plugins` and :option:`--no-r2-plugins`. + +.. option:: --no-cutter-plugins + + Start cutter with cutter plugins disabled. + +.. option:: --no-r2-plugins + + Start cutter with r2 plugins disabled. diff --git a/src/CutterApplication.cpp b/src/CutterApplication.cpp index 0df591c3..550c2dfe 100644 --- a/src/CutterApplication.cpp +++ b/src/CutterApplication.cpp @@ -68,47 +68,10 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); #endif - QCommandLineParser cmd_parser; - cmd_parser.setApplicationDescription( - QObject::tr("A Qt and C++ GUI for radare2 reverse engineering framework")); - cmd_parser.addHelpOption(); - cmd_parser.addVersionOption(); - cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open.")); - QCommandLineOption analOption({"A", "anal"}, - QObject::tr("Automatically open file and optionally start analysis. Needs filename to be specified. May be a value between 0 and 2: 0 = no analysis, 1 = aaa, 2 = aaaa (experimental)"), - QObject::tr("level")); - cmd_parser.addOption(analOption); - - QCommandLineOption formatOption({"F", "format"}, - QObject::tr("Force using a specific file format (bin plugin)"), - QObject::tr("name")); - cmd_parser.addOption(formatOption); - - QCommandLineOption baddrOption({"B", "base"}, - QObject::tr("Load binary at a specific base address"), - QObject::tr("base address")); - cmd_parser.addOption(baddrOption); - - QCommandLineOption scriptOption("i", - QObject::tr("Run script file"), - QObject::tr("file")); - cmd_parser.addOption(scriptOption); - - QCommandLineOption pythonHomeOption("pythonhome", QObject::tr("PYTHONHOME to use for embedded python interpreter"), - "PYTHONHOME"); - cmd_parser.addOption(pythonHomeOption); - - QCommandLineOption disableRedirectOption("no-output-redirect", - QObject::tr("Disable output redirection." - " Some of the output in console widget will not be visible." - " Use this option when debuging a crash or freeze and output " - " redirection is causing some messages to be lost.")); - cmd_parser.addOption(disableRedirectOption); - - cmd_parser.process(*this); - - QStringList args = cmd_parser.positionalArguments(); + if (!parseCommandLineOptions()) { + std::exit(1); + } // Check r2 version QString r2version = r_core_version(); @@ -128,8 +91,8 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc #ifdef CUTTER_ENABLE_PYTHON // Init python - if (cmd_parser.isSet(pythonHomeOption)) { - Python()->setPythonHome(cmd_parser.value(pythonHomeOption)); + if (!clOptions.pythonHome.isEmpty()) { + Python()->setPythonHome(clOptions.pythonHome); } Python()->initialize(); #endif @@ -139,14 +102,12 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc qputenv("R_ALT_SRC_DIR", "1"); #endif - Core()->initialize(); + Core()->initialize(clOptions.enableR2Plugins); Core()->setSettings(); Config()->loadInitial(); Core()->loadCutterRC(); - if (cmd_parser.isSet(disableRedirectOption)) { - Config()->setOutputRedirectionEnabled(false); - } + Config()->setOutputRedirectionEnabled(clOptions.outputRedirectionEnabled); if (R2DecDecompiler::isAvailable()) { Core()->registerDecompiler(new R2DecDecompiler(Core())); @@ -156,20 +117,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc Core()->registerDecompiler(new R2GhidraDecompiler(Core())); #endif - bool analLevelSpecified = false; - int analLevel = 0; - - if (cmd_parser.isSet(analOption)) { - analLevel = cmd_parser.value(analOption).toInt(&analLevelSpecified); - - if (!analLevelSpecified || analLevel < 0 || analLevel > 2) { - printf("%s\n", - QObject::tr("Invalid Analysis Level. May be a value between 0 and 2.").toLocal8Bit().constData()); - std::exit(1); - } - } - - Plugins()->loadPlugins(); + Plugins()->loadPlugins(clOptions.enableCutterPlugins); for (auto &plugin : Plugins()->getPlugins()) { plugin->registerDecompilers(); @@ -183,46 +131,16 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc setStyle(new CutterProxyStyle()); #endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION - if (args.empty()) { - if (analLevelSpecified) { - printf("%s\n", - QObject::tr("Filename must be specified to start analysis automatically.").toLocal8Bit().constData()); - std::exit(1); - } - + if (clOptions.args.empty()) { // check if this is the first execution of Cutter in this computer - // Note: the execution after the preferences benn reset, will be considered as first-execution + // Note: the execution after the preferences been reset, will be considered as first-execution if (Config()->isFirstExecution()) { mainWindow->displayWelcomeDialog(); } mainWindow->displayNewFileDialog(); } else { // filename specified as positional argument - InitialOptions options; - options.filename = args[0]; - options.forceBinPlugin = cmd_parser.value(formatOption); - if (cmd_parser.isSet(baddrOption)) { - bool ok; - RVA baddr = cmd_parser.value(baddrOption).toULongLong(&ok, 0); - if (ok) { - options.binLoadAddr = baddr; - } - } - if (analLevelSpecified) { - switch (analLevel) { - case 0: - default: - options.analCmd = {}; - break; - case 1: - options.analCmd = { {"aaa", "Auto analysis"} }; - break; - case 2: - options.analCmd = { {"aaaa", "Auto analysis (experimental)"} }; - break; - } - } - options.script = cmd_parser.value(scriptOption); - mainWindow->openNewFile(options, analLevelSpecified); + bool askOptions = clOptions.analLevel != AutomaticAnalysisLevel::Ask; + mainWindow->openNewFile(clOptions.fileOpenOptions, askOptions); } #ifdef CUTTER_APPVEYOR_R2DEC @@ -278,6 +196,21 @@ CutterApplication::~CutterApplication() #endif } +void CutterApplication::launchNewInstance(const QStringList &args) +{ + QProcess process(this); + process.setEnvironment(QProcess::systemEnvironment()); + QStringList allArgs; + if (!clOptions.enableCutterPlugins) { + allArgs.push_back("--no-cutter-plugins"); + } + if (!clOptions.enableR2Plugins) { + allArgs.push_back("--no-r2-plugins"); + } + allArgs.append(args); + process.startDetached(qApp->applicationFilePath(), allArgs); +} + bool CutterApplication::event(QEvent *e) { if (e->type() == QEvent::FileOpen) { @@ -287,10 +220,7 @@ bool CutterApplication::event(QEvent *e) // We already dropped a file in macOS, let's spawn another instance // (Like the File -> Open) QString fileName = openEvent->file(); - QProcess process(this); - process.setEnvironment(QProcess::systemEnvironment()); - QStringList args = QStringList(fileName); - process.startDetached(qApp->applicationFilePath(), args); + launchNewInstance({fileName}); } else { QString fileName = openEvent->file(); m_FileAlreadyDropped = true; @@ -362,6 +292,145 @@ bool CutterApplication::loadTranslations() return false; } +bool CutterApplication::parseCommandLineOptions() +{ + // Keep this function in sync with documentation + + QCommandLineParser cmd_parser; + cmd_parser.setApplicationDescription( + QObject::tr("A Qt and C++ GUI for radare2 reverse engineering framework")); + cmd_parser.addHelpOption(); + cmd_parser.addVersionOption(); + cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open.")); + + QCommandLineOption analOption({"A", "anal"}, + QObject::tr("Automatically open file and optionally start analysis. " + "Needs filename to be specified. May be a value between 0 and 2:" + " 0 = no analysis, 1 = aaa, 2 = aaaa (experimental)"), + QObject::tr("level")); + cmd_parser.addOption(analOption); + + QCommandLineOption formatOption({"F", "format"}, + QObject::tr("Force using a specific file format (bin plugin)"), + QObject::tr("name")); + cmd_parser.addOption(formatOption); + + QCommandLineOption baddrOption({"B", "base"}, + QObject::tr("Load binary at a specific base address"), + QObject::tr("base address")); + cmd_parser.addOption(baddrOption); + + QCommandLineOption scriptOption("i", + QObject::tr("Run script file"), + QObject::tr("file")); + cmd_parser.addOption(scriptOption); + + QCommandLineOption pythonHomeOption("pythonhome", + QObject::tr("PYTHONHOME to use for embedded python interpreter"), + "PYTHONHOME"); + cmd_parser.addOption(pythonHomeOption); + + QCommandLineOption disableRedirectOption("no-output-redirect", + QObject::tr("Disable output redirection." + " Some of the output in console widget will not be visible." + " Use this option when debuging a crash or freeze and output " + " redirection is causing some messages to be lost.")); + cmd_parser.addOption(disableRedirectOption); + + QCommandLineOption disablePlugins("no-plugins", + QObject::tr("Do not load plugins")); + cmd_parser.addOption(disablePlugins); + + QCommandLineOption disableCutterPlugins("no-cutter-plugins", + QObject::tr("Do not load Cutter plugins")); + cmd_parser.addOption(disableCutterPlugins); + + QCommandLineOption disableR2Plugins("no-r2-plugins", + QObject::tr("Do not load radare2 plugins")); + cmd_parser.addOption(disableR2Plugins); + + cmd_parser.process(*this); + + CutterCommandLineOptions opts; + opts.args = cmd_parser.positionalArguments(); + + if (cmd_parser.isSet(analOption)) { + bool analLevelSpecified = false; + int analLevel = cmd_parser.value(analOption).toInt(&analLevelSpecified); + + if (!analLevelSpecified || analLevel < 0 || analLevel > 2) { + fprintf(stderr, "%s\n", + QObject::tr("Invalid Analysis Level. May be a value between 0 and 2.").toLocal8Bit().constData()); + return false; + } + switch (analLevel) { + case 0: + opts.analLevel = AutomaticAnalysisLevel::None; + break; + case 1: + opts.analLevel = AutomaticAnalysisLevel::AAA; + break; + case 2: + opts.analLevel = AutomaticAnalysisLevel::AAAA; + break; + } + } + + if (opts.args.empty() && opts.analLevel != AutomaticAnalysisLevel::Ask) { + fprintf(stderr, "%s\n", + QObject::tr("Filename must be specified to start analysis automatically.").toLocal8Bit().constData()); + return false; + } + + InitialOptions options; + if (!opts.args.isEmpty()) { + opts.fileOpenOptions.filename = opts.args[0]; + opts.fileOpenOptions.forceBinPlugin = cmd_parser.value(formatOption); + if (cmd_parser.isSet(baddrOption)) { + bool ok; + RVA baddr = cmd_parser.value(baddrOption).toULongLong(&ok, 0); + if (ok) { + options.binLoadAddr = baddr; + } + } + switch (opts.analLevel) { + case AutomaticAnalysisLevel::Ask: + break; + case AutomaticAnalysisLevel::None: + opts.fileOpenOptions.analCmd = {}; + break; + case AutomaticAnalysisLevel::AAA: + opts.fileOpenOptions.analCmd = { {"aaa", "Auto analysis"} }; + break; + case AutomaticAnalysisLevel::AAAA: + opts.fileOpenOptions.analCmd = { {"aaaa", "Auto analysis (experimental)"} }; + break; + } + opts.fileOpenOptions.script = cmd_parser.value(scriptOption); + } + + if (cmd_parser.isSet(pythonHomeOption)) { + opts.pythonHome = cmd_parser.value(pythonHomeOption); + } + + opts.outputRedirectionEnabled = !cmd_parser.isSet(disableRedirectOption); + if (cmd_parser.isSet(disablePlugins)) { + opts.enableCutterPlugins = false; + opts.enableR2Plugins = false; + } + + if (cmd_parser.isSet(disableCutterPlugins)) { + opts.enableCutterPlugins = false; + } + + if (cmd_parser.isSet(disableR2Plugins)) { + opts.enableR2Plugins = false; + } + + this->clOptions = opts; + return true; +} + void CutterProxyStyle::polish(QWidget *widget) { @@ -370,7 +439,7 @@ void CutterProxyStyle::polish(QWidget *widget) // HACK: This is the only way I've found to force Qt (5.10 and newer) to // display shortcuts in context menus on all platforms. It's ugly, // but it gets the job done. - if (auto menu = qobject_cast(widget)) { + if (auto menu = qobject_cast(widget)) { const auto &actions = menu->actions(); for (auto action : actions) { action->setShortcutVisibleInContextMenu(true); diff --git a/src/CutterApplication.h b/src/CutterApplication.h index 12534a2b..9781e89e 100644 --- a/src/CutterApplication.h +++ b/src/CutterApplication.h @@ -8,6 +8,19 @@ #include "core/MainWindow.h" +enum class AutomaticAnalysisLevel { + Ask, None, AAA, AAAA +}; + +struct CutterCommandLineOptions { + QStringList args; + AutomaticAnalysisLevel analLevel = AutomaticAnalysisLevel::Ask; + InitialOptions fileOpenOptions; + QString pythonHome; + bool outputRedirectionEnabled = true; + bool enableCutterPlugins = true; + bool enableR2Plugins = true; +}; class CutterApplication : public QApplication { @@ -22,6 +35,7 @@ public: return mainWindow; } + void launchNewInstance(const QStringList &args = {}); protected: bool event(QEvent *e); @@ -31,10 +45,15 @@ private: * @return true on success */ bool loadTranslations(); - + /** + * @brief Parse commandline options and store them in a structure. + * @return false if options have error + */ + bool parseCommandLineOptions(); private: bool m_FileAlreadyDropped; MainWindow *mainWindow; + CutterCommandLineOptions clOptions; }; diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 059ee151..ebad0c5d 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -174,7 +174,7 @@ CutterCore *CutterCore::instance() return uniqueInstance; } -void CutterCore::initialize() +void CutterCore::initialize(bool loadPlugins) { r_cons_new(); // initialize console core_ = r_core_new(); @@ -208,7 +208,12 @@ void CutterCore::initialize() } #endif - r_core_loadlibs(this->core_, R_CORE_LOADLIBS_ALL, NULL); + if (!loadPlugins) { + setConfig("cfg.plugins", 0); + } + if (getConfigi("cfg.plugins")) { + r_core_loadlibs(this->core_, R_CORE_LOADLIBS_ALL, nullptr); + } // IMPLICIT r_bin_iobind (core_->bin, core_->io); // Otherwise r2 may ask the user for input and Cutter would freeze diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 836cdf1d..593f9ed7 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -44,7 +44,7 @@ public: ~CutterCore(); static CutterCore *instance(); - void initialize(); + void initialize(bool loadPlugins = true); void loadCutterRC(); AsyncTaskManager *getAsyncTaskManager() { return asyncTaskManager; } diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 65b43215..9c015145 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -13,6 +13,7 @@ #include "common/PythonManager.h" #include "plugins/PluginManager.h" #include "CutterConfig.h" +#include "CutterApplication.h" // Dialogs #include "dialogs/WelcomeDialog.h" @@ -1349,9 +1350,7 @@ void MainWindow::on_actionDefault_triggered() void MainWindow::on_actionNew_triggered() { // Create a new Cutter process - QProcess process(this); - process.setEnvironment(QProcess::systemEnvironment()); - process.startDetached(qApp->applicationFilePath()); + static_cast(qApp)->launchNewInstance(); } void MainWindow::on_actionSave_triggered() diff --git a/src/plugins/PluginManager.cpp b/src/plugins/PluginManager.cpp index 15b5a9d0..0efb266f 100644 --- a/src/plugins/PluginManager.cpp +++ b/src/plugins/PluginManager.cpp @@ -31,10 +31,15 @@ PluginManager::~PluginManager() { } -void PluginManager::loadPlugins() +void PluginManager::loadPlugins(bool enablePlugins) { assert(plugins.empty()); + if (!enablePlugins) { + // [#2159] list but don't enable the plugins + return; + } + QString userPluginDir = getUserPluginsDirectory(); if (!userPluginDir.isEmpty()) { loadPluginsFromDir(QDir(userPluginDir), true); diff --git a/src/plugins/PluginManager.h b/src/plugins/PluginManager.h index 4310e196..27f921ed 100644 --- a/src/plugins/PluginManager.h +++ b/src/plugins/PluginManager.h @@ -28,8 +28,9 @@ public: /** * @brief Load all plugins, should be called once on application start + * @param enablePlugins set to false if plugin code shouldn't be started */ - void loadPlugins(); + void loadPlugins(bool enablePlugins = true); /** * @brief Destroy all loaded plugins, should be called once on application shutdown