Created
March 1, 2024 11:58
-
-
Save agatti/ccb724bc33f8a63f9003dccaefeae8a8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Use the selected value as an offset for a reference with an user-chosen base block. | |
// @author Alessandro Gatti | |
// @category References | |
// @keybinding | |
// @menupath | |
// @toolbar | |
import docking.DialogComponentProvider; | |
import docking.widgets.table.AbstractSortedTableModel; | |
import docking.widgets.table.GBooleanCellRenderer; | |
import docking.widgets.table.GTableCellRenderer; | |
import docking.widgets.table.SelectionManager; | |
import ghidra.app.script.GhidraScript; | |
import ghidra.program.model.address.Address; | |
import ghidra.program.model.address.OverlayAddressSpace; | |
import ghidra.program.model.listing.CodeUnit; | |
import ghidra.program.model.listing.Data; | |
import ghidra.program.model.listing.Instruction; | |
import ghidra.program.model.listing.Program; | |
import ghidra.program.model.mem.MemoryBlock; | |
import ghidra.program.model.mem.MemoryBlockSourceInfo; | |
import ghidra.program.model.mem.MemoryBlockType; | |
import ghidra.program.model.scalar.Scalar; | |
import ghidra.program.model.symbol.RefType; | |
import ghidra.program.model.symbol.SourceType; | |
import ghidra.program.util.OperandFieldLocation; | |
import ghidra.util.table.GhidraTable; | |
import ghidra.util.table.GhidraTableFilterPanel; | |
import java.awt.*; | |
import java.awt.event.KeyAdapter; | |
import java.awt.event.KeyEvent; | |
import java.awt.event.MouseAdapter; | |
import java.awt.event.MouseEvent; | |
import java.util.*; | |
import java.util.List; | |
import java.util.stream.Collectors; | |
import java.util.stream.IntStream; | |
import javax.swing.*; | |
import javax.swing.table.TableModel; | |
public class AddAnySegmentReference extends GhidraScript { | |
@Override | |
protected void run() throws Exception { | |
if (!(currentLocation instanceof OperandFieldLocation)) { | |
return; | |
} | |
var dialog = new SegmentSelectionDialog(currentProgram, (OperandFieldLocation) currentLocation); | |
state.getTool().showDialog(dialog); | |
} | |
private static class SegmentSelectionDialog extends DialogComponentProvider { | |
private final Program program; | |
private final SegmentsModel tableModel; | |
private final GhidraTable table; | |
private final OperandFieldLocation location; | |
public SegmentSelectionDialog(Program program, OperandFieldLocation location) { | |
super("Choose base for offset", true, true, true, false); | |
this.program = program; | |
this.location = location; | |
tableModel = new SegmentsModel(program); | |
table = buildTable(tableModel); | |
addWorkPanel(buildMainPanel()); | |
addOKButton(); | |
addCancelButton(); | |
okButton.setText("Select"); | |
okButton.setEnabled(tableModel.getRowCount() > 0); | |
setDefaultButton(okButton); | |
} | |
private JPanel buildMainPanel() { | |
var panel = new JPanel(new BorderLayout()); | |
panel.add(new JScrollPane(table), BorderLayout.CENTER); | |
panel.add(new GhidraTableFilterPanel<>(table, tableModel), BorderLayout.SOUTH); | |
return panel; | |
} | |
private GhidraTable buildTable(SegmentsModel model) { | |
var table = new SegmentsTable(model); | |
table.setPreferredScrollableViewportSize(new Dimension(700, 105)); | |
var monoCellRenderer = | |
new GTableCellRenderer() { | |
@Override | |
protected Font getDefaultFont() { | |
return fixedWidthFont; | |
} | |
}; | |
for (var columnName : | |
new String[] { | |
SegmentsModel.START_COL, SegmentsModel.END_COL, SegmentsModel.LENGTH_COL, | |
}) { | |
table.getColumn(columnName).setCellRenderer(monoCellRenderer); | |
} | |
for (var columnName : | |
new String[] { | |
SegmentsModel.READ_COL, | |
SegmentsModel.WRITE_COL, | |
SegmentsModel.EXECUTE_COL, | |
SegmentsModel.VOLATILE_COL, | |
SegmentsModel.INIT_COL | |
}) { | |
table.getColumn(columnName).setCellRenderer(new GBooleanCellRenderer()); | |
} | |
table.addKeyListener( | |
new KeyAdapter() { | |
@Override | |
public void keyPressed(KeyEvent keyEvent) { | |
if (keyEvent.getKeyCode() != KeyEvent.VK_ENTER) { | |
return; | |
} | |
table.setRowSelectionInterval(table.getSelectedRow(), table.getSelectedRow()); | |
keyEvent.consume(); | |
okCallback(); | |
} | |
}); | |
table.addMouseListener( | |
new MouseAdapter() { | |
@Override | |
public void mouseClicked(MouseEvent mouseEvent) { | |
if (mouseEvent.getClickCount() != 2) { | |
return; | |
} | |
table.setRowSelectionInterval(table.getSelectedRow(), table.getSelectedRow()); | |
mouseEvent.consume(); | |
okCallback(); | |
} | |
}); | |
var column = table.getColumn(SegmentsModel.READ_COL); | |
column.setMaxWidth(25); | |
column.setMinWidth(25); | |
column.setResizable(false); | |
column = table.getColumn(SegmentsModel.WRITE_COL); | |
column.setMaxWidth(25); | |
column.setMinWidth(25); | |
column.setResizable(false); | |
column = table.getColumn(SegmentsModel.EXECUTE_COL); | |
column.setMaxWidth(25); | |
column.setMinWidth(25); | |
column.setResizable(false); | |
column = table.getColumn(SegmentsModel.VOLATILE_COL); | |
column.setMaxWidth(65); | |
column.setMinWidth(65); | |
column.setResizable(false); | |
column = table.getColumn(SegmentsModel.BLOCK_TYPE_COL); | |
column.setMinWidth(25); | |
column = table.getColumn(SegmentsModel.INIT_COL); | |
column.setMaxWidth(25); | |
column.setMinWidth(25); | |
column.setResizable(false); | |
return table; | |
} | |
private Long getDataDisplacement(Data codeUnit) { | |
var value = codeUnit.getValue(); | |
if (value instanceof Address address) { | |
return address.getOffset(); | |
} | |
if (value instanceof Scalar scalar) { | |
return scalar.getUnsignedValue(); | |
} | |
return null; | |
} | |
private Long getOperandDisplacement(CodeUnit codeUnit) { | |
var operandIndex = location.getOperandIndex(); | |
var scalar = codeUnit.getScalar(operandIndex); | |
if (scalar != null) { | |
return scalar.getUnsignedValue(); | |
} | |
var operandSubIndex = location.getSubOperandIndex(); | |
if (operandSubIndex >= 0) { | |
var objects = ((Instruction) codeUnit).getDefaultOperandRepresentationList(operandIndex); | |
if (objects != null) { | |
if (objects.size() > operandSubIndex) { | |
var object = objects.get(operandSubIndex); | |
if (object instanceof Scalar) { | |
return ((Scalar) objects.get(operandSubIndex)).getUnsignedValue(); | |
} | |
} | |
for (var object : objects) { | |
if (object instanceof Scalar) { | |
return ((Scalar) object).getUnsignedValue(); | |
} | |
} | |
} | |
} | |
return null; | |
} | |
@Override | |
protected void okCallback() { | |
var selectedRowIndex = table.getSelectedRow(); | |
if (selectedRowIndex < 0) { | |
return; | |
} | |
var listing = program.getListing(); | |
var codeUnit = listing.getCodeUnitAt(location.getAddress()); | |
Long displacement = null; | |
if (codeUnit instanceof Data) { | |
var dataDisplacement = getDataDisplacement((Data) codeUnit); | |
if (dataDisplacement == null) { | |
return; | |
} | |
displacement = dataDisplacement; | |
} else { | |
if (location.getOperandIndex() >= 0) { | |
var operandDisplacement = getOperandDisplacement(codeUnit); | |
if (operandDisplacement == null) { | |
return; | |
} | |
displacement = operandDisplacement; | |
} | |
} | |
if (displacement == null) { | |
return; | |
} | |
var referenceManager = program.getReferenceManager(); | |
var memoryBlock = tableModel.getRowObject(selectedRowIndex); | |
var addressBase = memoryBlock.getStart(); | |
referenceManager.addMemoryReference( | |
location.getAddress(), | |
addressBase.add(displacement), | |
RefType.DATA, | |
SourceType.USER_DEFINED, | |
location.getOperandIndex()); | |
close(); | |
} | |
} | |
static String getOverlayBaseSpaceName(MemoryBlock block) { | |
var space = block.getStart().getAddressSpace(); | |
return space instanceof OverlayAddressSpace ovSpace | |
? ovSpace.getOverlayedSpace().getName() | |
: ""; | |
} | |
private static class SegmentsTable extends GhidraTable { | |
SegmentsTable(TableModel model) { | |
super(model); | |
setVisibleRowCount(10); | |
setAutoCreateColumnsFromModel(false); | |
} | |
@Override | |
protected <T> SelectionManager createSelectionManager() { | |
return null; | |
} | |
} | |
private static class SegmentsModel extends AbstractSortedTableModel<MemoryBlock> { | |
static final byte NAME = 0; | |
static final byte START = 1; | |
static final byte END = 2; | |
static final byte LENGTH = 3; | |
static final byte READ = 4; | |
static final byte WRITE = 5; | |
static final byte EXECUTE = 6; | |
static final byte VOLATILE = 7; | |
static final byte OVERLAY = 8; | |
static final byte BLOCK_TYPE = 9; | |
static final byte INIT = 10; | |
static final byte BYTE_SOURCE = 11; | |
static final byte SOURCE = 12; | |
static final byte COMMENT = 13; | |
static final String NAME_COL = "Name"; | |
static final String START_COL = "Start"; | |
static final String END_COL = "End"; | |
static final String LENGTH_COL = "Length"; | |
static final String READ_COL = "R"; | |
static final String WRITE_COL = "W"; | |
static final String EXECUTE_COL = "X"; | |
static final String VOLATILE_COL = "Volatile"; | |
static final String OVERLAY_COL = "Overlayed Space"; | |
static final String BLOCK_TYPE_COL = "Type"; | |
static final String INIT_COL = "Initialized"; | |
static final String BYTE_SOURCE_COL = "Byte Source"; | |
static final String SOURCE_COL = "Source"; | |
static final String COMMENT_COL = "Comment"; | |
private static final String[] COLUMN_NAMES = { | |
NAME_COL, | |
START_COL, | |
END_COL, | |
LENGTH_COL, | |
READ_COL, | |
WRITE_COL, | |
EXECUTE_COL, | |
VOLATILE_COL, | |
OVERLAY_COL, | |
BLOCK_TYPE_COL, | |
INIT_COL, | |
BYTE_SOURCE_COL, | |
SOURCE_COL, | |
COMMENT_COL | |
}; | |
private final List<MemoryBlock> segmentsList; | |
SegmentsModel(Program program) { | |
super(START); | |
segmentsList = new ArrayList<>(Arrays.stream(program.getMemory().getBlocks()).toList()); | |
} | |
@Override | |
public String getName() { | |
return "Segments List"; | |
} | |
@Override | |
public List<MemoryBlock> getModelData() { | |
return segmentsList; | |
} | |
@Override | |
public String getColumnName(int column) { | |
return COLUMN_NAMES[column]; | |
} | |
@Override | |
public Object getColumnValueForRow(MemoryBlock memoryBlock, int columnIndex) { | |
switch (columnIndex) { | |
case NAME: | |
return memoryBlock.getName(); | |
case START: | |
return memoryBlock.getStart().toString(); | |
case END: | |
return memoryBlock.getEnd().toString(); | |
case LENGTH: | |
return "0x" + Long.toHexString(memoryBlock.getEnd().subtract(memoryBlock.getStart()) + 1); | |
case READ: | |
return memoryBlock.isRead() ? Boolean.TRUE : Boolean.FALSE; | |
case WRITE: | |
return memoryBlock.isWrite() ? Boolean.TRUE : Boolean.FALSE; | |
case EXECUTE: | |
return memoryBlock.isExecute() ? Boolean.TRUE : Boolean.FALSE; | |
case VOLATILE: | |
return memoryBlock.isVolatile() ? Boolean.TRUE : Boolean.FALSE; | |
case OVERLAY: | |
return getOverlayBaseSpaceName(memoryBlock); | |
case INIT: | |
var blockType = memoryBlock.getType(); | |
return blockType == MemoryBlockType.BIT_MAPPED | |
? null | |
: (memoryBlock.isInitialized() ? Boolean.TRUE : Boolean.FALSE); | |
case BYTE_SOURCE: | |
var sourceInfos = memoryBlock.getSourceInfos(); | |
var limited = sourceInfos.size() < 5 ? sourceInfos : sourceInfos.subList(0, 4); | |
var description = | |
limited.stream() | |
.map(MemoryBlockSourceInfo::getDescription) | |
.collect(Collectors.joining(" | ")); | |
if (limited != sourceInfos) { | |
description += "..."; | |
} | |
return description; | |
case SOURCE: | |
if ((memoryBlock.getType() == MemoryBlockType.BIT_MAPPED) | |
|| (memoryBlock.getType() == MemoryBlockType.BYTE_MAPPED)) { | |
MemoryBlockSourceInfo info = memoryBlock.getSourceInfos().get(0); | |
assert info.getMappedRange().isPresent(); | |
return info.getMappedRange().get().getMinAddress().toString(); | |
} | |
return memoryBlock.getSourceName(); | |
case COMMENT: | |
return memoryBlock.getComment(); | |
case BLOCK_TYPE: | |
return memoryBlock.getType().toString(); | |
default: | |
return "UNKNOWN"; | |
} | |
} | |
@Override | |
public boolean isSortable(int columnIndex) { | |
return columnIndex != READ | |
&& columnIndex != WRITE | |
&& columnIndex != EXECUTE | |
&& columnIndex != VOLATILE | |
&& columnIndex != INIT; | |
} | |
@Override | |
public int getColumnCount() { | |
return COLUMN_NAMES.length; | |
} | |
@Override | |
public int findColumn(String columnName) { | |
return IntStream.range(0, COLUMN_NAMES.length) | |
.filter(index -> COLUMN_NAMES[index].equals(columnName)) | |
.findFirst() | |
.orElse(0); | |
} | |
@Override | |
protected Comparator<MemoryBlock> createSortComparator(int columnIndex) { | |
return columnIndex == BYTE_SOURCE | |
? super.createSortComparator(columnIndex) | |
: new MemoryMapComparator(columnIndex); | |
} | |
@Override | |
public Class<?> getColumnClass(int columnIndex) { | |
return columnIndex == READ | |
|| columnIndex == WRITE | |
|| columnIndex == EXECUTE | |
|| columnIndex == VOLATILE | |
|| columnIndex == INIT | |
? Boolean.class | |
: String.class; | |
} | |
} | |
private record MemoryMapComparator(int sortColumn) implements Comparator<MemoryBlock> { | |
@Override | |
public int compare(MemoryBlock first, MemoryBlock second) { | |
return switch (sortColumn) { | |
case SegmentsModel.NAME -> first.getName().compareToIgnoreCase(second.getName()); | |
case SegmentsModel.START -> first.getStart().compareTo(second.getStart()); | |
case SegmentsModel.END -> first.getEnd().compareTo(second.getEnd()); | |
case SegmentsModel.LENGTH -> (int) (first.getSize() - second.getSize()); | |
case SegmentsModel.READ -> ((first.isRead() ? 1 : -1) - (second.isRead() ? 1 : -1)); | |
case SegmentsModel.WRITE -> ((first.isWrite() ? 1 : -1) - (second.isWrite() ? 1 : -1)); | |
case SegmentsModel.EXECUTE -> | |
((first.isExecute() ? 1 : -1) - (second.isExecute() ? 1 : -1)); | |
case SegmentsModel.VOLATILE -> | |
((first.isVolatile() ? 1 : -1) - (second.isVolatile() ? 1 : -1)); | |
case SegmentsModel.OVERLAY -> | |
getOverlayBaseSpaceName(first).compareTo(getOverlayBaseSpaceName(second)); | |
case SegmentsModel.INIT -> | |
((first.isInitialized() ? 1 : -1) - (second.isInitialized() ? 1 : -1)); | |
case SegmentsModel.SOURCE -> | |
Objects.requireNonNullElse(first.getSourceName(), "") | |
.compareToIgnoreCase(Objects.requireNonNullElse(second.getSourceName(), "")); | |
case SegmentsModel.COMMENT -> | |
Objects.requireNonNullElse(first.getComment(), "") | |
.compareToIgnoreCase(Objects.requireNonNullElse(second.getComment(), "")); | |
case SegmentsModel.BLOCK_TYPE -> | |
first.getType().toString().compareToIgnoreCase(second.getType().toString()); | |
default -> throw new RuntimeException("Unimplemented column comparator: " + sortColumn); | |
}; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment