diff --git a/docs/guide/dbgeng-ttd.md b/docs/guide/dbgeng-ttd.md
index bb3ff477..7a577e59 100644
--- a/docs/guide/dbgeng-ttd.md
+++ b/docs/guide/dbgeng-ttd.md
@@ -49,8 +49,7 @@ The WinDbg installation only needs to be done once.
## Record a TTD Trace
Once we have installed and configured WinDbg, we can start recording a TTD trace. There are two ways to do it, we can either
-do it from within Binary Ninja, or do it from WinDbg. Doing it from Binary Ninja is more convenient, though it does not support
-all types of recording supported by WinDbg (e.g., attach to a running process and start recroding).
+do it from within Binary Ninja, or do it from WinDbg. Binary Ninja now supports both launching new processes and attaching to running processes for TTD recording.
### Record a TTD Trace in Binary Ninja
@@ -60,13 +59,17 @@ all types of recording supported by WinDbg (e.g., attach to a running process an
-- In the "TTD Record" dialog, configure the recording as you wish:
- - Executable Path: the path of the executable to trace
- - Working Directory: the working directory to launch the executable in
- - Command Line Arguments: the command line arguments to pass to the executable
- - Trace Output Directory: the directory to write the trace. By default, it is equal to the working directory, but can be changed if necessary
-- Click "Record". A UAC dialog will pop up to because the TTD recording requires Administrator privilege
-- Accept the elevation. The program will be launched and recorded. Once it exits, find the trace file in the trace output directory
+- In the "TTD Record" dialog, you can choose between two recording modes:
+ - **Launch new process**: Records a new instance of an executable from the beginning
+ - Executable Path: the path of the executable to trace
+ - Working Directory: the working directory to launch the executable in
+ - Command Line Arguments: the command line arguments to pass to the executable
+ - Start application With Recording Off: checkbox to manually control when tracing begins
+ - **Attach to running process**: Attaches to an already running process and starts recording
+ - Target Process: click "Select Process..." to choose from running processes
+ - Trace Output Directory: the directory to write the trace (applies to both modes)
+- Click "Record". A UAC dialog will pop up because TTD recording requires Administrator privilege
+- Accept the elevation. The program will be launched/attached and recorded. Once it exits, find the trace file in the trace output directory
### Record a TTD Trace in WinDbg
diff --git a/ui/ttdrecord.cpp b/ui/ttdrecord.cpp
index e4ed3920..fabb5905 100644
--- a/ui/ttdrecord.cpp
+++ b/ui/ttdrecord.cpp
@@ -19,13 +19,21 @@ limitations under the License.
#include "qfiledialog.h"
#include "fmt/format.h"
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "attachprocess.h"
using namespace BinaryNinjaDebuggerAPI;
using namespace BinaryNinja;
using namespace std;
TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
- QDialog()
+ QDialog(), m_selectedPid(0)
{
if (data)
m_controller = DebuggerController::GetController(data);
@@ -35,13 +43,31 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
setModal(true);
QVBoxLayout* layout = new QVBoxLayout;
- layout->setSpacing(0);
-
+ layout->setSpacing(10);
+
+ // Mode selection
+ m_launchModeRadio = new QRadioButton("Launch new process", this);
+ m_attachModeRadio = new QRadioButton("Attach to running process", this);
+ m_modeButtonGroup = new QButtonGroup(this);
+ m_modeButtonGroup->addButton(m_launchModeRadio, 0);
+ m_modeButtonGroup->addButton(m_attachModeRadio, 1);
+ m_launchModeRadio->setChecked(true);
+
+ connect(m_modeButtonGroup, QOverload::of(&QButtonGroup::buttonClicked),
+ this, &TTDRecordDialog::onModeChanged);
+
+ QVBoxLayout* modeLayout = new QVBoxLayout;
+ modeLayout->addWidget(m_launchModeRadio);
+ modeLayout->addWidget(m_attachModeRadio);
+
+ // Launch mode group
+ m_launchGroup = new QGroupBox("Launch Settings", this);
+ QVBoxLayout* launchLayout = new QVBoxLayout;
+
m_pathEntry = new QLineEdit(this);
- m_pathEntry->setMinimumWidth(800);
+ m_pathEntry->setMinimumWidth(600);
m_argumentsEntry = new QLineEdit(this);
m_workingDirectoryEntry = new QLineEdit(this);
- m_outputDirectory = new QLineEdit(this);
m_launchWithoutTracing = new QCheckBox(this);
auto* pathSelector = new QPushButton("...", this);
@@ -61,6 +87,48 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
m_workingDirectoryEntry->setText(pathName);
});
+ auto pathEntryLayout = new QHBoxLayout;
+ pathEntryLayout->addWidget(m_pathEntry);
+ pathEntryLayout->addWidget(pathSelector);
+
+ auto workingDirLayout = new QHBoxLayout;
+ workingDirLayout->addWidget(m_workingDirectoryEntry);
+ workingDirLayout->addWidget(workingDirSelector);
+
+ launchLayout->addWidget(new QLabel("Executable Path"));
+ launchLayout->addLayout(pathEntryLayout);
+ launchLayout->addWidget(new QLabel("Working Directory"));
+ launchLayout->addLayout(workingDirLayout);
+ launchLayout->addWidget(new QLabel("Command Line Arguments"));
+ launchLayout->addWidget(m_argumentsEntry);
+ launchLayout->addWidget(new QLabel("Start application With Recording Off"));
+ launchLayout->addWidget(m_launchWithoutTracing);
+
+ m_launchGroup->setLayout(launchLayout);
+
+ // Attach mode group
+ m_attachGroup = new QGroupBox("Attach Settings", this);
+ QVBoxLayout* attachLayout = new QVBoxLayout;
+
+ m_selectedProcessDisplay = new QLineEdit(this);
+ m_selectedProcessDisplay->setReadOnly(true);
+ m_selectedProcessDisplay->setPlaceholderText("No process selected");
+
+ m_selectProcessButton = new QPushButton("Select Process...", this);
+ connect(m_selectProcessButton, &QPushButton::clicked, this, &TTDRecordDialog::selectProcess);
+
+ QHBoxLayout* processLayout = new QHBoxLayout;
+ processLayout->addWidget(m_selectedProcessDisplay);
+ processLayout->addWidget(m_selectProcessButton);
+
+ attachLayout->addWidget(new QLabel("Target Process"));
+ attachLayout->addLayout(processLayout);
+
+ m_attachGroup->setLayout(attachLayout);
+ m_attachGroup->setEnabled(false);
+
+ // Common settings
+ m_outputDirectory = new QLineEdit(this);
auto* outputDirSelector = new QPushButton("...", this);
outputDirSelector->setMaximumWidth(30);
connect(outputDirSelector, &QPushButton::clicked, [&]() {
@@ -70,31 +138,11 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
m_outputDirectory->setText(pathName);
});
- auto pathEntryLayout = new QHBoxLayout;
- pathEntryLayout->addWidget(m_pathEntry);
- pathEntryLayout->addWidget(pathSelector);
-
- auto workingDirLayout = new QHBoxLayout;
- workingDirLayout->addWidget(m_workingDirectoryEntry);
- workingDirLayout->addWidget(workingDirSelector);
-
auto outputLayout = new QHBoxLayout;
outputLayout->addWidget(m_outputDirectory);
outputLayout->addWidget(outputDirSelector);
- QVBoxLayout* contentLayout = new QVBoxLayout;
- contentLayout->setSpacing(10);
- contentLayout->addWidget(new QLabel("Executable Path"));
- contentLayout->addLayout(pathEntryLayout);
- contentLayout->addWidget(new QLabel("Working Directory"));
- contentLayout->addLayout(workingDirLayout);
- contentLayout->addWidget(new QLabel("Command Line Arguments"));
- contentLayout->addWidget(m_argumentsEntry);
- contentLayout->addWidget(new QLabel("Trace Output Directory"));
- contentLayout->addLayout(outputLayout);
- contentLayout->addWidget(new QLabel("Start application With Recording Off"));
- contentLayout->addWidget(m_launchWithoutTracing);
-
+ // Button layout
QHBoxLayout* buttonLayout = new QHBoxLayout;
buttonLayout->setContentsMargins(0, 0, 0, 0);
@@ -108,7 +156,12 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
buttonLayout->addWidget(cancelButton);
buttonLayout->addWidget(acceptButton);
- layout->addLayout(contentLayout);
+ // Main layout
+ layout->addLayout(modeLayout);
+ layout->addWidget(m_launchGroup);
+ layout->addWidget(m_attachGroup);
+ layout->addWidget(new QLabel("Trace Output Directory"));
+ layout->addLayout(outputLayout);
layout->addStretch(1);
layout->addSpacing(10);
layout->addLayout(buttonLayout);
@@ -123,16 +176,60 @@ TTDRecordDialog::TTDRecordDialog(QWidget* parent, BinaryView* data) :
}
m_launchWithoutTracing->setChecked(false);
- setFixedSize(QDialog::sizeHint());
-
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
}
+void TTDRecordDialog::onModeChanged()
+{
+ bool isLaunchMode = m_launchModeRadio->isChecked();
+ m_launchGroup->setEnabled(isLaunchMode);
+ m_attachGroup->setEnabled(!isLaunchMode);
+}
+
+
+void TTDRecordDialog::selectProcess()
+{
+ AttachProcessDialog dialog(this, m_controller);
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ m_selectedPid = dialog.GetSelectedPid();
+ if (m_selectedPid > 0)
+ {
+ m_selectedProcessDisplay->setText(QString("PID: %1").arg(m_selectedPid));
+ }
+ }
+}
+
+
void TTDRecordDialog::apply()
{
+ // Validate output directory
+ if (m_outputDirectory->text().isEmpty())
+ {
+ QMessageBox::critical(this, "Invalid Configuration", "Please specify a trace output directory.");
+ return;
+ }
+
+ // Mode-specific validation
+ if (m_launchModeRadio->isChecked())
+ {
+ if (m_pathEntry->text().isEmpty())
+ {
+ QMessageBox::critical(this, "Invalid Configuration", "Please specify an executable path for launch mode.");
+ return;
+ }
+ }
+ else
+ {
+ if (m_selectedPid == 0)
+ {
+ QMessageBox::critical(this, "Invalid Configuration", "Please select a process to attach to.");
+ return;
+ }
+ }
+
DoTTDTrace();
-
accept();
}
@@ -197,11 +294,25 @@ void TTDRecordDialog::DoTTDTrace()
LogDebug("TTD Recorder in path %s", ttdPath.c_str());
auto ttdRecorder = fmt::format("\"{}\\TTD.exe\"", ttdPath);
- auto ttdCommandLine = fmt::format("-accepteula -out \"{}\" {} -launch \"{}\" {}",
- m_outputDirectory->text().toStdString(),
- m_launchWithoutTracing->isChecked() ? "-tracingOff -recordMode Manual" : "",
- m_pathEntry->text().toStdString(),
- m_argumentsEntry->text().toStdString());
+ std::string ttdCommandLine;
+
+ if (m_launchModeRadio->isChecked())
+ {
+ // Launch mode - existing functionality
+ ttdCommandLine = fmt::format("-accepteula -out \"{}\" {} -launch \"{}\" {}",
+ m_outputDirectory->text().toStdString(),
+ m_launchWithoutTracing->isChecked() ? "-tracingOff -recordMode Manual" : "",
+ m_pathEntry->text().toStdString(),
+ m_argumentsEntry->text().toStdString());
+ }
+ else
+ {
+ // Attach mode - new functionality
+ ttdCommandLine = fmt::format("-accepteula -out \"{}\" -attach {}",
+ m_outputDirectory->text().toStdString(),
+ m_selectedPid);
+ }
+
LogWarn("TTD tracer cmd: %s %s", ttdRecorder.c_str(), ttdCommandLine.c_str());
SHELLEXECUTEINFOA info = {0};
@@ -210,7 +321,8 @@ void TTDRecordDialog::DoTTDTrace()
info.lpVerb = "runas";
info.lpFile = ttdRecorder.c_str();
info.lpParameters = ttdCommandLine.c_str();
- info.lpDirectory = m_workingDirectoryEntry->text().toStdString().c_str();
+ info.lpDirectory = m_launchModeRadio->isChecked() ?
+ m_workingDirectoryEntry->text().toStdString().c_str() : nullptr;
info.nShow = SW_NORMAL;
bool ret = ShellExecuteExA(&info);
if (ret == FALSE)
diff --git a/ui/ttdrecord.h b/ui/ttdrecord.h
index 7f0b85c4..24ed4b2b 100644
--- a/ui/ttdrecord.h
+++ b/ui/ttdrecord.h
@@ -22,6 +22,9 @@ limitations under the License.
#include
#include
#include
+#include
+#include
+#include
#include "inttypes.h"
#include "binaryninjaapi.h"
#include "viewframe.h"
@@ -30,6 +33,9 @@ limitations under the License.
using namespace BinaryNinjaDebuggerAPI;
+// Forward declare the attach process dialog
+class AttachProcessDialog;
+
class TTDRecordDialog : public QDialog
{
Q_OBJECT
@@ -41,6 +47,16 @@ class TTDRecordDialog : public QDialog
QLineEdit* m_argumentsEntry;
QLineEdit* m_outputDirectory;
QCheckBox* m_launchWithoutTracing;
+
+ // New UI elements for attach mode
+ QRadioButton* m_launchModeRadio;
+ QRadioButton* m_attachModeRadio;
+ QButtonGroup* m_modeButtonGroup;
+ QGroupBox* m_launchGroup;
+ QGroupBox* m_attachGroup;
+ QPushButton* m_selectProcessButton;
+ QLineEdit* m_selectedProcessDisplay;
+ uint32_t m_selectedPid;
public:
TTDRecordDialog(QWidget* parent, BinaryView* data);
@@ -49,4 +65,6 @@ class TTDRecordDialog : public QDialog
private Q_SLOTS:
void apply();
+ void onModeChanged();
+ void selectProcess();
};