mirror of
https://github.com/rizinorg/cutter.git
synced 2024-11-23 21:09:42 +00:00
Add Documentation about Plugins
This commit is contained in:
parent
aa591e1a47
commit
b8e9e37c86
@ -1,3 +1,5 @@
|
|||||||
|
.. _api:
|
||||||
|
|
||||||
API Reference
|
API Reference
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -46,6 +46,11 @@ to know what you can do to help the project!
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Contents:
|
:caption: Contents:
|
||||||
:glob:
|
|
||||||
|
|
||||||
*
|
shortcuts
|
||||||
|
building
|
||||||
|
common-errors
|
||||||
|
code
|
||||||
|
plugins
|
||||||
|
api
|
||||||
|
|
||||||
|
35
docs/source/plugins.rst
Normal file
35
docs/source/plugins.rst
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
Plugins
|
||||||
|
=======
|
||||||
|
|
||||||
|
Cutter supports writing plugins in both C++ and Python.
|
||||||
|
If you are unsure which one to choose, we strongly suggest starting with Python since
|
||||||
|
it allows for a quicker and easier workflow.
|
||||||
|
|
||||||
|
If you plan to implement support for a new file format or architecture, Cutter plugins are not the correct approach.
|
||||||
|
Instead, you will want to implement a radare2 plugin, which is documented `here <https://radare.gitbooks.io/radare2book/plugins/intro.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Loading and Overview
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Plugins are loaded from an OS-dependent user-level directory.
|
||||||
|
To get the location of this directory and a list of currently loaded plugins, navigate to Edit -> Preferences -> Plugins.
|
||||||
|
|
||||||
|
.. image:: plugins/preferences-plugins.png
|
||||||
|
|
||||||
|
The plugins directory contains two subdirectories, ``native`` and ``python`` for C++ and Python plugins respectively,
|
||||||
|
which will be created automatically by Cutter.
|
||||||
|
|
||||||
|
Note that support for Python plugins is only available if Cutter was built with the options ``CUTTER_ENABLE_PYTHON``
|
||||||
|
and ``CUTTER_ENABLE_PYTHON_BINDINGS`` enabled.
|
||||||
|
This is the case for all official builds from GitHub Releases starting with version 1.8.0.
|
||||||
|
|
||||||
|
|
||||||
|
Creating Plugins
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:glob:
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
plugins/*
|
BIN
docs/source/plugins/disasm-dynamic.png
Normal file
BIN
docs/source/plugins/disasm-dynamic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
BIN
docs/source/plugins/disasm-static.png
Normal file
BIN
docs/source/plugins/disasm-static.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
docs/source/plugins/mydockwidget-action.png
Normal file
BIN
docs/source/plugins/mydockwidget-action.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
docs/source/plugins/mydockwidget.png
Normal file
BIN
docs/source/plugins/mydockwidget.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
docs/source/plugins/preferences-plugins.png
Normal file
BIN
docs/source/plugins/preferences-plugins.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
251
docs/source/plugins/tutorial-python.rst
Normal file
251
docs/source/plugins/tutorial-python.rst
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
Getting started with Python Plugins
|
||||||
|
===================================
|
||||||
|
|
||||||
|
This article provides a step-by-step guide on how to write a simple Python plugin for Cutter.
|
||||||
|
|
||||||
|
Create a python file, called ``myplugin.py`` for example, and add the following contents:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import cutter
|
||||||
|
|
||||||
|
class MyCutterPlugin(cutter.CutterPlugin):
|
||||||
|
name = "My Plugin"
|
||||||
|
description = "This plugin does awesome things!"
|
||||||
|
version = "1.0"
|
||||||
|
author = "1337 h4x0r"
|
||||||
|
|
||||||
|
def setupPlugin(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setupInterface(self, main):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_cutter_plugin():
|
||||||
|
return MyCutterPlugin()
|
||||||
|
|
||||||
|
This is the most basic code that makes up a plugin.
|
||||||
|
Python plugins in Cutter are regular Python modules that are imported automatically on startup.
|
||||||
|
In order to load the plugin, Cutter will call the function ``create_cutter_plugin()`` located
|
||||||
|
in the root of the module and expects it to return an instance of ``cutter.CutterPlugin``.
|
||||||
|
Normally, you shouldn't have to do anything else in this function.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The Cutter API is exposed through the ``cutter`` module.
|
||||||
|
This consists mostly of direct bindings of the original C++ classes, generated with Shiboken2.
|
||||||
|
For more detail about this API, see the Cutter C++ code or :ref:`api`.
|
||||||
|
|
||||||
|
The ``CutterPlugin`` subclass contains some meta-info and two callback methods:
|
||||||
|
|
||||||
|
* ``setupPlugin()`` is called right after the plugin is loaded and can be used to initialize the plugin itself.
|
||||||
|
* ``setupInterface()`` is called with the instance of MainWindow as an argument and should create and register any UI components.
|
||||||
|
|
||||||
|
Copy this file into the ``python`` subdirectory located under the plugins directory of Cutter and start the application.
|
||||||
|
You should see an entry for your plugin in the list under Edit -> Preferences -> Plugins.
|
||||||
|
Here, the absolute path to the plugins directory is shown too if you are unsure where to put your plugin:
|
||||||
|
|
||||||
|
.. image:: preferences-plugins.png
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
As mentioned, plugins are Python modules. This means, instead of only a single .py file, you can also
|
||||||
|
use a directory containing multiple python files and an ``__init__.py`` file that defines or imports the
|
||||||
|
``create_cutter_plugin()`` function.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you are working on a Unix-like system, instead of copying, you can also symlink your plugin into the plugins
|
||||||
|
directory, which lets you store the plugin somewhere else without having to copy the files over and over again.
|
||||||
|
|
||||||
|
|
||||||
|
Creating a Widget
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Next, we are going to add a simple dock widget. Extend the code as follows:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import cutter
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import QAction, QLabel
|
||||||
|
|
||||||
|
class MyDockWidget(cutter.CutterDockWidget):
|
||||||
|
def __init__(self, parent, action):
|
||||||
|
super(MyDockWidget, self).__init__(parent, action)
|
||||||
|
self.setObjectName("MyDockWidget")
|
||||||
|
self.setWindowTitle("My cool DockWidget")
|
||||||
|
|
||||||
|
label = QLabel(self)
|
||||||
|
self.setWidget(label)
|
||||||
|
label.setText("Hello World")
|
||||||
|
|
||||||
|
class MyCutterPlugin(cutter.CutterPlugin):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def setupInterface(self, main):
|
||||||
|
action = QAction("My Plugin", main)
|
||||||
|
action.setCheckable(True)
|
||||||
|
widget = MyDockWidget(main, action)
|
||||||
|
main.addPluginDockWidget(widget, action)
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
We are subclassing ``cutter.CutterDockWidget``, which is the base class for all dock widgets in Cutter,
|
||||||
|
and adding a label to it.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You can access the whole Qt5 API from Python, which is exposed by PySide2. For more information about this, refer to the
|
||||||
|
Documentation of `Qt <https://doc.qt.io/qt-5/reference-overview.html>`_ and `PySide2 <https://wiki.qt.io/Qt_for_Python>`_.
|
||||||
|
|
||||||
|
In our ``setupInterface()`` method, we create an instance of our dock widget and an action to be
|
||||||
|
added to the menu for showing and hiding the widget.
|
||||||
|
MainWindow provides a helper method called ``addPluginDockWidget()`` to easily register these.
|
||||||
|
|
||||||
|
When running Cutter now, you should see the widget:
|
||||||
|
|
||||||
|
.. image:: mydockwidget.png
|
||||||
|
|
||||||
|
... as well as the action:
|
||||||
|
|
||||||
|
.. image:: mydockwidget-action.png
|
||||||
|
|
||||||
|
|
||||||
|
Fetching Data
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Next, we want to show some actual data from the binary in our widget.
|
||||||
|
As an example, we will display the instruction and instruction size at the current position.
|
||||||
|
Extend the code as follows:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
class MyDockWidget(cutter.CutterDockWidget):
|
||||||
|
def __init__(self, parent, action):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
label = QLabel(self)
|
||||||
|
self.setWidget(label)
|
||||||
|
|
||||||
|
disasm = cutter.cmd("pd 1").strip()
|
||||||
|
|
||||||
|
instruction = cutter.cmdj("pdj 1")
|
||||||
|
size = instruction[0]["size"]
|
||||||
|
|
||||||
|
label.setText("Current disassembly:\n{}\nwith size {}".format(disasm, size))
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
We can access the data by calling radare2 commands and utilizing their output.
|
||||||
|
This is done by using the two functions ``cmd()`` and ``cmdj()``, which behave just as they
|
||||||
|
do in `r2pipe <https://radare.gitbooks.io/radare2book/scripting/r2pipe.html>`_.
|
||||||
|
|
||||||
|
Many commands in radare2 can be suffixed with a ``j`` to return JSON output.
|
||||||
|
``cmdj()`` will automatically deserialize the JSON into python dicts and lists, so the
|
||||||
|
information can be easily accessed.
|
||||||
|
|
||||||
|
In our case, we use the two commands ``pd`` (Print Disassembly) and ``pdj`` (Print Disassembly as JSON)
|
||||||
|
with a parameter of 1 to fetch a single line of disassembly.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
To try out commands, you can use the Console widget in Cutter. Almost all commands support a ``?`` suffix, like in
|
||||||
|
``pd?``, to show help and available sub-commands.
|
||||||
|
To get a general overview, enter a single ``?``.
|
||||||
|
|
||||||
|
The result will look like the following:
|
||||||
|
|
||||||
|
.. image:: disasm-static.png
|
||||||
|
|
||||||
|
Of course, since we only fetch the info once during the creation of the widget, the content never updates.
|
||||||
|
We are going to change that in the next section.
|
||||||
|
|
||||||
|
|
||||||
|
Reacting to Events
|
||||||
|
------------------
|
||||||
|
|
||||||
|
We want to update the content of our widget on every seek.
|
||||||
|
This can be done like the following:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
from PySide2.QtCore import QObject, SIGNAL
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
class MyDockWidget(cutter.CutterDockWidget):
|
||||||
|
def __init__(self, parent, action):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
self._label = QLabel(self)
|
||||||
|
self.setWidget(self._label)
|
||||||
|
|
||||||
|
QObject.connect(cutter.core(), SIGNAL("seekChanged(RVA)"), self.update_contents)
|
||||||
|
self.update_contents()
|
||||||
|
|
||||||
|
def update_contents(self):
|
||||||
|
disasm = cutter.cmd("pd 1").strip()
|
||||||
|
|
||||||
|
instruction = cutter.cmdj("pdj 1")
|
||||||
|
size = instruction[0]["size"]
|
||||||
|
|
||||||
|
self._label.setText("Current disassembly:\n{}\nwith size {}".format(disasm, size))
|
||||||
|
|
||||||
|
|
||||||
|
First, we move the update code to a separate method.
|
||||||
|
Then we call ``cutter.core()``, which returns the global instance of ``CutterCore``.
|
||||||
|
This class provides the Qt signal ``seekChanged(RVA)``, which is emitted every time the current seek changes.
|
||||||
|
We can simply connect this signal to our method and our widget will update as we expect it to:
|
||||||
|
|
||||||
|
.. image:: disasm-dynamic.png
|
||||||
|
|
||||||
|
For more information about Qt signals and slots, refer to `<https://doc.qt.io/qt-5/signalsandslots.html>`_.
|
||||||
|
|
||||||
|
Full Code
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import cutter
|
||||||
|
|
||||||
|
from PySide2.QtCore import QObject, SIGNAL
|
||||||
|
from PySide2.QtWidgets import QAction, QLabel
|
||||||
|
|
||||||
|
class MyDockWidget(cutter.CutterDockWidget):
|
||||||
|
def __init__(self, parent, action):
|
||||||
|
super(MyDockWidget, self).__init__(parent, action)
|
||||||
|
self.setObjectName("MyDockWidget")
|
||||||
|
self.setWindowTitle("My cool DockWidget")
|
||||||
|
|
||||||
|
self._label = QLabel(self)
|
||||||
|
self.setWidget(self._label)
|
||||||
|
|
||||||
|
QObject.connect(cutter.core(), SIGNAL("seekChanged(RVA)"), self.update_contents)
|
||||||
|
self.update_contents()
|
||||||
|
|
||||||
|
def update_contents(self):
|
||||||
|
disasm = cutter.cmd("pd 1").strip()
|
||||||
|
|
||||||
|
instruction = cutter.cmdj("pdj 1")
|
||||||
|
size = instruction[0]["size"]
|
||||||
|
|
||||||
|
self._label.setText("Current disassembly:\n{}\nwith size {}".format(disasm, size))
|
||||||
|
|
||||||
|
|
||||||
|
class MyCutterPlugin(cutter.CutterPlugin):
|
||||||
|
name = "My Plugin"
|
||||||
|
description = "This plugin does awesome things!"
|
||||||
|
version = "1.0"
|
||||||
|
author = "1337 h4x0r"
|
||||||
|
|
||||||
|
def setupPlugin(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setupInterface(self, main):
|
||||||
|
action = QAction("My Plugin", main)
|
||||||
|
action.setCheckable(True)
|
||||||
|
widget = MyDockWidget(main, action)
|
||||||
|
main.addPluginDockWidget(widget, action)
|
||||||
|
|
||||||
|
def create_cutter_plugin():
|
||||||
|
return MyCutterPlugin()
|
Loading…
Reference in New Issue
Block a user