summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Smith <glenn@vector35.com>2019-07-23 23:48:05 -0400
committerGlenn Smith <glenn@vector35.com>2019-07-29 16:41:06 -0400
commit53c6d163da2c10fa3643e8e105fee101b31c0396 (patch)
tree382a8a1ecda5e4f5fbd3c09acd1a0322e9f60f0e
parent3ab5d0ceebd853147644d6cf1100740ff929e3d2 (diff)
Input completion through python
-rw-r--r--binaryninjaapi.h6
-rw-r--r--binaryninjacore.h5
-rw-r--r--python/scriptingprovider.py82
-rw-r--r--scriptingprovider.cpp35
-rw-r--r--ui/scriptingconsole.h53
5 files changed, 161 insertions, 20 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index 6b8d5fc2..1e5ab511 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -4099,6 +4099,7 @@ namespace BinaryNinja
static void SetCurrentBasicBlockCallback(void* ctxt, BNBasicBlock* block);
static void SetCurrentAddressCallback(void* ctxt, uint64_t addr);
static void SetCurrentSelectionCallback(void* ctxt, uint64_t begin, uint64_t end);
+ static char* CompleteInputCallback(void* ctxt, const char* text, uint64_t state);
virtual void DestroyInstance();
@@ -4110,6 +4111,7 @@ namespace BinaryNinja
virtual void SetCurrentBasicBlock(BasicBlock* block);
virtual void SetCurrentAddress(uint64_t addr);
virtual void SetCurrentSelection(uint64_t begin, uint64_t end);
+ virtual std::string CompleteInput(const std::string& text, uint64_t state);
void Output(const std::string& text);
void Error(const std::string& text);
@@ -4118,6 +4120,9 @@ namespace BinaryNinja
void RegisterOutputListener(ScriptingOutputListener* listener);
void UnregisterOutputListener(ScriptingOutputListener* listener);
+
+ std::string GetDelimiters();
+ void SetDelimiters(const std::string& delimiters);
};
class CoreScriptingInstance: public ScriptingInstance
@@ -4133,6 +4138,7 @@ namespace BinaryNinja
virtual void SetCurrentBasicBlock(BasicBlock* block) override;
virtual void SetCurrentAddress(uint64_t addr) override;
virtual void SetCurrentSelection(uint64_t begin, uint64_t end) override;
+ virtual std::string CompleteInput(const std::string& text, uint64_t state) override;
};
class ScriptingProvider: public StaticCoreRefCountObject<BNScriptingProvider>
diff --git a/binaryninjacore.h b/binaryninjacore.h
index 441e36b7..0481c5f4 100644
--- a/binaryninjacore.h
+++ b/binaryninjacore.h
@@ -1741,6 +1741,7 @@ extern "C"
void (*setCurrentBasicBlock)(void* ctxt, BNBasicBlock* block);
void (*setCurrentAddress)(void* ctxt, uint64_t addr);
void (*setCurrentSelection)(void* ctxt, uint64_t begin, uint64_t end);
+ char* (*completeInput)(void* ctxt, const char* text, uint64_t state);
};
struct BNScriptingProviderCallbacks
@@ -3640,6 +3641,9 @@ extern "C"
BINARYNINJACOREAPI void BNUnregisterScriptingInstanceOutputListener(BNScriptingInstance* instance,
BNScriptingOutputListener* callbacks);
+ BINARYNINJACOREAPI const char* BNGetScriptingInstanceDelimiters(BNScriptingInstance* instance);
+ BINARYNINJACOREAPI void BNSetScriptingInstanceDelimiters(BNScriptingInstance* instance, const char* delimiters);
+
BINARYNINJACOREAPI BNScriptingProviderInputReadyState BNGetScriptingInstanceInputReadyState(
BNScriptingInstance* instance);
BINARYNINJACOREAPI BNScriptingProviderExecuteResult BNExecuteScriptInput(BNScriptingInstance* instance,
@@ -3651,6 +3655,7 @@ extern "C"
BINARYNINJACOREAPI void BNSetScriptingInstanceCurrentAddress(BNScriptingInstance* instance, uint64_t addr);
BINARYNINJACOREAPI void BNSetScriptingInstanceCurrentSelection(BNScriptingInstance* instance,
uint64_t begin, uint64_t end);
+ BINARYNINJACOREAPI char* BNScriptingInstanceCompleteInput(BNScriptingInstance* instance, const char* text, uint64_t state);
// Main thread actions
BINARYNINJACOREAPI void BNRegisterMainThread(BNMainThreadCallbacks* callbacks);
diff --git a/python/scriptingprovider.py b/python/scriptingprovider.py
index fcbca6dc..b1405790 100644
--- a/python/scriptingprovider.py
+++ b/python/scriptingprovider.py
@@ -25,6 +25,8 @@ import ctypes
import threading
import abc
import sys
+import rlcompleter
+import re
# Binary Ninja components
import binaryninja
@@ -113,7 +115,10 @@ class ScriptingInstance(object):
self._cb.setCurrentBasicBlock = self._cb.setCurrentBasicBlock.__class__(self._set_current_basic_block)
self._cb.setCurrentAddress = self._cb.setCurrentAddress.__class__(self._set_current_address)
self._cb.setCurrentSelection = self._cb.setCurrentSelection.__class__(self._set_current_selection)
+ self._cb.completeInput = self._cb.completeInput.__class__(self._complete_input)
+ self._cb.completeInput.restype = ctypes.c_void_p
self.handle = core.BNInitScriptingInstance(provider.handle, self._cb)
+ self.delimiters = ' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?'
else:
self.handle = core.handle_of_type(handle, core.BNScriptingInstance)
self.listeners = []
@@ -189,6 +194,15 @@ class ScriptingInstance(object):
except:
log.log_error(traceback.format_exc())
+ def _complete_input(self, ctxt, text, state):
+ try:
+ if not isinstance(text, str):
+ text = text.decode("charmap")
+ return ctypes.cast(binaryninja.cstr(self.perform_complete_input(text, state)), ctypes.c_void_p).value
+ except:
+ log.log_error(traceback.format_exc())
+ return ctypes.cast(binaryninja.cstr(""), ctypes.c_void_p).value
+
@abc.abstractmethod
def perform_destroy_instance(self):
raise NotImplementedError
@@ -221,6 +235,10 @@ class ScriptingInstance(object):
def perform_set_current_selection(self, begin, end):
raise NotImplementedError
+ @abc.abstractmethod
+ def perform_complete_input(self, text, state):
+ raise NotImplementedError
+
@property
def input_ready_state(self):
return core.BNGetScriptingInstanceInputReadyState(self.handle)
@@ -262,6 +280,9 @@ class ScriptingInstance(object):
def set_current_selection(self, begin, end):
core.BNSetScriptingInstanceCurrentSelection(self.handle, begin, end)
+ def complete_input(self, text, state):
+ return core.BNScriptingInstanceCompleteInput(self.handlue, text, state)
+
def register_output_listener(self, listener):
listener._register(self.handle)
self.listeners.append(listener)
@@ -270,6 +291,14 @@ class ScriptingInstance(object):
if listener in self.listeners:
listener._unregister(self.handle)
self.listeners.remove(listener)
+
+ @property
+ def delimiters(self):
+ return core.BNGetScriptingInstanceDelimiters(self.handle)
+
+ @delimiters.setter
+ def delimiters(self, value):
+ core.BNSetScriptingInstanceDelimiters(self.handle, value)
class _ScriptingProviderMetaclass(type):
@@ -507,6 +536,8 @@ class PythonScriptingInstance(ScriptingInstance):
self.code = None
self.input = ""
+ self.completer = rlcompleter.Completer(namespace = self.locals)
+
self.interpreter.push("from binaryninja import *")
def execute(self, code):
@@ -549,26 +580,7 @@ class PythonScriptingInstance(ScriptingInstance):
PythonScriptingInstance._interpreter.value = self
try:
- self.active_view = self.current_view
- self.active_func = self.current_func
- self.active_block = self.current_block
- self.active_addr = self.current_addr
- self.active_selection_begin = self.current_selection_begin
- self.active_selection_end = self.current_selection_end
-
- self.locals["current_view"] = self.active_view
- self.locals["bv"] = self.active_view
- self.locals["current_function"] = self.active_func
- self.locals["current_basic_block"] = self.active_block
- self.locals["current_address"] = self.active_addr
- self.locals["here"] = self.active_addr
- self.locals["current_selection"] = (self.active_selection_begin, self.active_selection_end)
- if self.active_func is None:
- self.locals["current_llil"] = None
- self.locals["current_mlil"] = None
- else:
- self.locals["current_llil"] = self.active_func.llil
- self.locals["current_mlil"] = self.active_func.mlil
+ self.update_locals()
for line in code.split(b'\n'):
self.interpreter.push(line.decode('charmap'))
@@ -595,6 +607,29 @@ class PythonScriptingInstance(ScriptingInstance):
PythonScriptingInstance._interpreter.value = None
self.instance.input_ready_state = ScriptingProviderInputReadyState.ReadyForScriptExecution
+ def update_locals(self):
+ self.active_view = self.current_view
+ self.active_func = self.current_func
+ self.active_block = self.current_block
+ self.active_addr = self.current_addr
+ self.active_selection_begin = self.current_selection_begin
+ self.active_selection_end = self.current_selection_end
+
+ self.locals["current_view"] = self.active_view
+ self.locals["bv"] = self.active_view
+ self.locals["current_function"] = self.active_func
+ self.locals["current_basic_block"] = self.active_block
+ self.locals["current_address"] = self.active_addr
+ self.locals["here"] = self.active_addr
+ self.locals["current_selection"] = (self.active_selection_begin, self.active_selection_end)
+ if self.active_func is None:
+ self.locals["current_llil"] = None
+ self.locals["current_mlil"] = None
+ else:
+ self.locals["current_llil"] = self.active_func.llil
+ self.locals["current_mlil"] = self.active_func.mlil
+
+
def get_selected_data(self):
if self.active_view is None:
return None
@@ -680,6 +715,13 @@ class PythonScriptingInstance(ScriptingInstance):
self.interpreter.current_selection_begin = begin
self.interpreter.current_selection_end = end
+ @abc.abstractmethod
+ def perform_complete_input(self, text, state):
+ self.interpreter.update_locals()
+ result = self.interpreter.completer.complete(text, state)
+ if result is None:
+ return ""
+ return result
class PythonScriptingProvider(ScriptingProvider):
name = "Python"
diff --git a/scriptingprovider.cpp b/scriptingprovider.cpp
index 5e6d1a02..ed010100 100644
--- a/scriptingprovider.cpp
+++ b/scriptingprovider.cpp
@@ -61,6 +61,7 @@ ScriptingInstance::ScriptingInstance(ScriptingProvider* provider)
cb.setCurrentBasicBlock = SetCurrentBasicBlockCallback;
cb.setCurrentAddress = SetCurrentAddressCallback;
cb.setCurrentSelection = SetCurrentSelectionCallback;
+ cb.completeInput = CompleteInputCallback;
AddRefForRegistration();
m_object = BNInitScriptingInstance(provider->GetObject(), &cb);
}
@@ -128,6 +129,13 @@ void ScriptingInstance::SetCurrentSelectionCallback(void* ctxt, uint64_t begin,
}
+char* ScriptingInstance::CompleteInputCallback(void* ctxt, const char* text, uint64_t state)
+{
+ ScriptingInstance* instance = (ScriptingInstance*)ctxt;
+ return BNAllocString(instance->CompleteInput(text, state).c_str());
+}
+
+
void ScriptingInstance::DestroyInstance()
{
ReleaseForRegistration();
@@ -163,6 +171,12 @@ void ScriptingInstance::SetCurrentSelection(uint64_t, uint64_t)
}
+std::string ScriptingInstance::CompleteInput(const std::string&, uint64_t)
+{
+ return "";
+}
+
+
void ScriptingInstance::Output(const string& text)
{
BNNotifyOutputForScriptingInstance(m_object, text.c_str());
@@ -199,6 +213,18 @@ void ScriptingInstance::UnregisterOutputListener(ScriptingOutputListener* listen
}
+std::string ScriptingInstance::GetDelimiters()
+{
+ return BNGetScriptingInstanceDelimiters(m_object);
+}
+
+
+void ScriptingInstance::SetDelimiters(const std::string& delimiters)
+{
+ BNSetScriptingInstanceDelimiters(m_object, delimiters.c_str());
+}
+
+
CoreScriptingInstance::CoreScriptingInstance(BNScriptingInstance* instance): ScriptingInstance(instance)
{
}
@@ -246,6 +272,15 @@ void CoreScriptingInstance::SetCurrentSelection(uint64_t begin, uint64_t end)
}
+std::string CoreScriptingInstance::CompleteInput(const std::string& text, uint64_t state)
+{
+ char* result = BNScriptingInstanceCompleteInput(m_object, text.c_str(), state);
+ std::string ret = result;
+ BNFreeString(result);
+ return ret;
+}
+
+
ScriptingProvider::ScriptingProvider(const string& name): m_nameForRegister(name)
{
}
diff --git a/ui/scriptingconsole.h b/ui/scriptingconsole.h
index 482516c3..6c438f16 100644
--- a/ui/scriptingconsole.h
+++ b/ui/scriptingconsole.h
@@ -6,10 +6,14 @@
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QWidget>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QListView>
+#include <QtCore/QAbstractListModel>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
+#include <functional>
#include "binaryninjaapi.h"
#include "action.h"
#include "dockhandler.h"
@@ -20,18 +24,67 @@
class ScriptingConsole;
+class BINARYNINJAUIAPI ScriptingCompletionModel: public QAbstractListModel
+{
+ Q_OBJECT
+ std::vector<std::string> m_completions;
+
+public:
+ ScriptingCompletionModel(QWidget* parent);
+
+ virtual QModelIndex index(int row, int col, const QModelIndex& parent = QModelIndex()) const override;
+ virtual QModelIndex parent(const QModelIndex& i) const override;
+ virtual bool hasChildren(const QModelIndex& parent) const override;
+ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ virtual int columnCount(const QModelIndex& parent) const override;
+ virtual QVariant data(const QModelIndex& i, int role) const override;
+ void setModelData(const std::vector<std::string>& completions);
+};
+
+class BINARYNINJAUIAPI ScriptingCompletionPopup: public QDialog
+{
+ Q_OBJECT
+
+ QListView* m_list;
+ ScriptingCompletionModel* m_model;
+
+public:
+ ScriptingCompletionPopup(QWidget* parent);
+ void showWithData(QPoint pt, int cursorSize, const std::vector<std::string>& completions);
+ bool handleKeyEvent(QKeyEvent* event);
+
+private Q_SLOTS:
+ void clickRow(const QModelIndex& index);
+
+Q_SIGNALS:
+ void complete(QString text);
+};
+
class BINARYNINJAUIAPI ScriptingConsoleEdit: public QTextEdit
{
Q_OBJECT
+public:
+ typedef std::function<std::vector<std::string>(const std::string&)> CompletionCallback;
+private:
ScriptingConsole* m_console;
int m_charHeight;
bool m_continuation;
+ CompletionCallback m_completionCallback;
+ ScriptingCompletionPopup* m_popup;
+
+ uint64_t m_completionRegionStart;
+ uint64_t m_completionRegionInitialStop;
+ uint64_t m_completionRegionStop;
public:
ScriptingConsoleEdit(ScriptingConsole* parent);
void setCharHeight(int height);
void setContinutation(bool cont);
+ void setCompletionCallback(CompletionCallback callback) { m_completionCallback = callback; }
+
+private Q_SLOTS:
+ void complete(QString text);
protected:
virtual void keyPressEvent(QKeyEvent* event) override;