diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java index 3080d36a0b..94e410f18e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerByteSource.java @@ -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(); + } + } diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm index e46b80efa3..f5510bf233 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Memory.htm @@ -163,6 +163,15 @@ reduced based on the values that changed.
+++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.
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java index 9bbb081eed..f6b6550530 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/AddressableByteSource.java @@ -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); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java index a9e2f1ff33..f509d50806 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/EmptyByteSource.java @@ -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(); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java index 0506245e7b..39d44fa82d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java @@ -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(); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java index f7b63ee3d3..0b23004906 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryMatchTableModel.java @@ -75,6 +75,11 @@ public class MemoryMatchTableModel extends AddressBasedTableModel{ loader = null; } + @Override + protected void startInitialLoad() { + // Don't start up a load + } + void setLoader(MemoryMatchTableLoader loader) { this.loader = loader; reload(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java index eac955f614..8725fabedd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java @@ -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()); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java index 972e0d48de..356de33c3b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java @@ -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 navigatables = NavigatableRegistry.getRegisteredNavigatables(tool); + Map 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 choices = new ArrayList (programMap.keySet()); + AskDialog dialog = new AskDialog (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 searchResults = getSearchResults(); + List 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); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java index c9fc700058..e07d711f1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchResultsPanel.java @@ -119,6 +119,11 @@ public class MemorySearchResultsPanel extends JPanel { TaskLauncher.launch(task); } + public void refreshAndMaybeScanForChanges(AddressableByteSource byteSource, Scanner scanner, List 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 matchList; public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner) { + this(byteSource, scanner, tableModel.getModelData()); + } + + public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner, List 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 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 matches) { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java index 1c94a0fb1c..ef96d38e49 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/searcher/MemSearcherTest.java @@ -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) {