Redirect stdout/stderr to Console Widget (#1821)

* Added stdout/stderr redirection to the console
This commit is contained in:
yossizap 2019-10-14 10:00:44 +00:00 committed by Itay Cohen
parent b81eed7f22
commit 235761941f
3 changed files with 149 additions and 15 deletions

View File

@ -121,6 +121,46 @@ static void migrateThemes()
}
}
/**
* @brief Attempt to connect to a parent console and configure outputs.
*
* @note Doesn't do anything if the exe wasn't executed from a console.
*/
#ifdef Q_OS_WIN
static void connectToConsole()
{
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
return;
}
// Avoid reconfiguring stderr/stdout if one of them is already connected to a stream.
// This can happen when running with stdout/stderr redirected to a file.
if (0 > fileno(stdout)) {
// Overwrite FD 1 and 2 for the benefit of any code that uses the FDs
// directly. This is safe because the CRT allocates FDs 0, 1 and
// 2 at startup even if they don't have valid underlying Windows
// handles. This means we won't be overwriting an FD created by
// _open() after startup.
_close(1);
if (freopen("CONOUT$", "a+", stdout)) {
// Avoid buffering stdout/stderr since IOLBF is replaced by IOFBF in Win32.
setvbuf(stdout, nullptr, _IONBF, 0);
}
}
if (0 > fileno(stderr)) {
_close(2);
if (freopen("CONOUT$", "a+", stderr)) {
setvbuf(stderr, nullptr, _IONBF, 0);
}
}
// Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog.
std::ios::sync_with_stdio();
}
#endif
int main(int argc, char *argv[])
{
@ -133,6 +173,10 @@ int main(int argc, char *argv[])
initCrashHandler();
#ifdef Q_OS_WIN
connectToConsole();
#endif
qRegisterMetaType<QList<StringDescription>>();
qRegisterMetaType<QList<FunctionDescription>>();

View File

@ -13,6 +13,20 @@
#include "common/Helpers.h"
#include "common/SvgIconEngine.h"
#ifdef Q_OS_WIN
#include <io.h>
#include <QUuid>
#define dup2 _dup2
#define dup _dup
#define fileno _fileno
#define fdopen _fdopen
#define PIPE_SIZE 65536 // Match Linux size
#define PIPE_NAME "\\\\.\\pipe\\cutteroutput-%1"
#else
#include <unistd.h>
#define PIPE_READ (0)
#define PIPE_WRITE (1)
#endif
static const int invalidHistoryPos = -1;
@ -90,6 +104,8 @@ ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) :
connect(Config(), &Configuration::interfaceThemeChanged, this, &ConsoleWidget::setupFont);
completer->popup()->installEventFilter(this);
redirectOutput();
}
ConsoleWidget::~ConsoleWidget()
@ -156,36 +172,26 @@ void ConsoleWidget::executeCommand(const QString &command)
}
ui->inputLineEdit->setEnabled(false);
const int originalLines = ui->outputTextEdit->blockCount();
QTimer *timer = new QTimer(this);
timer->setInterval(500);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [this]() {
ui->outputTextEdit->appendPlainText("Executing the command...");
});
QString cmd_line = "[" + RAddressString(Core()->getOffset()) + "]> " + command;
addOutput(cmd_line);
QString cmd_line = "<br>[" + RAddressString(Core()->getOffset()) + "]> " + command + "<br>";
RVA oldOffset = Core()->getOffset();
commandTask = QSharedPointer<CommandTask>(new CommandTask(command, CommandTask::ColorMode::MODE_256, true));
connect(commandTask.data(), &CommandTask::finished, this, [this, cmd_line,
command, originalLines, oldOffset] (const QString & result) {
command, oldOffset] (const QString & result) {
if (originalLines < ui->outputTextEdit->blockCount()) {
removeLastLine();
}
ui->outputTextEdit->appendHtml(cmd_line + result);
ui->outputTextEdit->appendHtml(result);
scrollOutputToEnd();
historyAdd(command);
commandTask.clear();
ui->inputLineEdit->setEnabled(true);
ui->inputLineEdit->setFocus();
if (oldOffset != Core()->getOffset()) {
Core()->updateSeek();
}
});
connect(commandTask.data(), &CommandTask::finished, timer, &QTimer::stop);
timer->start();
Core()->getAsyncTaskManager()->start(commandTask);
}
@ -324,3 +330,63 @@ void ConsoleWidget::invalidateHistoryPosition()
{
lastHistoryPosition = invalidHistoryPos;
}
void ConsoleWidget::processQueuedOutput()
{
// Partial lines are ignored since carriage return is currently unsupported
while (pipeSocket->canReadLine()) {
QString output = QString(pipeSocket->readLine());
fprintf(origStderr, "%s", output.toStdString().c_str());
// Get the last segment that wasn't overwritten by carriage return
output = output.trimmed();
output = output.remove(0, output.lastIndexOf('\r')).trimmed();
ui->outputTextEdit->appendHtml(CutterCore::ansiEscapeToHtml(output));
scrollOutputToEnd();
}
}
void ConsoleWidget::redirectOutput()
{
// Make sure that we are running in a valid console with initialized output handles
if (0 > fileno(stderr) && 0 > fileno(stdout)) {
addOutput("Run cutter in a console to enable r2 output redirection into this widget.");
return;
}
pipeSocket = new QLocalSocket(this);
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,
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));
pipeSocket->connectToServer(pipeName, QIODevice::ReadOnly);
#else
pipe(redirectPipeFds);
dup2(redirectPipeFds[PIPE_WRITE], fileno(stderr));
dup2(redirectPipeFds[PIPE_WRITE], fileno(stdout));
// Attempt to force line buffering to avoid calling processQueuedOutput
// for partial lines
setlinebuf(stderr);
setlinebuf(stdout);
// Configure the pipe to work in async mode
fcntl(redirectPipeFds[PIPE_READ], F_SETFL, O_ASYNC | O_NONBLOCK);
pipeSocket->setSocketDescriptor(redirectPipeFds[PIPE_READ]);
pipeSocket->connectToServer(QIODevice::ReadOnly);
#endif
connect(pipeSocket, SIGNAL(readyRead()), this, SLOT(processQueuedOutput()));
}

View File

@ -6,6 +6,8 @@
#include "common/CommandTask.h"
#include <QStringListModel>
#include <QSocketNotifier>
#include <QLocalSocket>
#include <memory>
@ -62,6 +64,11 @@ private slots:
void clear();
/**
* @brief Passes redirected output from the pipe to the terminal and console
*/
void processQueuedOutput();
private:
void scrollOutputToEnd();
void historyAdd(const QString &input);
@ -70,6 +77,12 @@ private:
void executeCommand(const QString &command);
void setWrap(bool wrap);
/**
* @brief Redirects stderr and stdout to the output pipe which is handled by
* processQueuedOutput
*/
void redirectOutput();
QSharedPointer<CommandTask> commandTask;
std::unique_ptr<Ui::ConsoleWidget> ui;
@ -84,6 +97,17 @@ private:
QCompleter *completer;
QShortcut *historyUpShortcut;
QShortcut *historyDownShortcut;
FILE *origStderr;
FILE *origStdout;
QLocalSocket *pipeSocket;
#ifdef Q_OS_WIN
HANDLE hRead;
HANDLE hWrite;
#else
int redirectPipeFds[2];
QVector<char> *redirectionBuffer;
QSocketNotifier *outputNotifier;
#endif
};
#endif // CONSOLEWIDGET_H