GP-1645 Added relative pointer action in the decompiler

This commit is contained in:
caheckman 2022-01-03 12:46:32 -05:00 committed by ghidra1
parent ca410b1274
commit ec5b6aada7
4 changed files with 540 additions and 3 deletions

View file

@ -812,6 +812,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
RetypeLocalAction retypeLocalAction = new RetypeLocalAction();
setGroupInfo(retypeLocalAction, variableGroup, subGroupPosition++);
CreatePointerRelative createRelativeAction = new CreatePointerRelative();
setGroupInfo(createRelativeAction, variableGroup, subGroupPosition++);
RetypeGlobalAction retypeGlobalAction = new RetypeGlobalAction();
setGroupInfo(retypeGlobalAction, variableGroup, subGroupPosition++);
@ -991,6 +994,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(setEquateAction);
addLocalAction(removeEquateAction);
addLocalAction(retypeLocalAction);
addLocalAction(createRelativeAction);
addLocalAction(retypeGlobalAction);
addLocalAction(retypeReturnAction);
addLocalAction(retypeFieldAction);

View file

@ -0,0 +1,529 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile.actions;
import java.awt.BorderLayout;
import java.util.*;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.label.GLabel;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.datatype.DataTypeSelectionDialog;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.data.PointerTypedefInspector;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.util.Msg;
import ghidra.util.UndefinedFunction;
import ghidra.util.data.DataTypeParser;
import ghidra.util.layout.VerticalLayout;
public class CreatePointerRelative extends RetypeLocalAction {
private DataType initialParent;
private int initialOffset;
private DataType userDataType = null;
private CategoryPath userPath;
private int userOffset;
private String userName;
private HighSymbol highSymbol;
private Varnode userVarnode;
private int pointerSize;
private TypeDef relativePointer;
public class RelativePointerDialog extends DataTypeSelectionDialog {
private Program program;
private JPanel updatedPanel;
private JTextField offsetField;
private JTextField nameField;
public RelativePointerDialog(PluginTool pluginTool, Program prog) {
super(pluginTool, prog.getDataTypeManager(), -1,
DataTypeParser.AllowedDataTypes.FIXED_LENGTH);
program = prog;
DataTypeSelectionEditor editor = getEditor();
// Remove the listener that causes a RETURN key being pressed while selecting
// a data-type to trigger okCallback
CellEditorListener[] listeners = editor.getCellEditorListeners();
if (listeners.length != 0) {
CellEditorListener lastListen = listeners[listeners.length - 1];
editor.removeCellEditorListener(lastListen);
}
}
public void setInitialOffset(int off) {
offsetField.setText(Integer.toString(off));
}
public void setInitialName(String nm) {
nameField.setText(nm);
}
@Override
protected void okCallback() {
String valueString = offsetField.getText();
try {
userOffset = Integer.decode(valueString);
}
catch (NumberFormatException ex) {
setStatusText("Invalid offset");
return;
}
userName = nameField.getText();
String errMessage = testNameValidity(userName);
if (errMessage != null) {
setStatusText(errMessage);
return;
}
DataTypeSelectionEditor editor = getEditor();
try {
if (!editor.validateUserSelection()) {
// users can only select existing data types
setStatusText("Unrecognized data type of \"" +
editor.getCellEditorValueAsText() + "\" entered.");
return;
}
userDataType = (DataType) editor.getCellEditorValue();
userPath = userDataType.getCategoryPath();
relativePointer = findPreexistingTypeDef(program);
if (relativePointer != null) {
if (PointerTypedefInspector
.getPointerComponentOffset(relativePointer) != userOffset) {
int yesno = OptionDialog.showYesNoDialog(updatedPanel,
"Data-type already exists",
"Data-type " + userName + " already exists with a different offset\n" +
"and may be used in other places.\n\n" +
"Do you want to change the offset?");
if (yesno != 1) {
userDataType = null;
userPath = null;
return; // Don't close the dialog
}
}
}
}
catch (InvalidDataTypeException e) {
setStatusText(e.getMessage());
return;
}
clearStatusText();
close();
}
@Override
protected JComponent createEditorPanel(DataTypeSelectionEditor dtEditor) {
setTitle("Create Relative Pointer");
updatedPanel = new JPanel();
updatedPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 10, 0));
updatedPanel.setLayout(new VerticalLayout(5));
JPanel dataTypePanel = new JPanel();
dataTypePanel.setLayout(new BoxLayout(dataTypePanel, BoxLayout.LINE_AXIS));
dataTypePanel.add(new GLabel(" Data-type:"), BorderLayout.WEST);
dataTypePanel.add(Box.createHorizontalStrut(5));
dataTypePanel.add(dtEditor.getEditorComponent(), BorderLayout.CENTER);
offsetField = new JTextField();
JPanel offsetPanel = new JPanel();
offsetPanel.setLayout(new BoxLayout(offsetPanel, BoxLayout.LINE_AXIS));
offsetPanel.add(new GLabel(" Offset:"), BorderLayout.WEST);
offsetPanel.add(Box.createHorizontalStrut(5));
offsetPanel.add(offsetField, BorderLayout.CENTER);
nameField = new JTextField(15);
JPanel namePanel = new JPanel();
namePanel.setLayout(new BoxLayout(namePanel, BoxLayout.LINE_AXIS));
namePanel.add(new GLabel(" Name:"), BorderLayout.WEST);
namePanel.add(Box.createHorizontalStrut(5));
namePanel.add(nameField, BorderLayout.CENTER);
updatedPanel.add(dataTypePanel);
updatedPanel.add(offsetPanel);
updatedPanel.add(namePanel);
return updatedPanel;
}
}
private static class TreeSearch {
public PcodeOp op;
public int slot;
public int offset;
public Iterator<PcodeOp> iterForward;
public DataType dataType;
public TreeSearch(PcodeOp o, int s, int off) {
op = o;
slot = s;
offset = off;
dataType = null;
}
public TreeSearch(Varnode vn, int off) {
iterForward = vn.getDescendants();
offset = off;
dataType = null;
}
public Varnode nextVarnode() {
if (slot != 0 && op.getOpcode() != PcodeOp.MULTIEQUAL) {
return null;
}
if (slot >= op.getNumInputs()) {
return null;
}
Varnode res = op.getInput(slot);
slot += 1;
return res;
}
public boolean isDoneBackward(DataType origType) {
return (dataType != null && dataType != origType && offset > 0);
}
public boolean isDoneForward(DataType origType) {
return (dataType != null && dataType != origType && offset < 0);
}
public void stripTypeDef() {
if (dataType instanceof TypeDef) {
TypeDef typedef = (TypeDef) dataType;
offset += PointerTypedefInspector.getPointerComponentOffset(typedef);
dataType = ((Pointer) typedef.getDataType()).getDataType();
}
}
public static DataType getValidDataType(Varnode vn) {
DataType dt = vn.getHigh().getDataType();
while (dt instanceof TypeDef) {
TypeDef typedef = (TypeDef) dt;
if (typedef.isPointer() &&
PointerTypedefInspector.getPointerComponentOffset(typedef) != 0) {
return typedef;
}
dt = typedef.getDataType();
}
if (!(dt instanceof Pointer)) {
return null;
}
dt = ((Pointer) dt).getDataType();
if (dt instanceof Structure) {
return dt;
}
return null;
}
public static TreeSearch searchBackward(Varnode vn, int depth) {
ArrayList<TreeSearch> stack = new ArrayList<>();
HashSet<SequenceNumber> marked = new HashSet<>();
TreeSearch currentNode = new TreeSearch(null, 0, 0);
DataType origType = getValidDataType(vn);
if (origType instanceof TypeDef) {
origType = null;
}
for (;;) {
if (vn != null) {
currentNode.dataType = getValidDataType(vn);
if (currentNode.isDoneBackward(origType)) {
currentNode.stripTypeDef();
return currentNode;
}
PcodeOp op = vn.getDef();
if (op != null && stack.size() < depth && marked.add(op.getSeqnum())) {
switch (op.getOpcode()) {
case PcodeOp.INDIRECT:
case PcodeOp.CAST:
case PcodeOp.COPY:
stack.add(new TreeSearch(op, 0, currentNode.offset));
break;
case PcodeOp.PTRSUB:
stack.add(new TreeSearch(op, 0,
currentNode.offset + (int) op.getInput(1).getOffset()));
break;
case PcodeOp.MULTIEQUAL:
stack.add(new TreeSearch(op, 0, currentNode.offset));
break;
default:
break;
}
}
}
if (stack.isEmpty()) {
break;
}
currentNode = stack.get(stack.size() - 1);
vn = currentNode.nextVarnode();
if (vn == null) {
stack.remove(stack.size() - 1);
}
}
return null;
}
public static TreeSearch searchForward(Varnode vn, int depth) {
ArrayList<TreeSearch> stack = new ArrayList<>();
HashSet<SequenceNumber> marked = new HashSet<>();
TreeSearch currentNode = new TreeSearch(vn, 0);
stack.add(currentNode);
DataType origType = getValidDataType(vn);
if (origType instanceof TypeDef) {
origType = null;
}
for (;;) {
if (stack.isEmpty()) {
break;
}
currentNode = stack.get(stack.size() - 1);
if (currentNode.iterForward.hasNext()) {
PcodeOp op = currentNode.iterForward.next();
if (stack.size() < depth && marked.add(op.getSeqnum())) {
TreeSearch nextNode = null;
switch (op.getOpcode()) {
case PcodeOp.MULTIEQUAL:
case PcodeOp.INDIRECT:
case PcodeOp.COPY:
case PcodeOp.CAST:
nextNode = new TreeSearch(op.getOutput(), currentNode.offset);
break;
case PcodeOp.PTRSUB:
nextNode = new TreeSearch(op.getOutput(),
currentNode.offset + (int) op.getInput(1).getOffset());
break;
case PcodeOp.PTRADD:
if (op.getInput(1).isConstant()) {
long off =
op.getInput(1).getOffset() * op.getInput(2).getOffset();
nextNode = new TreeSearch(op.getOutput(),
currentNode.offset + (int) off);
}
break;
default:
break;
}
if (nextNode != null) {
nextNode.dataType = getValidDataType(op.getOutput());
if (nextNode.isDoneForward(origType)) {
nextNode.stripTypeDef();
return nextNode;
}
stack.add(nextNode);
}
}
}
else {
stack.remove(stack.size() - 1); // Pop last node
}
}
return null;
}
}
public CreatePointerRelative() {
super("Create Relative Pointer");
// setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionRetypeVariable"));
setPopupMenuData(new MenuData(new String[] { "Adjust Pointer Offset" }, "Decompile"));
// setKeyBindingData(new KeyBindingData(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK));
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
Function function = context.getFunction();
if (function == null || function instanceof UndefinedFunction) {
return false;
}
ClangToken tokenAtCursor = context.getTokenAtCursor();
if (tokenAtCursor == null) {
return false;
}
if (!tokenAtCursor.isVariableRef()) {
return false;
}
HighVariable high = tokenAtCursor.getHighVariable();
if (high == null || high.getSymbol() == null) {
return false;
}
highSymbol = high.getSymbol();
DataType dataType = high.getDataType();
if (dataType instanceof TypeDef) {
dataType = ((TypeDef) dataType).getBaseDataType();
}
return (dataType instanceof Pointer);
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
clearInfo();
collectInitialInfo(context);
Program program = context.getProgram();
PluginTool tool = context.getTool();
RelativePointerDialog dialog = new RelativePointerDialog(tool, program);
dialog.setTabCommitsEdit(false);
if (initialParent != null) {
dialog.setInitialDataType(initialParent);
dialog.setInitialOffset(initialOffset);
dialog.setInitialName(buildDefaultName(initialParent, initialOffset));
}
tool.showDialog(dialog);
if (userDataType == null) { // Cancel
return;
}
createTypeDef(program, tool);
retypeSymbol(program, highSymbol, userVarnode, relativePointer, tool);
}
/**
* Make sure the given String works as a data-type name
* @param name is the given String
* @return an error message or null if the name is valid
*/
private String testNameValidity(String name) {
if (name == null || name.length() == 0) {
return "Must provide a name for the data-type";
}
return null;
}
/**
* Assuming the dialog has filled in the desired name and category of the relative pointer, as
* well as the baseType, search for a preexisting TypeDef data-type that matches the name.
* If no TypeDef is found return null. If a TypeDef is found but it is not a pointer to
* the baseType, throw an exception. Otherwise return the TypeDef.
* @param program is the Program in which to search
* @return the matching TypeDef or null
* @throws InvalidDataTypeException if a mismatched TypeDef already exists
*/
private TypeDef findPreexistingTypeDef(Program program) throws InvalidDataTypeException {
DataTypeManager dtm = program.getDataTypeManager();
DataType dt = dtm.getDataType(userPath, userName);
if (dt == null) {
return null;
}
if (!(dt instanceof TypeDef)) {
throw new InvalidDataTypeException("Data-type " + userName + " already exists");
}
TypeDef res = (TypeDef) dt;
if (!(res.getDataType() instanceof Pointer)) {
throw new InvalidDataTypeException(
"Data-type " + userName + " already exists and is not a pointer TypeDef");
}
DataType baseType = ((Pointer) res.getDataType()).getDataType();
if (!userDataType.getName().equals(baseType.getName()) ||
!userDataType.getCategoryPath().equals(baseType.getCategoryPath())) {
throw new InvalidDataTypeException(
"Data-type " + userName + " already exists and has a different base");
}
return res;
}
/**
* Assuming the parameters have been filled in by the dialog, create the matching TypeDef.
* If a new TypeDef is created, or if a pointer to the base DataType already exists as a TypeDef,
* set the ComponentOffsetSetting to the value selected by the dialog (userOffset).
* @param program is the Program owning the TypeDef
* @param tool is the tool showing the dialog
*/
private void createTypeDef(Program program, PluginTool tool) {
int transaction = program.startTransaction("Create Relative Pointer");
try {
if (relativePointer == null) {
PointerDataType ptr = new PointerDataType(userDataType, pointerSize);
TypedefDataType typedef = new TypedefDataType(userPath, userName, ptr);
relativePointer = (TypeDef) program.getDataTypeManager().resolve(typedef, null);
}
ComponentOffsetSettingsDefinition.DEF.setValue(relativePointer.getDefaultSettings(),
userOffset);
}
catch (IllegalArgumentException e) {
Msg.showError(this, tool.getToolFrame(), "Relative TypeDef Failed",
"Failed to create relative TypeDef: " + e.getMessage());
}
finally {
program.endTransaction(transaction, true);
}
}
private void clearInfo() {
userDataType = null;
userPath = null;
userOffset = 0;
userName = null;
initialParent = null;
initialOffset = 0;
}
private void collectInitialInfo(DecompilerActionContext context) {
ClangToken tokenAtCursor = context.getTokenAtCursor();
HighVariable high = tokenAtCursor.getHighVariable();
highSymbol = high.getSymbol();
DataType dataType = high.getDataType();
if (dataType instanceof TypeDef) {
dataType = ((TypeDef) dataType).getBaseDataType();
}
if (!(dataType instanceof Pointer)) {
return;
}
pointerSize = dataType.getLength();
Varnode vn = tokenAtCursor.getVarnode();
if (vn == null) { // If we don't have a specific Varnode instance
return; // We can't search for an offset
}
TreeSearch node = TreeSearch.searchBackward(vn, 5);
if (node != null) {
initialParent = node.dataType;
initialOffset = node.offset;
return;
}
node = TreeSearch.searchForward(vn, 5);
if (node != null) {
initialParent = node.dataType;
initialOffset = -node.offset;
}
}
/**
* Build a default name for a relative pointer, given the base data-type and offset
* @param dt is the given base data-type
* @param off is the given offset
* @return the name
*/
public static String buildDefaultName(DataType dt, int off) {
DataType inner = PcodeDataTypeManager.findPointerRelativeInner(dt, off);
StringBuilder buffer = new StringBuilder();
buffer.append(dt.getName());
int val = off;
if (val < 0) {
buffer.append("_ptrminus_");
val = -val;
}
else {
buffer.append("_ptr_");
}
buffer.append(val);
buffer.append('_').append(inner.getName());
return buffer.toString();
}
}

View file

@ -59,7 +59,11 @@ public class RetypeLocalAction extends AbstractDecompilerAction {
setKeyBindingData(new KeyBindingData(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK));
}
private void retypeSymbol(Program program, HighSymbol highSymbol, Varnode exactSpot,
protected RetypeLocalAction(String name) {
super(name);
}
protected void retypeSymbol(Program program, HighSymbol highSymbol, Varnode exactSpot,
DataType dt, PluginTool tool) {
HighFunction hfunction = highSymbol.getHighFunction();

View file

@ -260,7 +260,7 @@ public class PcodeDataTypeManager {
* @param offset is the offset into the base data-type
* @return the inner data-type
*/
private DataType findSubType(DataType base, int offset) {
public static DataType findPointerRelativeInner(DataType base, int offset) {
if (base instanceof TypeDef) {
base = ((TypeDef) base).getBaseDataType();
}
@ -297,7 +297,7 @@ public class PcodeDataTypeManager {
}
resBuf.append(">\n");
DataType parent = pointer.getDataType();
DataType ptrto = findSubType(parent, (int) offset);
DataType ptrto = findPointerRelativeInner(parent, (int) offset);
buildTypeRef(resBuf, ptrto, 1);
buildTypeRef(resBuf, parent, 1);
resBuf.append("\n<off>").append(offset).append("</off>\n");