From f284f9d209b43e23bff6d560419d64a3a830c93f Mon Sep 17 00:00:00 2001 From: yossizap Date: Wed, 4 Dec 2019 12:27:38 +0000 Subject: [PATCH] Stdin redirection for locally debugged processes (#1892) --- src/Cutter.pro | 2 + src/common/DirectionalComboBox.cpp | 24 ++++++ src/common/DirectionalComboBox.h | 23 +++++ src/core/Cutter.cpp | 18 ++++ src/core/Cutter.h | 4 + src/widgets/ConsoleWidget.cpp | 130 ++++++++++++++++++++++------- src/widgets/ConsoleWidget.h | 9 +- src/widgets/ConsoleWidget.ui | 61 ++++++++++++-- 8 files changed, 236 insertions(+), 35 deletions(-) create mode 100644 src/common/DirectionalComboBox.cpp create mode 100644 src/common/DirectionalComboBox.h diff --git a/src/Cutter.pro b/src/Cutter.pro index 222372c1..6ac0329f 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -275,6 +275,7 @@ SOURCES += \ common/HexHighlighter.cpp \ common/Highlighter.cpp \ common/MdHighlighter.cpp \ + common/DirectionalComboBox.cpp \ dialogs/preferences/AsmOptionsWidget.cpp \ dialogs/NewFileDialog.cpp \ common/AnalTask.cpp \ @@ -408,6 +409,7 @@ HEADERS += \ core/MainWindow.h \ common/Highlighter.h \ common/MdHighlighter.h \ + common/DirectionalComboBox.h \ dialogs/InitialOptionsDialog.h \ dialogs/NewFileDialog.h \ common/AnalTask.h \ diff --git a/src/common/DirectionalComboBox.cpp b/src/common/DirectionalComboBox.cpp new file mode 100644 index 00000000..9bf4de03 --- /dev/null +++ b/src/common/DirectionalComboBox.cpp @@ -0,0 +1,24 @@ +#include "DirectionalComboBox.h" + +DirectionalComboBox::DirectionalComboBox(QWidget *parent, bool upwards) + : QComboBox(parent), popupUpwards(upwards) +{ +} + +void DirectionalComboBox::showPopup() +{ + QComboBox::showPopup(); + QWidget *popup = this->findChild(); + if (popupUpwards) { + popup->move(popup->x(), + mapToGlobal(this->rect().bottomLeft()).y() - popup->height()); + } else { + popup->move(popup->x(), + mapToGlobal(this->rect().topLeft()).y()); + } +} + +void DirectionalComboBox::setPopupDirection(bool upwards) +{ + popupUpwards = upwards; +} diff --git a/src/common/DirectionalComboBox.h b/src/common/DirectionalComboBox.h new file mode 100644 index 00000000..96bb315f --- /dev/null +++ b/src/common/DirectionalComboBox.h @@ -0,0 +1,23 @@ +#pragma once + +#include +/** + * @brief Custom QComboBox created to prevent the menu popup from opening up at different + * offsets for different items, which may result in list items being rendered outside + * of the screen/containing widget. + */ +class DirectionalComboBox : public QComboBox +{ + Q_OBJECT + +public: + explicit DirectionalComboBox(QWidget *parent = nullptr, bool upwards = true); + + void setPopupDirection(bool upwards); + +private: + bool popupUpwards; + + void showPopup(); +}; + diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 736c228e..60a3c241 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -313,6 +313,24 @@ QString CutterCore::cmd(const char *str) return o; } +bool CutterCore::isRedirectableDebugee() +{ + if (!currentlyDebugging || currentlyAttachedToPID != -1) { + return false; + } + + // We are only able to redirect locally debugged unix processes + QJsonArray openFilesArray = cmdj("oj").array();; + for (QJsonValue value : openFilesArray) { + QJsonObject openFile = value.toObject(); + QString URI = openFile["uri"].toString(); + if (URI.contains("ptrace") | URI.contains("mach")) { + return true; + } + } + return false; +} + bool CutterCore::isDebugTaskInProgress() { if (!debugTask.isNull()) { diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 4a8fcabe..c00fc256 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -298,6 +298,10 @@ public: QStringList getDebugPlugins(); void setDebugPlugin(QString plugin); bool isDebugTaskInProgress(); + /** + * @brief Check if we can use output/input redirection with the currently debugged process + */ + bool isRedirectableDebugee(); bool currentlyDebugging = false; bool currentlyEmulating = false; int currentlyAttachedToPID = -1; diff --git a/src/widgets/ConsoleWidget.cpp b/src/widgets/ConsoleWidget.cpp index 972b4ec6..23c9cda6 100644 --- a/src/widgets/ConsoleWidget.cpp +++ b/src/widgets/ConsoleWidget.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include "core/Cutter.h" #include "ConsoleWidget.h" @@ -15,7 +17,6 @@ #ifdef Q_OS_WIN #include -#include #define dup2 _dup2 #define dup _dup #define fileno _fileno @@ -26,8 +27,12 @@ #include #define PIPE_READ (0) #define PIPE_WRITE (1) +#define STDIN_PIPE_NAME "%1/cutter-stdin-%2" #endif +#define CONSOLE_R2_INPUT ("R2 Console") +#define CONSOLE_DEBUGEE_INPUT ("Debugee Input") + static const int invalidHistoryPos = -1; static const char *consoleWrapSettingsKey = "console.wrap"; @@ -45,7 +50,8 @@ ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) : ui->setupUi(this); // Adjust console lineedit - ui->inputLineEdit->setTextMargins(10, 0, 0, 0); + ui->r2InputLineEdit->setTextMargins(10, 0, 0, 0); + ui->debugeeInputLineEdit->setTextMargins(10, 0, 0, 0); setupFont(); @@ -71,9 +77,9 @@ ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) : completer->setMaxVisibleItems(20); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setFilterMode(Qt::MatchStartsWith); - ui->inputLineEdit->setCompleter(completer); + ui->r2InputLineEdit->setCompleter(completer); - connect(ui->inputLineEdit, &QLineEdit::textEdited, this, &ConsoleWidget::updateCompletion); + connect(ui->r2InputLineEdit, &QLineEdit::textEdited, this, &ConsoleWidget::updateCompletion); updateCompletion(); // Set console output context menu @@ -81,28 +87,46 @@ ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) : connect(ui->outputTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showCustomContextMenu(const QPoint &))); - // Esc clears inputLineEdit (like OmniBar) - QShortcut *clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), ui->inputLineEdit); - connect(clear_shortcut, SIGNAL(activated()), this, SLOT(clear())); - clear_shortcut->setContext(Qt::WidgetShortcut); + // Esc clears r2InputLineEdit and debugeeInputLineEdit (like OmniBar) + QShortcut *r2_clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), ui->r2InputLineEdit); + connect(r2_clear_shortcut, SIGNAL(activated()), this, SLOT(clear())); + r2_clear_shortcut->setContext(Qt::WidgetShortcut); + + QShortcut *debugee_clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), ui->debugeeInputLineEdit); + connect(debugee_clear_shortcut, SIGNAL(activated()), this, SLOT(clear())); + debugee_clear_shortcut->setContext(Qt::WidgetShortcut); // Up and down arrows show history - historyUpShortcut = new QShortcut(QKeySequence(Qt::Key_Up), ui->inputLineEdit); + historyUpShortcut = new QShortcut(QKeySequence(Qt::Key_Up), ui->r2InputLineEdit); connect(historyUpShortcut, SIGNAL(activated()), this, SLOT(historyPrev())); historyUpShortcut->setContext(Qt::WidgetShortcut); - historyDownShortcut = new QShortcut(QKeySequence(Qt::Key_Down), ui->inputLineEdit); + historyDownShortcut = new QShortcut(QKeySequence(Qt::Key_Down), ui->r2InputLineEdit); connect(historyDownShortcut, SIGNAL(activated()), this, SLOT(historyNext())); historyDownShortcut->setContext(Qt::WidgetShortcut); - QShortcut *completionShortcut = new QShortcut(QKeySequence(Qt::Key_Tab), ui->inputLineEdit); + QShortcut *completionShortcut = new QShortcut(QKeySequence(Qt::Key_Tab), ui->r2InputLineEdit); connect(completionShortcut, &QShortcut::activated, this, &ConsoleWidget::triggerCompletion); - connect(ui->inputLineEdit, &QLineEdit::editingFinished, this, &ConsoleWidget::disableCompletion); + connect(ui->r2InputLineEdit, &QLineEdit::editingFinished, this, &ConsoleWidget::disableCompletion); connect(Config(), &Configuration::fontsUpdated, this, &ConsoleWidget::setupFont); connect(Config(), &Configuration::interfaceThemeChanged, this, &ConsoleWidget::setupFont); + connect(ui->inputCombo, + static_cast(&QComboBox::currentIndexChanged), + this, &ConsoleWidget::onIndexChange); + + connect(Core(), &CutterCore::debugTaskStateChanged, this, [ = ]() { + if (Core()->isRedirectableDebugee()) { + ui->inputCombo->setVisible(true); + } else { + ui->inputCombo->setVisible(false); + // Return to the r2 console + ui->inputCombo->setCurrentIndex(ui->inputCombo->findText(CONSOLE_R2_INPUT)); + } + }); + completer->popup()->installEventFilter(this); redirectOutput(); @@ -111,6 +135,11 @@ ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) : ConsoleWidget::~ConsoleWidget() { delete completer; + +#ifndef Q_OS_WIN + ::close(stdinFile); + remove(stdinFifoPath.toStdString().c_str()); +#endif } bool ConsoleWidget::eventFilter(QObject *obj, QEvent *event) @@ -150,7 +179,7 @@ void ConsoleWidget::addDebugOutput(const QString &msg) void ConsoleWidget::focusInputLineEdit() { - ui->inputLineEdit->setFocus(); + ui->r2InputLineEdit->setFocus(); } void ConsoleWidget::removeLastLine() @@ -170,7 +199,7 @@ void ConsoleWidget::executeCommand(const QString &command) if (!commandTask.isNull()) { return; } - ui->inputLineEdit->setEnabled(false); + ui->r2InputLineEdit->setEnabled(false); QString cmd_line = "[" + RAddressString(Core()->getOffset()) + "]> " + command; addOutput(cmd_line); @@ -184,8 +213,8 @@ void ConsoleWidget::executeCommand(const QString &command) scrollOutputToEnd(); historyAdd(command); commandTask.clear(); - ui->inputLineEdit->setEnabled(true); - ui->inputLineEdit->setFocus(); + ui->r2InputLineEdit->setEnabled(true); + ui->r2InputLineEdit->setFocus(); if (oldOffset != Core()->getOffset()) { Core()->updateSeek(); @@ -195,6 +224,32 @@ void ConsoleWidget::executeCommand(const QString &command) Core()->getAsyncTaskManager()->start(commandTask); } +void ConsoleWidget::sendToStdin(const QString &input) +{ +#ifndef Q_OS_WIN + write(stdinFile, (input + "\n").toStdString().c_str(), input.size() + 1); + fsync(stdinFile); + addOutput("Sent input: '" + input + "'"); +#else + // Stdin redirection isn't currently available in windows because console applications + // with stdin already get their own console window with stdin when they are launched + // that the user can type into. + addOutput("Unsupported feature"); +#endif +} + +void ConsoleWidget::onIndexChange() +{ + QString console = ui->inputCombo->currentText(); + if (console == CONSOLE_DEBUGEE_INPUT) { + ui->r2InputLineEdit->setVisible(false); + ui->debugeeInputLineEdit->setVisible(true); + } else if (console == CONSOLE_R2_INPUT) { + ui->r2InputLineEdit->setVisible(true); + ui->debugeeInputLineEdit->setVisible(false); + } +} + void ConsoleWidget::setWrap(bool wrap) { QSettings().setValue(consoleWrapSettingsKey, wrap); @@ -202,19 +257,29 @@ void ConsoleWidget::setWrap(bool wrap) ui->outputTextEdit->setLineWrapMode(wrap ? QPlainTextEdit::WidgetWidth: QPlainTextEdit::NoWrap); } -void ConsoleWidget::on_inputLineEdit_returnPressed() +void ConsoleWidget::on_r2InputLineEdit_returnPressed() { - QString input = ui->inputLineEdit->text(); + QString input = ui->r2InputLineEdit->text(); if (input.isEmpty()) { return; } executeCommand(input); - ui->inputLineEdit->clear(); + ui->r2InputLineEdit->clear(); +} + +void ConsoleWidget::on_debugeeInputLineEdit_returnPressed() +{ + QString input = ui->debugeeInputLineEdit->text(); + if (input.isEmpty()) { + return; + } + sendToStdin(input); + ui->debugeeInputLineEdit->clear(); } void ConsoleWidget::on_execButton_clicked() { - on_inputLineEdit_returnPressed(); + on_r2InputLineEdit_returnPressed(); } void ConsoleWidget::showCustomContextMenu(const QPoint &pt) @@ -238,9 +303,9 @@ void ConsoleWidget::historyNext() --lastHistoryPosition; if (lastHistoryPosition >= 0) { - ui->inputLineEdit->setText(history.at(lastHistoryPosition)); + ui->r2InputLineEdit->setText(history.at(lastHistoryPosition)); } else { - ui->inputLineEdit->clear(); + ui->r2InputLineEdit->clear(); } @@ -255,7 +320,7 @@ void ConsoleWidget::historyPrev() lastHistoryPosition = history.size() - 2; } - ui->inputLineEdit->setText(history.at(++lastHistoryPosition)); + ui->r2InputLineEdit->setText(history.at(++lastHistoryPosition)); } } @@ -286,7 +351,7 @@ void ConsoleWidget::updateCompletion() return; } - auto current = ui->inputLineEdit->text(); + auto current = ui->r2InputLineEdit->text(); auto completions = Core()->autocomplete(current, R_LINE_PROMPT_DEFAULT); int lastSpace = current.lastIndexOf(' '); if (lastSpace >= 0) { @@ -301,13 +366,14 @@ void ConsoleWidget::updateCompletion() void ConsoleWidget::clear() { disableCompletion(); - ui->inputLineEdit->clear(); + ui->r2InputLineEdit->clear(); + ui->debugeeInputLineEdit->clear(); invalidateHistoryPosition(); // Close the potential shown completer popup - ui->inputLineEdit->clearFocus(); - ui->inputLineEdit->setFocus(); + ui->r2InputLineEdit->clearFocus(); + ui->r2InputLineEdit->setFocus(); } void ConsoleWidget::scrollOutputToEnd() @@ -357,15 +423,16 @@ void ConsoleWidget::redirectOutput() pipeSocket = new QLocalSocket(this); + origStdin = fdopen(dup(fileno(stderr)), "r"); origStderr = fdopen(dup(fileno(stderr)), "a"); origStdout = fdopen(dup(fileno(stdout)), "a"); #ifdef Q_OS_WIN QString pipeName = QString::fromLatin1(PIPE_NAME).arg(QUuid::createUuid().toString()); SECURITY_ATTRIBUTES attributes = {sizeof(SECURITY_ATTRIBUTES), 0, false}; - hWrite = CreateNamedPipeW((wchar_t*)pipeName.utf16(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + hWrite = CreateNamedPipeW((wchar_t *)pipeName.utf16(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, PIPE_SIZE, PIPE_SIZE, 0, &attributes); - + int writeFd = _open_osfhandle((intptr_t)hWrite, _O_WRONLY | _O_TEXT); dup2(writeFd, fileno(stdout)); dup2(writeFd, fileno(stderr)); @@ -373,6 +440,11 @@ void ConsoleWidget::redirectOutput() pipeSocket->connectToServer(pipeName, QIODevice::ReadOnly); #else pipe(redirectPipeFds); + stdinFifoPath = QString(STDIN_PIPE_NAME).arg(QDir::tempPath(), QUuid::createUuid().toString()); + mkfifo(stdinFifoPath.toStdString().c_str(), (mode_t) 0777); + stdinFile = open(stdinFifoPath.toStdString().c_str(), O_RDWR | O_ASYNC); + + dup2(stdinFile, fileno(stdin)); dup2(redirectPipeFds[PIPE_WRITE], fileno(stderr)); dup2(redirectPipeFds[PIPE_WRITE], fileno(stdout)); diff --git a/src/widgets/ConsoleWidget.h b/src/widgets/ConsoleWidget.h index 0b61f531..01017749 100644 --- a/src/widgets/ConsoleWidget.h +++ b/src/widgets/ConsoleWidget.h @@ -4,6 +4,7 @@ #include "core/MainWindow.h" #include "CutterDockWidget.h" #include "common/CommandTask.h" +#include "common/DirectionalComboBox.h" #include #include @@ -49,7 +50,9 @@ public slots: private slots: void setupFont(); - void on_inputLineEdit_returnPressed(); + void on_r2InputLineEdit_returnPressed(); + void on_debugeeInputLineEdit_returnPressed(); + void onIndexChange(); void on_execButton_clicked(); @@ -75,6 +78,7 @@ private: void invalidateHistoryPosition(); void removeLastLine(); void executeCommand(const QString &command); + void sendToStdin(const QString &input); void setWrap(bool wrap); /** @@ -99,12 +103,15 @@ private: QShortcut *historyDownShortcut; FILE *origStderr; FILE *origStdout; + FILE *origStdin; QLocalSocket *pipeSocket; #ifdef Q_OS_WIN HANDLE hRead; HANDLE hWrite; #else int redirectPipeFds[2]; + int stdinFile; + QString stdinFifoPath; QVector *redirectionBuffer; QSocketNotifier *outputNotifier; #endif diff --git a/src/widgets/ConsoleWidget.ui b/src/widgets/ConsoleWidget.ui index a6ee16e9..133d3191 100644 --- a/src/widgets/ConsoleWidget.ui +++ b/src/widgets/ConsoleWidget.ui @@ -64,9 +64,9 @@ - + - 4 + 5 4 @@ -80,8 +80,28 @@ 2 - - + + + + QComboBox::AdjustToContents + + + false + + + + R2 Console + + + + + Debugee Input + + + + + + 0 @@ -99,7 +119,29 @@ - + + + + false + + + + 0 + 0 + + + + false + + + Enter input for the debugee + + + true + + + + @@ -133,8 +175,17 @@ + + + DirectionalComboBox + QComboBox +
common/DirectionalComboBox.h
+ 1 +
+
+