Merge remote-tracking branch

'origin/GP-2594-dragonmacher-xrefs-dialog--SQUASHED' (Closes #3851)
This commit is contained in:
Ryan Kurtz 2022-09-27 00:48:07 -04:00
commit 2ebfb4d6fa
8 changed files with 332 additions and 20 deletions

View file

@ -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>

View file

@ -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) {

View file

@ -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();
}

View file

@ -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 "";
}
}
}

View file

@ -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) {

View file

@ -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 -> {

View file

@ -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;
}

View file

@ -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);