summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Potchik <brian@vector35.com>2025-04-27 10:29:51 -0400
committerBrian Potchik <brian@vector35.com>2025-04-27 10:29:51 -0400
commit54990215c2ad8bda17de857a7fe97d9a7559364f (patch)
treec48f5215c7b9bc4b70391dc8aea48bf72193e9ca
parentaf87287534221d6c5b0e3425dfcbc84800b7c2f0 (diff)
Add initial Workflow Monitor UI and support for nested subflows.
-rw-r--r--binaryninjaapi.h36
-rw-r--r--python/workflow.py22
-rw-r--r--ui/workflowmonitor.h83
-rw-r--r--workflow.cpp102
4 files changed, 175 insertions, 68 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index a749ac79..680b9592 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -10113,17 +10113,45 @@ namespace BinaryNinja {
bool PostRequest(const std::string& command);
public:
+
+ // TODO: Convert to BNWorkflowMachineStatus structure
+ struct Status
+ {
+ std::string state = "Invalid";
+ std::string activity;
+ bool localLogEnabled;
+ bool globalLogEnabled;
+ };
+
WorkflowMachine(Ref<BinaryView> view);
WorkflowMachine(Ref<Function> function);
bool PostJsonRequest(const std::string& request);
- /*! Start the workflow WorkflowMachine
+ void ShowTopology();
+
+ WorkflowMachine::Status GetStatus();
+
+ /*! Resume the workflow machine
+
+ Resumes the workflow machine for the given BinaryView or Function.
+ \return true if the command is accepted, false otherwise.
+ */
+ bool Resume();
+
+ /*! Start the workflow Machine
Starts the workflow machine for the given BinaryView or Function.
\return true if the command is accepted, false otherwise.
*/
bool Run();
+ /*! Configure the workflow machine
+
+ Configures the workflow machine.
+ \return true if the command is accepted, false otherwise.
+ */
+ bool Configure();
+
/*! Halt the workflow machine
Halts analysis at a resumable point.
@@ -10161,12 +10189,6 @@ namespace BinaryNinja {
*/
bool Step();
- // TODO: Add new BNWorkflowMachineStatus structure and cooresponding API
- // BNWorkflowMachineStatus GetStatus();
- // TODO remove the following APIs once the above is implemented
- std::string GetState();
- std::pair<bool, bool> GetLogStatus();
-
bool SetLogEnabled(bool enable, bool global = false);
std::optional<bool> QueryOverride(const std::string& activity);
diff --git a/python/workflow.py b/python/workflow.py
index d118b9f5..ba20591f 100644
--- a/python/workflow.py
+++ b/python/workflow.py
@@ -643,31 +643,27 @@ class WorkflowMachine:
return json.loads(core.BNPostWorkflowRequestForBinaryView(self.handle, request))
def configure(self, advanced: bool = True, incremental: bool = False):
- request = json.dumps({"command": "configure", "advanced": advanced, "incremental": incremental})
if self.is_function_machine:
+ request = json.dumps({"command": "configure", "advanced": advanced, "incremental": incremental})
return json.loads(core.BNPostWorkflowRequestForFunction(self.handle, request))
else:
+ request = json.dumps({"command": "configure"})
return json.loads(core.BNPostWorkflowRequestForBinaryView(self.handle, request))
- def resume(self):
- request = json.dumps({"command": "run"})
+ def resume(self, advanced: bool = True, incremental: bool = False):
if self.is_function_machine:
+ request = json.dumps({"command": "resume", "advanced": advanced, "incremental": incremental})
return json.loads(core.BNPostWorkflowRequestForFunction(self.handle, request))
else:
+ request = json.dumps({"command": "resume"})
return json.loads(core.BNPostWorkflowRequestForBinaryView(self.handle, request))
- def run(self):
- status = self.status()
- if 'machineState' in status and 'state' in status['machineState']:
- if status['machineState']['state'] == 'Idle':
- self.configure()
- else:
- raise AttributeError("Unknown status response!")
-
- request = json.dumps({"command": "run"})
+ def run(self, advanced: bool = True, incremental: bool = False):
if self.is_function_machine:
+ request = json.dumps({"command": "run", "advanced": advanced, "incremental": incremental})
return json.loads(core.BNPostWorkflowRequestForFunction(self.handle, request))
else:
+ request = json.dumps({"command": "run"})
return json.loads(core.BNPostWorkflowRequestForBinaryView(self.handle, request))
def halt(self):
@@ -783,8 +779,8 @@ class WorkflowMachineCLI(cmd.Cmd):
"l": "log",
"m": "metrics",
"d": "dump",
- "c": "resume",
"r": "run",
+ "c": "resume",
"h": "halt",
"s": "step",
"b": "breakpoint",
diff --git a/ui/workflowmonitor.h b/ui/workflowmonitor.h
new file mode 100644
index 00000000..7dcb05a5
--- /dev/null
+++ b/ui/workflowmonitor.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <QAction>
+#include <QToolBar>
+#include <QPushButton>
+#include <QIcon>
+#include <QLabel>
+#include <QPropertyAnimation>
+#include "binaryninjaapi.h"
+#include "sidebarwidget.h"
+#include "uicontext.h"
+
+
+class BINARYNINJAUIAPI WorkflowMonitorWidget : public SidebarWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal dotBrightness READ dotBrightness WRITE setDotBrightness)
+
+ BinaryViewRef m_data;
+ FunctionRef m_function;
+ WorkflowRef m_workflow;
+
+ Menu* m_menu;
+ ContextMenuManager* m_contextMenuManager;
+ UIActionHandler m_actionHandler;
+
+ QToolBar* m_toolbar;
+
+ QAction* m_startAction;
+ QAction* m_haltAction;
+ QAction* m_stepAction;
+ QAction* m_resetAction;
+ QAction* m_toggleSuspendAction;
+ QAction* m_toggleLogAction;
+ QAction* m_topologyAction;
+ QPushButton* m_contextButton;
+ QLabel* m_contextLabel;
+
+ std::map<std::string, QIcon> m_iconCache;
+ std::map<std::string, QColor> m_colorCache;
+ std::string m_lastState;
+ QColor m_lastStatusColor;
+ QColor m_labelColor;
+
+ QLabel* m_currentActivity;
+ QLabel* m_statusIndicator;
+ QPropertyAnimation* m_dotAnimation;
+ qreal m_dotBrightness;
+ bool m_animationRunning;
+
+ virtual void contextMenuEvent(QContextMenuEvent*) override;
+
+ void updateToolbarActions(bool force = false);
+ void updateToolbarIcons();
+ void setupToolbar();
+ void setupActions();
+ void updateStatusIndicator();
+
+ qreal dotBrightness() const;
+ void setDotBrightness(qreal brightness);
+
+public:
+ WorkflowMonitorWidget(BinaryViewRef data);
+ ~WorkflowMonitorWidget();
+
+ void notifyRefresh() override;
+ void notifyFontChanged() override;
+ void notifyThemeChanged() override;
+ void notifyViewLocationChanged(View* view, const ViewLocation& viewLocation) override;
+
+ void startDotAnimation();
+ void stopDotAnimation();
+};
+
+
+class BINARYNINJAUIAPI WorkflowMonitorWidgetType : public SidebarWidgetType
+{
+public:
+ WorkflowMonitorWidgetType();
+ SidebarWidget* createWidget(ViewFrame* frame, BinaryViewRef data) override;
+ SidebarWidgetLocation defaultLocation() const override { return SidebarWidgetLocation::LeftReference; }
+ SidebarContextSensitivity contextSensitivity() const override { return PerViewTypeSidebarContext; }
+};
diff --git a/workflow.cpp b/workflow.cpp
index 8146e923..d4b94eb4 100644
--- a/workflow.cpp
+++ b/workflow.cpp
@@ -164,87 +164,93 @@ bool WorkflowMachine::PostJsonRequest(const std::string& request)
}
-bool WorkflowMachine::Run()
+void WorkflowMachine::ShowTopology()
{
- return PostRequest("run");
+ if (m_function)
+ BNShowWorkflowReportForFunction(m_function->GetObject(), "topology");
+ else
+ BNShowWorkflowReportForBinaryView(m_view->GetObject(), "topology");
}
-bool WorkflowMachine::Halt()
+WorkflowMachine::Status WorkflowMachine::GetStatus()
{
- return PostRequest("halt");
+ WorkflowMachine::Status status;
+ rapidjson::Document request(rapidjson::kObjectType);
+ rapidjson::Document::AllocatorType& allocator = request.GetAllocator();
+ request.AddMember("command", "status", allocator);
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ request.Accept(writer);
+
+ string jsonResult;
+ if (m_function)
+ jsonResult = BNPostWorkflowRequestForFunction(m_function->GetObject(), buffer.GetString());
+ else
+ jsonResult = BNPostWorkflowRequestForBinaryView(m_view->GetObject(), buffer.GetString());
+
+ rapidjson::Document response(rapidjson::kObjectType);
+ response.Parse(jsonResult.c_str());
+ if (response.HasMember("machineState") && response["machineState"].HasMember("state") && response["machineState"].HasMember("activity"))
+ {
+ status.state = response["machineState"]["state"].GetString();
+ status.activity = response["machineState"]["activity"].GetString();
+ }
+ if (response.HasMember("logStatus") && response["logStatus"].HasMember("local") && response["logStatus"].HasMember("global"))
+ {
+ status.localLogEnabled = response["logStatus"]["local"].GetBool();
+ status.globalLogEnabled = response["logStatus"]["global"].GetBool();
+ }
+
+ return status;
}
-bool WorkflowMachine::Reset()
+bool WorkflowMachine::Resume()
{
- return PostRequest("reset");
+ return PostRequest("resume");
}
-bool WorkflowMachine::Enable()
+bool WorkflowMachine::Run()
{
- return PostRequest("enable");
+ return PostRequest("run");
}
-bool WorkflowMachine::Disable()
+bool WorkflowMachine::Configure()
{
- return PostRequest("disable");
+ return PostRequest("configure");
}
-bool WorkflowMachine::Step()
+bool WorkflowMachine::Halt()
{
- return PostRequest("step");
+ return PostRequest("halt");
}
-string WorkflowMachine::GetState()
+bool WorkflowMachine::Reset()
{
- rapidjson::Document request(rapidjson::kObjectType);
- rapidjson::Document::AllocatorType& allocator = request.GetAllocator();
- request.AddMember("command", "status", allocator);
- rapidjson::StringBuffer buffer;
- rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
- request.Accept(writer);
-
- string jsonResult;
- if (m_function)
- jsonResult = BNPostWorkflowRequestForFunction(m_function->GetObject(), buffer.GetString());
- else
- jsonResult = BNPostWorkflowRequestForBinaryView(m_view->GetObject(), buffer.GetString());
+ return PostRequest("reset");
+}
- rapidjson::Document response(rapidjson::kObjectType);
- response.Parse(jsonResult.c_str());
- if (response.HasMember("machineState") && response["machineState"].HasMember("state"))
- return response["machineState"]["state"].GetString();
- return "Invalid";
+bool WorkflowMachine::Enable()
+{
+ return PostRequest("enable");
}
-std::pair<bool, bool> WorkflowMachine::GetLogStatus()
+bool WorkflowMachine::Disable()
{
- rapidjson::Document request(rapidjson::kObjectType);
- rapidjson::Document::AllocatorType& allocator = request.GetAllocator();
- request.AddMember("command", "status", allocator);
- rapidjson::StringBuffer buffer;
- rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
- request.Accept(writer);
-
- string jsonResult;
- if (m_function)
- jsonResult = BNPostWorkflowRequestForFunction(m_function->GetObject(), buffer.GetString());
- else
- jsonResult = BNPostWorkflowRequestForBinaryView(m_view->GetObject(), buffer.GetString());
+ return PostRequest("disable");
+}
- rapidjson::Document response(rapidjson::kObjectType);
- response.Parse(jsonResult.c_str());
- if (response.HasMember("logStatus") && response["logStatus"].HasMember("local") && response["logStatus"].HasMember("global"))
- return {response["logStatus"]["local"].GetBool(), response["logStatus"]["global"].GetBool()};
- return {false, false};
+bool WorkflowMachine::Step()
+{
+ return PostRequest("step");
}