Skip to content

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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
REOXIDE_RULES({"simple", new_SimpleRule}, {"basic", new_SimpleRule})