Defining actions and rules
While the plugin creation shows the structure a plugin needs to adhere to, the plugin template also offers a sample plugin. The plugin called simple
has documented code for all available components. Our main interest lies in the definition of actions and rules of the Ghidra decompilation pipeline.
Actions
An action performs some form of transformation on the whole function. The implementation of an Action
follows the rules of all other actions within Ghidra. Of all methods in Action
, you only have to write clone
and apply
:
class SimpleAction : public Action {
public:
virtual Action *clone(const ActionGroupList &grouplist) const override {
// ...
}
virtual int4 apply(Funcdata &data) override {
// ...
}
};
Ghidra uses the clone
function to construct the action internally. When the user starts the decompilation, the Ghidra front end chooses the default group to run. The universal action has instances of every Action
and Ghidra constructs a new action tree based on the default group. It iterates over all actions inside the universal action tree and calls clone
on the actions. If the action identifies itself as part of the group it creates a new instance. Otherwise it returns a null pointer, enabling or disabling itself as a result. To write a clone
method, the SimpleAction
needs a constructor:
SimpleAction(const string &g, ReOxideInterface &reox, SimplePlugin &plugin)
: Action(0, "simpleaction", g), reox{reox}, simple_plugin{plugin} {
LOG_INFO("Initializing simpleaction");
}
Here we only need the group name string and the Action
base constructor. The SimpleAction
uses the plugin and ReOxide reference for communication with the ReOxide manager, but not every Action
needs this. Using the constructor we can then write the canonical form of clone
:
virtual Action *clone(const ActionGroupList &grouplist) const override {
if (!grouplist.contains(getGroup()))
return nullptr;
return new SimpleAction{getGroup(), reox, simple_plugin};
}
While the clone
method looks almost the same for all actions, the apply
method represents the core of the action. Here we perform a transformation on the Funcdata
object we receive. The return value indicates if the transform changed the function or not. A return value of 0 means the transform did not change the function. Our SimpleAction
does not perform a transformation but only sends a message to the ReOxide manager:
virtual int4 apply(Funcdata &data) override {
reox.send_string("simple_action triggered for " + data.getName());
return 0;
}
Finally, to allow ReOxide itself to create an object of SimpleAction
, we need to write a wrapper for the constructor. This function always follows the same signature, taking a ReOxide context and returning an Action
pointer:
static Action *new_SimpleAction(const reoxide::Context *ctx) {
SimplePlugin *plugin = dynamic_cast<SimplePlugin *>(ctx->plugin_context);
return new SimpleAction(ctx->group_name, *ctx->reoxide, *plugin);
}
The context object has all objects that we require in the constructor. The definition of the context in reoxide_plugin.hh
shows all available objects:
struct Context {
ghidra::Architecture* arch;
ghidra::AddrSpace* stackspace;
ghidra::ReOxideInterface* reoxide;
Plugin* plugin_context;
const char* group_name;
uint64_t extra_arg;
};
At the end of the plugin file, we now only need to register our newly defined SimpleAction
with the REOXIDE_ACTIONS
macro:
REOXIDE_ACTIONS({"simpleaction", new_SimpleAction})
Rules
A Rule
in Ghidra and ReOxide requires the same clone
function, the same constructor requirements, and the same freestanding construction function as an Action
. For that reason We will not repeat the details here, see the code of the plugin template instead. Besides clone
a Rule
needs two other methods:
class SimpleRule : public Rule {
public:
virtual void getOpList(vector<uint4> &oplist) const {
// ...
}
virtual int4 applyOp(PcodeOp *op, Funcdata &data) {
// ...
}
};
Because a rule matches a P-Code operation, you can specify the operation you want to match with the getOpList
method:
virtual void getOpList(vector<uint4> &oplist) const {
oplist.push_back(CPUI_CALL);
}
In this case, the rule will try to apply on all CPUI_CALL
P-Code operations. Since the method takes a list as parameter, you can specify more than one operation type to match. If a rule matches a P-Code operation, Ghidra will call the applyOp
method. The applyOp
method of a rule works the same as the apply
method of an action. Besides the Funcdata
context, the applyOp
method also receives the PcodeOp
it matched. We can again mark whether we changed the PcodeOp
/Funcdata
by returning 1 on change. The following snippet shows a Rule
extracting the function name from a CPUI_CALL
instruction and sending it to ReOxide:
virtual int4 applyOp(PcodeOp *op, Funcdata &data) {
Varnode *vn = op->getIn(0);
AddrSpace *spc = vn->getSpace();
if (spc->getType() != IPTR_FSPEC)
return 0;
FuncCallSpecs *fc = reinterpret_cast<FuncCallSpecs *>(vn->getOffset());
reox.send_string(fc->getName());
return 0;
}
At the end of our plugin file, we again need to register our construction function by using the REOXIDE_RULES
macro. To show the syntax for registering more than one rule, we register the same rule under a different name:
REOXIDE_RULES({"simple", new_SimpleRule}, {"basic", new_SimpleRule})