Stdin redirection for locally debugged processes (#1892)

This commit is contained in:
yossizap 2019-12-04 12:27:38 +00:00 committed by Itay Cohen
parent 49d58b3624
commit f284f9d209
8 changed files with 236 additions and 35 deletions

View File

@ -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 \

View File

@ -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<QFrame *>();
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;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <QComboBox>
/**
* @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();
};

View File

@ -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()) {

View File

@ -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;

View File

@ -6,6 +6,8 @@
#include <QStringListModel>
#include <QTimer>
#include <QSettings>
#include <QDir>
#include <QUuid>
#include <iostream>
#include "core/Cutter.h"
#include "ConsoleWidget.h"
@ -15,7 +17,6 @@
#ifdef Q_OS_WIN
#include <io.h>
#include <QUuid>
#define dup2 _dup2
#define dup _dup
#define fileno _fileno
@ -26,8 +27,12 @@
#include <unistd.h>
#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<void (QComboBox::*)(int)>(&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));

View File

@ -4,6 +4,7 @@
#include "core/MainWindow.h"
#include "CutterDockWidget.h"
#include "common/CommandTask.h"
#include "common/DirectionalComboBox.h"
#include <QStringListModel>
#include <QSocketNotifier>
@ -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<char> *redirectionBuffer;
QSocketNotifier *outputNotifier;
#endif

View File

@ -64,9 +64,9 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>4</number>
<number>5</number>
</property>
<property name="leftMargin">
<number>4</number>
@ -80,8 +80,28 @@
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QLineEdit" name="inputLineEdit">
<item row="0" column="0">
<widget class="DirectionalComboBox" name="inputCombo">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<property name="visible">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>R2 Console</string>
</property>
</item>
<item>
<property name="text">
<string>Debugee Input</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="r2InputLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
@ -99,7 +119,29 @@
</property>
</widget>
</item>
<item>
<item row="0" column="1">
<widget class="QLineEdit" name="debugeeInputLineEdit">
<property name="visible">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="placeholderText">
<string> Enter input for the debugee</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="execButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -133,8 +175,17 @@
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>DirectionalComboBox</class>
<extends>QComboBox</extends>
<header>common/DirectionalComboBox.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../resources.qrc"/>
</resources>
<connections/>
</ui>