diff options
| author | Brian Potchik <brian@vector35.com> | 2025-04-27 10:29:51 -0400 |
|---|---|---|
| committer | Brian Potchik <brian@vector35.com> | 2025-04-27 10:29:51 -0400 |
| commit | 54990215c2ad8bda17de857a7fe97d9a7559364f (patch) | |
| tree | c48f5215c7b9bc4b70391dc8aea48bf72193e9ca | |
| parent | af87287534221d6c5b0e3425dfcbc84800b7c2f0 (diff) | |
Add initial Workflow Monitor UI and support for nested subflows.
| -rw-r--r-- | binaryninjaapi.h | 36 | ||||
| -rw-r--r-- | python/workflow.py | 22 | ||||
| -rw-r--r-- | ui/workflowmonitor.h | 83 | ||||
| -rw-r--r-- | workflow.cpp | 102 |
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"); } |
