GP-6260: a few more bits

GP-6260: from review
GP-6260: fix for load
GP-6260: working but...
GP-6260: new provider logic
This commit is contained in:
d-millar
2025-12-23 16:43:25 -05:00
parent dc5836119f
commit 75bc090c3e
10 changed files with 168 additions and 12 deletions

View File

@@ -22,14 +22,15 @@ import java.util.stream.Stream;
import db.Transaction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.DebuggerReadsMemoryTrait;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.bytesource.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.program.TraceProgramView;
@@ -49,6 +50,7 @@ public class DebuggerByteSource implements AddressableByteSource {
private final TraceProgramView view;
private final Target target;
private final DebuggerReadsMemoryTrait readsMem;
private final DebuggerStaticMappingService mappingService;
public DebuggerByteSource(PluginTool tool, TraceProgramView view, Target target,
DebuggerReadsMemoryTrait readsMem) {
@@ -56,6 +58,7 @@ public class DebuggerByteSource implements AddressableByteSource {
this.view = view;
this.target = target;
this.readsMem = readsMem;
this.mappingService = tool.getService(DebuggerStaticMappingService.class);
}
@Override
@@ -109,4 +112,17 @@ public class DebuggerByteSource implements AddressableByteSource {
}
}
}
@Override
public ProgramLocation getCanonicalLocation(Address address) {
ProgramLocation loc = AddressableByteSource.generateProgramLocation(view, address);
return mappingService.getStaticLocationFromDynamic(loc);
}
@Override
public Address rebaseFromCanonical(ProgramLocation location) {
ProgramLocation newloc = mappingService.getDynamicLocationFromStatic(view, location);
return newloc == null ? null : newloc.getAddress();
}
}

View File

@@ -163,6 +163,15 @@
reduced based on the values that changed.</P>
</BLOCKQUOTE>
<H4>"Compare to..." Button:</H4>
<BLOCKQUOTE>
<P>This button instantiates a new Memory Search Window using a new choice of program.
It rebases the existing results and initiates a scan of the byte values in all the matches
in the new copy of the results table. Depending on the selected scan option,
the set of matches in the table may be reduced based on the values that changed.</P>
</BLOCKQUOTE>
<H4>Scan Option Radio Buttons</H4>
<BLOCKQUOTE>

View File

@@ -18,6 +18,8 @@ package ghidra.features.base.memsearch.bytesource;
import java.util.List;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* Interface for reading bytes from a program. This provides a level of indirection for reading the
@@ -56,4 +58,23 @@ public interface AddressableByteSource {
*/
public void invalidate();
/**
* Convert byte source address to the canonical (static) location
* @param address address to be converted
* @return canonical location
*/
public ProgramLocation getCanonicalLocation(Address address);
/**
* Rebase a canonical location in the current byte source
* @param location location to be rebased
* @return address for new byte source
*/
public Address rebaseFromCanonical(ProgramLocation location);
static ProgramLocation generateProgramLocation(Program pgm, Address address) {
// A simpler constructor would be nice, but they all use getCodeUnitAddress
return new ProgramLocation(pgm, address, address, new int[0], address, 0, 0, 0);
}
}

View File

@@ -18,6 +18,7 @@ package ghidra.features.base.memsearch.bytesource;
import java.util.List;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
/**
* Implementation for an empty {@link AddressableByteSource}
@@ -39,4 +40,15 @@ public enum EmptyByteSource implements AddressableByteSource {
public void invalidate() {
// nothing to do
}
@Override
public ProgramLocation getCanonicalLocation(Address address) {
return AddressableByteSource.generateProgramLocation(null, address);
}
@Override
public Address rebaseFromCanonical(ProgramLocation location) {
return location.getAddress();
}
}

View File

@@ -21,6 +21,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.ProgramLocation;
/**
* {@link AddressableByteSource} implementation for a Ghidra {@link Program}
@@ -53,4 +54,18 @@ public class ProgramByteSource implements AddressableByteSource {
// nothing to do in the static case
}
@Override
public ProgramLocation getCanonicalLocation(Address address) {
return AddressableByteSource.generateProgramLocation(getProgram(), address);
}
@Override
public Address rebaseFromCanonical(ProgramLocation location) {
long offset = location.getByteAddress().subtract(location.getProgram().getImageBase());
return getProgram().getImageBase().add(offset);
}
public Program getProgram() {
return memory.getProgram();
}
}

View File

@@ -75,6 +75,11 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
loader = null;
}
@Override
protected void startInitialLoad() {
// Don't start up a load
}
void setLoader(MemoryMatchTableLoader loader) {
this.loader = loader;
reload();

View File

@@ -35,6 +35,7 @@ public class MemoryScanControlPanel extends JPanel {
private boolean hasResults;
private boolean isBusy;
private JButton scanButton;
private JButton compareButton;
MemoryScanControlPanel(MemorySearchProvider provider) {
super();
@@ -50,6 +51,16 @@ public class MemoryScanControlPanel extends JPanel {
"those that don't meet the selected change criteria");
add(scanButton);
add(Box.createHorizontalStrut(20));
compareButton = new JButton("Compare to...");
compareButton.setEnabled(false);
compareButton.setToolTipText("Create a new search using a new program, " +
"remapping the current results");
add(compareButton);
add(Box.createHorizontalStrut(20));
add(buildButtonPanel());
@@ -57,6 +68,7 @@ public class MemoryScanControlPanel extends JPanel {
helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Scan_Controls"));
scanButton.addActionListener(e -> provider.scan(selectedScanner));
compareButton.addActionListener(e -> provider.generateNewProvider(selectedScanner));
}
private JComponent buildButtonPanel() {
@@ -77,6 +89,7 @@ public class MemoryScanControlPanel extends JPanel {
this.hasResults = hasResults;
this.isBusy = isBusy;
updateScanButton();
updateCompareButton();
}
private void updateScanButton() {
@@ -87,4 +100,8 @@ public class MemoryScanControlPanel extends JPanel {
return hasResults && !isBusy;
}
private void updateCompareButton() {
compareButton.setEnabled(canScan());
}
}

View File

@@ -17,9 +17,8 @@ package ghidra.features.base.memsearch.gui;
import java.awt.*;
import java.time.Duration;
import java.util.HashSet;
import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import javax.swing.*;
@@ -36,8 +35,9 @@ import docking.widgets.OptionDialogBuilder;
import docking.widgets.table.actions.DeleteTableRowAction;
import generic.theme.GIcon;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigatableRemovalListener;
import ghidra.app.nav.*;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.script.AskDialog;
import ghidra.app.util.HelpTopics;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
@@ -80,6 +80,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
private Navigatable navigatable;
private Program program;
private AddressableByteSource byteSource;
private SearchHistory searchHistory;
private JComponent mainComponent;
private JPanel controlPanel;
@@ -107,7 +108,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
// used to show a temporary message over the table
private GGlassPaneMessage glassPaneMessage;
public MemorySearchProvider(MemorySearchPlugin plugin, Navigatable navigatable,
SearchSettings settings, MemorySearchOptions options, SearchHistory history) {
super(plugin.getTool(), "Memory Search", plugin.getName());
@@ -116,6 +117,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
this.options = options;
this.program = navigatable.getProgram();
this.byteSource = navigatable.getByteSource();
this.searchHistory = history;
// always initially use the byte ordering of the program, regardless of previous searches
if (settings == null) {
@@ -725,4 +727,44 @@ public class MemorySearchProvider extends ComponentProviderAdapter
glassPaneMessage.showCenteredMessage(message);
}
public void generateNewProvider(Scanner scanner) {
List<Navigatable> navigatables = NavigatableRegistry.getRegisteredNavigatables(tool);
Map<String, Navigatable> programMap = new HashMap<>();
for (Navigatable nav : navigatables) {
if (nav instanceof CodeViewerProvider listing) {
String key = listing.getTitle();
if (listing.getSubTitle() != null) {
key += ": " + listing.getSubTitle();
}
programMap.put(key, listing);
}
}
ArrayList<String> choices = new ArrayList<String>(programMap.keySet());
AskDialog<String> dialog = new AskDialog<String>(null, "Compare to...", "Program", AskDialog.STRING, choices, null);
if (dialog.isCanceled()) {
return;
}
Navigatable next = programMap.get(dialog.getChoiceValue());
MemorySearchProvider nextProvider = new MemorySearchProvider(plugin, next, model.getSettings(), options, new SearchHistory(searchHistory));
AddressableByteSource nextByteSource = nextProvider.byteSource;
nextProvider.setSearchInput(this.getSearchInput());
nextProvider.showScanPanel(true);
List<MemoryMatch> searchResults = getSearchResults();
List<MemoryMatch> rebasedResults = new ArrayList<>();
for (MemoryMatch match : searchResults) {
ProgramLocation canonicalLocation = byteSource.getCanonicalLocation(match.getAddress());
Address rebase = nextByteSource.rebaseFromCanonical(canonicalLocation);
if (rebase != null) {
MemoryMatch nextMatch = new MemoryMatch(rebase, match.getBytes(), match.getByteMatcher());
rebasedResults.add(nextMatch);
}
}
MemorySearchResultsPanel nextResultsPanel = nextProvider.getResultsPanel();
nextProvider.setBusy(true);
nextResultsPanel.refreshAndMaybeScanForChanges(nextByteSource, scanner, rebasedResults);
}
}

View File

@@ -119,6 +119,11 @@ public class MemorySearchResultsPanel extends JPanel {
TaskLauncher.launch(task);
}
public void refreshAndMaybeScanForChanges(AddressableByteSource byteSource, Scanner scanner, List<MemoryMatch> previousResults) {
RefreshAndScanTask task = new RefreshAndScanTask(byteSource, scanner, previousResults);
TaskLauncher.launch(task);
}
private MemoryMatchTableLoader createLoader(MemorySearcher searcher, Combiner combiner) {
if (!hasResults()) {
hasDeleted = false;
@@ -229,11 +234,17 @@ public class MemorySearchResultsPanel extends JPanel {
private AddressableByteSource byteSource;
private Scanner scanner;
private List<MemoryMatch> matchList;
public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner) {
this(byteSource, scanner, tableModel.getModelData());
}
public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner, List<MemoryMatch> matches) {
super("Refreshing", true, true, true);
this.byteSource = byteSource;
this.scanner = scanner;
this.matchList = matches;
}
private void tableLoadComplete(MemoryMatch match) {
@@ -250,16 +261,13 @@ public class MemorySearchResultsPanel extends JPanel {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
List<MemoryMatch> matches = tableModel.getModelData();
if (refreshByteValues(monitor, matches) && scanner != null) {
performScanFiltering(monitor, matches);
if (refreshByteValues(monitor, matchList) && scanner != null) {
performScanFiltering(monitor, matchList);
}
else {
tableModel.fireTableDataChanged(); // some data bytes may have changed, repaint
provider.refreshAndScanCompleted(null);
}
}
private boolean refreshByteValues(TaskMonitor monitor, List<MemoryMatch> matches) {

View File

@@ -27,6 +27,7 @@ import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.matcher.RegExByteMatcher;
import ghidra.program.model.address.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.task.TaskMonitor;
@@ -329,6 +330,16 @@ public class MemSearcherTest {
public void invalidate() {
// ignore
}
@Override
public ProgramLocation getCanonicalLocation(Address address) {
return AddressableByteSource.generateProgramLocation(null, address);
}
@Override
public Address rebaseFromCanonical(ProgramLocation location) {
return location.getAddress();
}
}
private Address addr(long offset) {