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
|
||||
===========
|
||||
|
||||
|
@ -46,6 +46,11 @@ to know what you can do to help the project!
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
: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