mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-09-13 21:56:19 +00:00
GP-2594 - Updated the Xrefs Dialog to allow users to show xrefs to thunk functions
This commit is contained in:
parent
45165ea167
commit
c22064cf61
|
@ -132,6 +132,22 @@
|
|||
<P align="left">This dialog lists all the Xref addresses, any labels that are at that address
|
||||
and a preview of the instruction at that address. Clicking on any row in the table will cause
|
||||
the browser to navigate to that address</P>
|
||||
|
||||
<H3><A NAME="Show_Thunk_Xrefs"></A>Show Thunk Xrefs
|
||||
<IMG border="0" src="images/ThunkFunction.gif" alt=""></H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
Available when viewing all Xrefs to a particular function, this toolbar action
|
||||
allows you to see not only xrefs directly to the function, but also any xrefs to
|
||||
functions that thunk that function as well. With this action toggled on you will
|
||||
see all Xrefs to the final 'base' thunked function, along with all Xrefs to
|
||||
functions that thunk that function, regardless of which function was used to launch
|
||||
the dialog. Alternatively, when toggled off, you will only see those Xrefs that
|
||||
point to the function used to launch the dialog.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A NAME="Keyboard_Controls">Keyboard Controls</H2>
|
||||
|
|
|
@ -25,17 +25,13 @@ import ghidra.program.model.symbol.Symbol;
|
|||
import ghidra.program.model.symbol.SymbolType;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* <CODE>EditThunkFunctionAction</CODE> allows the user to modify the function
|
||||
* referenced by this function
|
||||
*/
|
||||
class EditThunkFunctionAction extends ProgramContextAction {
|
||||
/** the plugin associated with this action. */
|
||||
FunctionPlugin funcPlugin;
|
||||
|
||||
/**
|
||||
* Create a new action, to edit a thunk function at the current location
|
||||
* @param functionPlugin does checking for this action
|
||||
* @param plugin does checking for this action
|
||||
*/
|
||||
public EditThunkFunctionAction(FunctionPlugin plugin) {
|
||||
super("Set Thunked Function", plugin.getName());
|
||||
|
@ -51,10 +47,6 @@ class EditThunkFunctionAction extends ProgramContextAction {
|
|||
setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when the action is invoked.
|
||||
* @param ActionEvent details regarding the invocation of this action
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ProgramActionContext context) {
|
||||
|
||||
|
|
|
@ -114,8 +114,9 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
// remove it; we will add it later to a group
|
||||
markerService.removeMarker(markerSet, program);
|
||||
loadMarkers();
|
||||
model.addTableModelListener(this);
|
||||
}
|
||||
|
||||
model.addTableModelListener(this);
|
||||
}
|
||||
|
||||
private JPanel buildMainPanel(GhidraProgramTableModel<T> tableModel, GoToService gotoService) {
|
||||
|
@ -154,8 +155,8 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection"));
|
||||
|
||||
selectionNavigationAction = new SelectionNavigationAction(plugin, table);
|
||||
selectionNavigationAction.setHelpLocation(
|
||||
new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
|
||||
selectionNavigationAction
|
||||
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
|
||||
|
||||
DockingAction externalGotoAction = new DockingAction("Go to External Location", getName()) {
|
||||
@Override
|
||||
|
@ -206,6 +207,10 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
tool.addLocalAction(this, removeItemsAction);
|
||||
}
|
||||
|
||||
public String getActionOwner() {
|
||||
return tableServicePlugin.getName();
|
||||
}
|
||||
|
||||
private JPanel createFilterFieldPanel(JTable table, AbstractSortedTableModel<T> sortedModel) {
|
||||
tableFilterPanel = new GhidraTableFilterPanel<>(table, sortedModel);
|
||||
tableFilterPanel.setToolTipText("Filter search results");
|
||||
|
@ -217,12 +222,18 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
buffer.append("(");
|
||||
buffer.append(programName);
|
||||
buffer.append(") ");
|
||||
|
||||
String filteredText = "";
|
||||
if (tableFilterPanel.isFiltered()) {
|
||||
filteredText = " of " + tableFilterPanel.getUnfilteredRowCount();
|
||||
}
|
||||
|
||||
int n = model.getRowCount();
|
||||
if (n == 1) {
|
||||
buffer.append(" (1 entry)");
|
||||
buffer.append(" (1 entry").append(filteredText).append(")");
|
||||
}
|
||||
else if (n > 1) {
|
||||
buffer.append(" (" + n + " entries)");
|
||||
buffer.append(" (").append(n).append(" entries").append(filteredText).append(")");
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/* ###
|
||||
* 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.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.Reference;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.ReferencesFromTableModel;
|
||||
import ghidra.util.table.field.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class FunctionXrefsTableModel extends ReferencesFromTableModel {
|
||||
|
||||
private Function function;
|
||||
private boolean showAllThunkXrefs;
|
||||
|
||||
public FunctionXrefsTableModel(Function function, Collection<Reference> directRefs, ServiceProvider sp,
|
||||
Program program) {
|
||||
super(directRefs, sp, program);
|
||||
this.function = function;
|
||||
|
||||
addTableColumn(new IsThunkTableColumn());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<ReferenceEndpoint> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
if (!showAllThunkXrefs) {
|
||||
super.doLoad(accumulator, monitor); // only include the supplied refs from our parent
|
||||
return;
|
||||
}
|
||||
|
||||
Function baseFunction = function;
|
||||
if (function.isThunk()) {
|
||||
baseFunction = function.getThunkedFunction(true);
|
||||
}
|
||||
|
||||
doLoadThunkFunctionReferences(baseFunction, accumulator, monitor);
|
||||
}
|
||||
|
||||
private void doLoadThunkFunctionReferences(Function baseFunction,
|
||||
Accumulator<ReferenceEndpoint> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
addReferences(accumulator, baseFunction.getEntryPoint());
|
||||
|
||||
Address[] thunks = baseFunction.getFunctionThunkAddresses(true);
|
||||
if (thunks == null) {
|
||||
return; // no thunks
|
||||
}
|
||||
|
||||
for (Address address : thunks) {
|
||||
monitor.checkCanceled();
|
||||
addReferences(accumulator, address);
|
||||
}
|
||||
}
|
||||
|
||||
private void addReferences(Accumulator<ReferenceEndpoint> accumulator, Address address) {
|
||||
|
||||
ProgramLocation location = new ProgramLocation(program, address);
|
||||
Set<Reference> refs = XReferenceUtils.getAllXrefs(location);
|
||||
for (Reference ref : refs) {
|
||||
boolean offcut = ReferenceUtils.isOffcut(program, ref.getToAddress());
|
||||
accumulator.add(new ThunkIncomingReferenceEndpoint(ref, offcut));
|
||||
}
|
||||
}
|
||||
|
||||
void toggleShowAllThunkXRefs() {
|
||||
this.showAllThunkXrefs = !showAllThunkXrefs;
|
||||
reload();
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Inner Classes
|
||||
//=================================================================================================
|
||||
|
||||
private class ThunkIncomingReferenceEndpoint extends IncomingReferenceEndpoint {
|
||||
|
||||
public ThunkIncomingReferenceEndpoint(Reference r, boolean isOffcut) {
|
||||
super(r, isOffcut);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class IsThunkTableColumn
|
||||
extends AbstractProgramBasedDynamicTableColumn<ReferenceEndpoint, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Thunk?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ReferenceEndpoint rowObject, Settings settings, Program data,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
|
||||
Address toAddress = rowObject.getReference().getToAddress();
|
||||
FunctionManager fm = program.getFunctionManager();
|
||||
Function f = fm.getFunctionAt(toAddress);
|
||||
if (f != null && f.isThunk()) {
|
||||
return "thunk";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ package ghidra.app.util;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.app.util.query.TableService;
|
||||
|
@ -26,8 +27,10 @@ import ghidra.program.model.data.DataUtilities;
|
|||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.table.ReferencesFromTableModel;
|
||||
import ghidra.util.table.field.ReferenceEndpoint;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class XReferenceUtils {
|
||||
|
||||
|
@ -204,13 +207,41 @@ public class XReferenceUtils {
|
|||
public static void showXrefs(Navigatable navigatable, ServiceProvider serviceProvider,
|
||||
TableService service, ProgramLocation location, Collection<Reference> xrefs) {
|
||||
|
||||
ReferencesFromTableModel model = new ReferencesFromTableModel(new ArrayList<>(xrefs),
|
||||
serviceProvider, location.getProgram());
|
||||
Address address = location.getAddress();
|
||||
Program program = location.getProgram();
|
||||
FunctionManager fm = program.getFunctionManager();
|
||||
Function function = fm.getFunctionAt(address);
|
||||
|
||||
ReferencesFromTableModel model;
|
||||
if (function == null) {
|
||||
model = new ReferencesFromTableModel(xrefs, serviceProvider, program);
|
||||
}
|
||||
else {
|
||||
model = new FunctionXrefsTableModel(function, xrefs, serviceProvider, program);
|
||||
}
|
||||
|
||||
String title = generateXRefTitle(location);
|
||||
TableComponentProvider<ReferenceEndpoint> provider =
|
||||
service.showTable(title, "XRefs", model, "XRefs", navigatable);
|
||||
service.showTable(title, "Xrefs", model, "Xrefs", navigatable);
|
||||
provider.installRemoveItemsAction();
|
||||
|
||||
if (function != null) {
|
||||
//@formatter:off
|
||||
String actionName = "Show Thunk Xrefs";
|
||||
new ToggleActionBuilder(actionName, provider.getActionOwner())
|
||||
.toolBarIcon(ResourceManager.loadImage("images/ThunkFunction.gif"))
|
||||
.toolBarGroup("A")
|
||||
.helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, actionName))
|
||||
.selected(false)
|
||||
.onAction(c -> {
|
||||
((FunctionXrefsTableModel) model).toggleShowAllThunkXRefs();
|
||||
})
|
||||
.buildAndInstallLocal(provider);
|
||||
//@formatter:on
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String generateXRefTitle(ProgramLocation location) {
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.util.table;
|
|||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -45,7 +46,8 @@ public class ReferencesFromTableModel extends AddressBasedTableModel<ReferenceEn
|
|||
|
||||
private List<IncomingReferenceEndpoint> refs;
|
||||
|
||||
public ReferencesFromTableModel(List<Reference> refs, ServiceProvider sp, Program program) {
|
||||
public ReferencesFromTableModel(Collection<Reference> refs, ServiceProvider sp,
|
||||
Program program) {
|
||||
super("References", sp, program, null);
|
||||
|
||||
this.refs = refs.stream().map(r -> {
|
||||
|
|
|
@ -59,6 +59,12 @@ public class LabelTableColumn
|
|||
if (symbol != null) {
|
||||
return symbol.getName();
|
||||
}
|
||||
|
||||
Address address = rowObject.getAddress();
|
||||
if (address == Address.EXT_FROM_ADDRESS) {
|
||||
return address.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,12 @@ package ghidra.app.util.viewer.field;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.app.cmd.data.CreateStructureCmd;
|
||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||
|
@ -34,8 +37,7 @@ import ghidra.program.model.address.Address;
|
|||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.XRefHeaderFieldLocation;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
|
@ -121,10 +123,130 @@ public class XrefViewerTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertTableXRefCount(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewReferencesShowThunkXrefs_FromNonThunk() {
|
||||
|
||||
//
|
||||
// create thunk to 1001005 from 1001050
|
||||
//
|
||||
|
||||
// thunk lives at 1001050
|
||||
String thunkAddress = "1001050";
|
||||
String baseFunctionAddress = "1001005";
|
||||
createThunkFunction(thunkAddress, baseFunctionAddress);
|
||||
|
||||
/*
|
||||
Direct References
|
||||
|
||||
01001009 ?? LAB_01001007 READ
|
||||
01001050 thunk_FUN_01001005 ?? FUN_01001005 UNCONDITIONAL_CALL
|
||||
01001050 thunk_FUN_01001005 ?? FUN_01001005 THUNK
|
||||
|
||||
|
||||
References to the Thunk Function
|
||||
|
||||
01001046 ?? thunk_FUN_01001005 UNCONDITIONAL_CALL thunk
|
||||
*/
|
||||
|
||||
doubleClickXRef(baseFunctionAddress, "XREF[1]: ");
|
||||
ComponentProvider comp = waitForComponentProvider(TableComponentProvider.class);
|
||||
TableComponentProvider<?> tableProvider = (TableComponentProvider<?>) comp;
|
||||
GhidraProgramTableModel<?> model = tableProvider.getModel();
|
||||
assertEquals(2, model.getRowCount());
|
||||
|
||||
DockingActionIf showThunksAction = getLocalAction(tableProvider, "Show Thunk Xrefs");
|
||||
performAction(showThunksAction);
|
||||
waitForTableModel(model);
|
||||
|
||||
assertEquals(3, model.getRowCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewReferencesShowThunkXrefs_FromThunk() {
|
||||
|
||||
//
|
||||
// create thunk to 1001005 from 1001050
|
||||
//
|
||||
|
||||
// thunk lives at 1001050
|
||||
String thunkAddress = "1001050";
|
||||
String baseFunctionAddress = "1001005";
|
||||
createThunkFunction(thunkAddress, baseFunctionAddress);
|
||||
|
||||
/*
|
||||
Direct References
|
||||
|
||||
01001046 ?? thunk_FUN_01001005 UNCONDITIONAL_CALL thunk
|
||||
|
||||
|
||||
References to the thunk and the end thunked function
|
||||
|
||||
01001046 ?? thunk_FUN_01001005 UNCONDITIONAL_CALL thunk
|
||||
01001009 ?? LAB_01001007 READ
|
||||
01001050 thunk_FUN_01001005 ?? FUN_01001005 UNCONDITIONAL_CALL
|
||||
01001050 thunk_FUN_01001005 ?? FUN_01001005 THUNK
|
||||
*/
|
||||
|
||||
doubleClickXRef(thunkAddress, "XREF[1]: ");
|
||||
ComponentProvider comp = waitForComponentProvider(TableComponentProvider.class);
|
||||
TableComponentProvider<?> tableProvider = (TableComponentProvider<?>) comp;
|
||||
GhidraProgramTableModel<?> model = tableProvider.getModel();
|
||||
assertEquals(1, model.getRowCount());
|
||||
|
||||
DockingActionIf showThunksAction = getLocalAction(tableProvider, "Show Thunk Xrefs");
|
||||
performAction(showThunksAction);
|
||||
waitForTableModel(model);
|
||||
|
||||
assertEquals(3, model.getRowCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewReferencesFromNonFunctionDoesNotAddShowThunkXrefsAction() {
|
||||
|
||||
doubleClickXRef("0100100f", "XREF[2]: ");
|
||||
ComponentProvider comp = waitForComponentProvider(TableComponentProvider.class);
|
||||
TableComponentProvider<?> tableProvider = (TableComponentProvider<?>) comp;
|
||||
GhidraProgramTableModel<?> model = tableProvider.getModel();
|
||||
assertEquals(2, model.getRowCount());
|
||||
|
||||
DockingActionIf showThunksAction = getLocalAction(tableProvider, "Show Thunk Xrefs");
|
||||
assertNull(showThunksAction);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void createThunkFunction(String thunkAddressString, String thunkedAddressString) {
|
||||
|
||||
Address thunkAddress = addr(thunkAddressString);
|
||||
goTo(tool, program, thunkAddress);
|
||||
|
||||
// go to new function
|
||||
DockingActionIf thunkAction = getAction(tool, "Set Thunked Function");
|
||||
performAction(thunkAction, false);
|
||||
|
||||
// get dialog
|
||||
DialogComponentProvider dialog =
|
||||
waitForDialogComponent("Thunk Destination Function/Address");
|
||||
JTextField textField = findComponent(dialog, JTextField.class);
|
||||
setText(textField, thunkedAddressString);
|
||||
pressButtonByText(dialog, "OK");
|
||||
waitForProgram(program);
|
||||
|
||||
// add a reference to the thunk function that will show up later when showing all refs
|
||||
Address arbitraryAddress = addr(thunkAddressString).subtract(10);
|
||||
createReference(arbitraryAddress, thunkAddress);
|
||||
}
|
||||
|
||||
private void createReference(Address fromAddress, Address toAddress) {
|
||||
tx(program, () -> {
|
||||
ReferenceManager refManager = program.getReferenceManager();
|
||||
refManager.addMemoryReference(fromAddress, toAddress, RefType.UNCONDITIONAL_CALL,
|
||||
SourceType.ANALYSIS, 0);
|
||||
});
|
||||
}
|
||||
|
||||
private void assertTableXRefCount(int expectedRowCount) {
|
||||
ComponentProvider comp = waitForComponentProvider(TableComponentProvider.class);
|
||||
TableComponentProvider<?> provider = (TableComponentProvider<?>) comp;
|
||||
|
@ -186,6 +308,8 @@ public class XrefViewerTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
builder.createMemory(".text", "0x1001000", 0x6600);
|
||||
builder.createEntryPoint("1001000", "entrypoint");
|
||||
builder.createEmptyFunction(null, "1001005", 40, null);
|
||||
builder.createEmptyFunction(null, "1001050", 1, null);
|
||||
|
||||
builder.setBytes("1001005", "ff 74 24 04", true);
|
||||
builder.setBytes("10010a0", "ff 15 d4 10 00 01", true);
|
||||
|
||||
|
|
Loading…
Reference in a new issue