GP-432: Implementing dynamic memory search. Fixing actions to bind to focused listing.

This commit is contained in:
Dan 2021-05-19 12:10:47 -04:00
parent 22675e9f39
commit fc885e7837
15 changed files with 268 additions and 83 deletions

View File

@ -26,7 +26,7 @@ import com.google.protobuf.*;
import com.google.protobuf.CodedOutputStream.OutOfSpaceException;
import ghidra.async.*;
import ghidra.comm.util.ByteBufferUtils;
import ghidra.util.ByteBufferUtils;
import ghidra.util.Msg;
public class AsyncProtobufMessageChannel<S extends GeneratedMessageV3, R extends GeneratedMessageV3> {

View File

@ -381,10 +381,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
protected final ForStaticSyncMappingChangeListener mappingChangeListener =
new ForStaticSyncMappingChangeListener();
protected final boolean isMainListing;
public DebuggerListingProvider(DebuggerListingPlugin plugin, FormatManager formatManager,
boolean isConnected) {
super(plugin, formatManager, isConnected);
this.plugin = plugin;
this.isMainListing = isConnected;
goToDialog = new DebuggerGoToDialog(this);
@ -420,6 +423,34 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
setHelpLocation(DebuggerResources.HELP_PROVIDER_LISTING);
}
@Override
public boolean isConnected() {
/*
* NB. Other plugins ask isConnected meaning the main static listing. We don't want to be
* mistaken for it.
*/
return false;
}
/**
* Check if this is the main dynamic listing.
*
* <p>
* The method {@link #isConnected()} is not quite the same as this, although the concepts are a
* little conflated, since before the debugger, no one else presented a listing that could claim
* to be "main" except the "connected" one. Here, we treat "connected" to mean that the address
* is synchronized exactly with the other providers. "Main" on the other hand, does not
* necessarily have that property, but it is still <em>not</em> a snapshot. It is the main
* listing presented by this plugin, and so it has certain unique features. Calling
* {@link DebuggerListingPlugin#getConnectedProvider()} will return the main dynamic listing,
* despite it not really being "connected."
*
* @return true if this is the main listing for the plugin.
*/
public boolean isMainListing() {
return isMainListing;
}
@Override
public String getWindowGroup() {
//TODO: Overriding this to align disconnected providers
@ -428,7 +459,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@Override
public void writeDataState(SaveState saveState) {
if (!isConnected()) {
if (!isMainListing()) {
current.writeDataState(tool, saveState, KEY_DEBUGGER_COORDINATES);
}
super.writeDataState(saveState);
@ -436,7 +467,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@Override
public void readDataState(SaveState saveState) {
if (!isConnected()) {
if (!isMainListing()) {
DebuggerCoordinates coordinates =
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES, true);
coordinatesActivated(coordinates);
@ -466,7 +497,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
actionTrackLocation.setCurrentActionStateByUserData(trackingSpec);
if (isConnected()) {
if (isMainListing()) {
actionSyncToStaticListing.setSelected(syncToStaticListing);
followsCurrentThread = true;
}
@ -549,7 +580,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
this.markerService = markerService;
createNewStaticTrackingMarker();
if (this.markerService != null && !isConnected()) {
if (this.markerService != null && !isMainListing()) {
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
this.markerService.addChangeListener(markerChangeListener);
}
@ -735,7 +766,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
.onAction(this::activatedGoTo)
.buildAndInstallLocal(this);
if (isConnected()) {
if (isMainListing()) {
actionSyncToStaticListing = new SyncToStaticListingAction();
}
else {
@ -999,7 +1030,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
public void setSyncToStaticListing(boolean sync) {
if (!isConnected()) {
if (!isMainListing()) {
throw new IllegalStateException(
"Only the main dynamic listing can be synced to the main static listing");
}
@ -1018,7 +1049,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
public void setFollowsCurrentThread(boolean follows) {
if (isConnected()) {
if (isMainListing()) {
throw new IllegalStateException(
"The main dynamic listing always follows the current trace and thread");
}

View File

@ -1,54 +0,0 @@
/* ###
* 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.comm.util;
import java.nio.ByteBuffer;
/**
* Some utilities for manipulating a {@link ByteBuffer}
*/
public interface ByteBufferUtils {
/**
* Resize a write-mode buffer
*
* This preserves the buffer contents
*
* @param buf the buffer
* @param capacity the new capacity, greater or equal to the buffer's limit
* @return the new buffer
*/
public static ByteBuffer resize(ByteBuffer buf, int capacity) {
if (capacity < buf.limit()) {
throw new IllegalArgumentException("New capacity must fit current contents");
}
buf.flip();
ByteBuffer resized = ByteBuffer.allocate(capacity);
resized.put(buf);
return resized;
}
/**
* Resize a write-mode buffer to twice its current capacity
*
* This preserves the buffer contents
*
* @param buf the buffer
* @return the new buffer
*/
public static ByteBuffer upsize(ByteBuffer buf) {
return resize(buf, buf.capacity() * 2);
}
}

View File

@ -34,8 +34,12 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.space.DBTraceSpaceKey;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.Trace.TraceCommentChangeType;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@ -49,7 +53,7 @@ public class DBTraceCommentAdapter
protected static final int MAX_COMMENT_TYPE = CodeUnit.REPEATABLE_COMMENT;
@DBAnnotatedObjectInfo(version = 0)
protected static class DBTraceCommentEntry
public static class DBTraceCommentEntry
extends AbstractDBTraceAddressSnapRangePropertyMapData<DBTraceCommentEntry> {
static final String TYPE_COLUMN_NAME = "Type";
static final String COMMENT_COLUMN_NAME = "Comment";
@ -89,6 +93,10 @@ public class DBTraceCommentAdapter
void setLifespan(Range<Long> lifespan) {
super.doSetLifespan(lifespan);
}
public int getType() {
return type;
}
}
public DBTraceCommentAdapter(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
@ -106,10 +114,15 @@ public class DBTraceCommentAdapter
if (commentType < MIN_COMMENT_TYPE || commentType > MAX_COMMENT_TYPE) {
throw new IllegalArgumentException("commentType");
}
String oldValue = null;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (DBTraceCommentEntry entry : reduce(TraceAddressSnapRangeQuery.intersecting(
new AddressRangeImpl(address, address), lifespan)).values()) {
if (entry.type == commentType) {
if (lifespan.hasLowerBound() &&
entry.getLifespan().contains(lifespan.lowerEndpoint())) {
oldValue = entry.comment;
}
makeWay(entry, lifespan);
}
}
@ -118,6 +131,11 @@ public class DBTraceCommentAdapter
entry.set((byte) commentType, comment);
}
}
trace.setChanged(new TraceChangeRecord<TraceAddressSnapRange, String>(
TraceCommentChangeType.byType(commentType),
DBTraceSpaceKey.create(address.getAddressSpace(), null, 0),
new ImmutableTraceAddressSnapRange(address, lifespan),
oldValue, comment));
}
public static String commentFromArray(String[] comment) {

View File

@ -15,8 +15,6 @@
*/
package ghidra.trace.database.memory;
import static ghidra.lifecycle.Unfinished.TODO;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -47,8 +45,8 @@ import ghidra.trace.model.Trace.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceViewportSpanIterator;
import ghidra.util.LockHold;
import ghidra.util.MathUtilities;
import ghidra.util.*;
import ghidra.util.AddressIteratorAdapter;
import ghidra.util.database.*;
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
import ghidra.util.exception.DuplicateNameException;
@ -758,10 +756,59 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
return len;
}
protected Address doFindBytesInRange(long snap, AddressRange range, ByteBuffer data,
ByteBuffer mask, boolean forward, TaskMonitor monitor) {
int len = data.capacity();
AddressRange rangeOfStarts =
new AddressRangeImpl(range.getMinAddress(), range.getMaxAddress().subtract(len - 1));
ByteBuffer read = ByteBuffer.allocate(len);
for (Address addr : AddressIteratorAdapter.forRange(rangeOfStarts, forward)) {
monitor.incrementProgress(1);
if (monitor.isCancelled()) {
return null;
}
read.clear();
int l = getBytes(snap, addr, read);
if (l != len) {
continue;
}
if (!ByteBufferUtils.maskedEquals(mask, data, read)) {
continue;
}
return addr;
}
return null;
}
@Override
public Address findBytes(long snap, AddressRange range, ByteBuffer data, ByteBuffer mask,
boolean forward, TaskMonitor monitor) {
return TODO();
// ProgramDB uses the naive method with some skipping, so here we go....
// TODO: This could be made faster by skipping over non-initialized blocks
// TODO: DFA method would be complicated by masks....
int len = data.capacity();
if (mask != null && mask.capacity() != len) {
throw new IllegalArgumentException("data and mask must have same capacity");
}
if (len == 0 || range.getLength() > 0 && range.getLength() < len) {
return null;
}
// TODO: Could do better, but have to worry about viewport, too
// This will reduce the search to ranges that have been written at any snap
// We could do for this and previous snaps, but that's where the viewport comes in.
// TODO: Potentially costly to pre-compute the set concretely
AddressSet known = new AddressSet(
stateMapSpace.getAddressSetView(Range.all(), s -> s == TraceMemoryState.KNOWN))
.intersect(new AddressSet(range));
monitor.initialize(known.getNumAddresses());
for (AddressRange knownRange : known.getAddressRanges(forward)) {
Address found = doFindBytesInRange(snap, knownRange, data, mask, forward, monitor);
if (found != null) {
return found;
}
}
return null;
}
// TODO: Test this

View File

@ -424,8 +424,10 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
@Override
public AddressIterator getCommentAddressIterator(int commentType, AddressSetView addrSet,
boolean forward) {
// TODO Auto-generated method stub
return null;
return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses(
s -> program.trace.getCommentAdapter()
.getAddressSetView(Range.singleton(s), e -> e.getType() == commentType)))
.getAddresses(forward);
}
@Override

View File

@ -240,8 +240,10 @@ public abstract class AbstractDBTraceProgramViewMemory
ByteBuffer bufBytes = ByteBuffer.wrap(bytes);
ByteBuffer bufMasks = masks == null ? null : ByteBuffer.wrap(masks);
Address minAddr = forward ? startAddr : endAddr;
Address maxAddr = forward ? endAddr : startAddr;
Iterator<AddressRange> it =
program.getAddressFactory().getAddressSet(startAddr, endAddr).iterator(forward);
program.getAddressFactory().getAddressSet(minAddr, maxAddr).iterator(forward);
while (it.hasNext()) {
AddressRange range = it.next();
DBTraceMemorySpace space = memoryManager.getMemorySpace(range.getAddressSpace(), false);

View File

@ -110,6 +110,12 @@ public class DBTraceProgramView implements TraceProgramView {
listenFor(TraceCodeChangeType.FRAGMENT_CHANGED, this::codeFragmentChanged);
listenFor(TraceCodeChangeType.DATA_TYPE_REPLACED, this::codeDataTypeReplaced);
listenFor(TraceCommentChangeType.EOL_CHANGED, this::commentEolChanged);
listenFor(TraceCommentChangeType.PLATE_CHANGED, this::commentPlateChanged);
listenFor(TraceCommentChangeType.POST_CHANGED, this::commentPostChanged);
listenFor(TraceCommentChangeType.PRE_CHANGED, this::commentPreChanged);
listenFor(TraceCommentChangeType.REPEATABLE_CHANGED, this::commentRepeatableChanged);
listenFor(TraceCompositeDataChangeType.ADDED, this::compositeDataAdded);
listenFor(TraceCompositeDataChangeType.LIFESPAN_CHANGED,
this::compositeLifespanChanged);
@ -328,6 +334,48 @@ public class DBTraceProgramView implements TraceProgramView {
range.getX1(), range.getX2(), null, null, null));
}
private void commentChanged(int docrType, TraceAddressSpace space,
TraceAddressSnapRange range,
String oldValue, String newValue) {
DomainObjectEventQueues queues = isVisible(space, range);
if (queues == null) {
return;
}
queues.fireEvent(new ProgramChangeRecord(docrType,
range.getX1(), range.getX2(), null, oldValue, newValue));
}
private void commentEolChanged(TraceAddressSpace space, TraceAddressSnapRange range,
String oldValue, String newValue) {
commentChanged(ChangeManager.DOCR_EOL_COMMENT_CHANGED, space, range, oldValue,
newValue);
}
private void commentPlateChanged(TraceAddressSpace space, TraceAddressSnapRange range,
String oldValue, String newValue) {
commentChanged(ChangeManager.DOCR_PLATE_COMMENT_CHANGED, space, range, oldValue,
newValue);
}
private void commentPostChanged(TraceAddressSpace space, TraceAddressSnapRange range,
String oldValue, String newValue) {
commentChanged(ChangeManager.DOCR_POST_COMMENT_CHANGED, space, range, oldValue,
newValue);
}
private void commentPreChanged(TraceAddressSpace space, TraceAddressSnapRange range,
String oldValue, String newValue) {
commentChanged(ChangeManager.DOCR_PRE_COMMENT_CHANGED, space, range, oldValue,
newValue);
}
private void commentRepeatableChanged(TraceAddressSpace space, TraceAddressSnapRange range,
String oldValue, String newValue) {
// TODO: The "repeatable" semantics are not implemented, yet.
commentChanged(ChangeManager.DOCR_REPEATABLE_COMMENT_CHANGED, space, range, oldValue,
newValue);
}
private void compositeDataAdded(TraceAddressSpace space, TraceAddressSnapRange range,
TraceData oldIsNull, TraceData added) {
DomainObjectEventQueues queues = isCodeVisible(space, added);

View File

@ -15,6 +15,8 @@
*/
package ghidra.trace.model;
import java.util.*;
import javax.swing.ImageIcon;
import com.google.common.collect.Range;
@ -106,6 +108,33 @@ public interface Trace extends DataTypeManagerDomainObject {
new TraceCodeChangeType<>();
}
public static final class TraceCommentChangeType
extends DefaultTraceChangeType<TraceAddressSnapRange, String> {
private static final Map<Integer, TraceCommentChangeType> BY_TYPE = new HashMap<>();
public static final TraceCommentChangeType PLATE_CHANGED =
new TraceCommentChangeType(CodeUnit.PLATE_COMMENT);
public static final TraceCommentChangeType PRE_CHANGED =
new TraceCommentChangeType(CodeUnit.PRE_COMMENT);
public static final TraceCommentChangeType POST_CHANGED =
new TraceCommentChangeType(CodeUnit.POST_COMMENT);
public static final TraceCommentChangeType EOL_CHANGED =
new TraceCommentChangeType(CodeUnit.EOL_COMMENT);
public static final TraceCommentChangeType REPEATABLE_CHANGED =
new TraceCommentChangeType(CodeUnit.REPEATABLE_COMMENT);
public static TraceCommentChangeType byType(int type) {
return Objects.requireNonNull(BY_TYPE.get(type));
}
public final int type;
private TraceCommentChangeType(int type) {
this.type = type;
BY_TYPE.put(type, this);
}
}
public static final class TraceCompositeDataChangeType<T, U>
extends DefaultTraceChangeType<T, U> {
public static final TraceCompositeDataChangeType<TraceAddressSnapRange, TraceData> ADDED =

View File

@ -418,7 +418,7 @@ public interface TraceMemoryOperations {
* @param snap the time to search
* @param range the address range to search
* @param data the values to search for
* @param mask a mask on the bits of {@code data}
* @param mask a mask on the bits of {@code data}; or null to match all bytes exactly
* @param forward true to return the match with the lowest address in {@code range}, false for
* the highest address.
* @param monitor a monitor for progress reporting and canceling

View File

@ -42,6 +42,7 @@ import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.util.database.*;
import ghidra.util.task.ConsoleTaskMonitor;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractDBTraceMemoryManagerTest
extends AbstractGhidraHeadlessIntegrationTest {
@ -715,6 +716,66 @@ public abstract class AbstractDBTraceMemoryManagerTest
assertArrayEquals(arr(1, 2, 3, 4), read.array());
}
@Test
public void testFindBytes() {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
assertEquals(4, memory.putBytes(3, addr(0x4000), buf(1, 2, 3, 4)));
}
try {
memory.findBytes(3, range(0x4000, 0x4003), buf(1, 2, 3, 4), buf(-1, -1, -1),
true, TaskMonitor.DUMMY);
}
catch (IllegalArgumentException e) {
// pass
}
// Degenerate
assertNull(
memory.findBytes(2, range(0x4000, 0x4003), buf(), buf(),
true, TaskMonitor.DUMMY));
// Too soon
assertNull(
memory.findBytes(2, range(0x4000, 0x4003), buf(1, 2, 3, 4), buf(-1, -1, -1, -1),
true, TaskMonitor.DUMMY));
// Too small
assertNull(
memory.findBytes(3, range(0x4000, 0x4002), buf(1, 2, 3, 4), buf(-1, -1, -1, -1),
true, TaskMonitor.DUMMY));
// Too high
assertNull(
memory.findBytes(3, range(0x4001, 0x4004), buf(1, 2, 3, 4), buf(-1, -1, -1, -1),
true, TaskMonitor.DUMMY));
// Too low
assertNull(
memory.findBytes(3, range(0x3fff, 0x4002), buf(1, 2, 3, 4), buf(-1, -1, -1, -1),
true, TaskMonitor.DUMMY));
// Perfect match
assertEquals(addr(0x4000),
memory.findBytes(3, range(0x4000, 0x4003), buf(1, 2, 3, 4), buf(-1, -1, -1, -1),
true, TaskMonitor.DUMMY));
// Make it work for the match
assertEquals(addr(0x4000),
memory.findBytes(3, range(0x0, -1), buf(1, 2, 3, 4), buf(-1, -1, -1, -1),
true, TaskMonitor.DUMMY));
// Make it work for the match
assertEquals(addr(0x4000),
memory.findBytes(3, range(0x0, -1), buf(1), buf(-1),
true, TaskMonitor.DUMMY));
// Sub match
assertEquals(addr(0x4001),
memory.findBytes(3, range(0x4000, 0x4003), buf(2, 3, 4), buf(-1, -1, -1),
true, TaskMonitor.DUMMY));
}
@Test
public void testRemoveBytes() {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {

View File

@ -89,6 +89,11 @@ public class AddressIteratorAdapter extends NestedIterator<AddressRange, Address
}
}
public static Iterable<Address> forRange(AddressRange range, boolean forward) {
return () -> forward ? new ForwardAddressIterator(range)
: new BackwardAddressIterator(range);
}
public AddressIteratorAdapter(Iterator<AddressRange> outer, boolean forward) {
super(outer, forward ? ForwardAddressIterator::new : BackwardAddressIterator::new);
}

View File

@ -62,8 +62,7 @@ import ghidra.util.task.*;
import resources.ResourceManager;
/**
* Plugin to search text as it is displayed in the fields of the
* Code Browser.
* Plugin to search text as it is displayed in the fields of the Code Browser.
*/
//@formatter:off
@PluginInfo(
@ -114,6 +113,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
/**
* The constructor for the SearchTextPlugin.
*
* @param plugintool The tool required by this plugin.
*/
public SearchTextPlugin(PluginTool plugintool) {
@ -260,10 +260,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
}
private ProgramLocation getStartLocation() {
if (currentLocation == null) {
currentLocation = navigatable.getLocation();
}
return currentLocation;
return currentLocation = navigatable.getLocation();
}
private void searchNext(Program program, Navigatable searchNavigatable, Searcher textSearcher) {
@ -475,9 +472,8 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
searchDialog.setHasSelection(context.hasSelection());
}
CodeViewerService codeViewerService = tool.getService(CodeViewerService.class);
String textSelection = navigatable.getTextSelection();
ProgramLocation location = codeViewerService.getCurrentLocation();
ProgramLocation location = navigatable.getLocation();
Address address = location.getAddress();
Listing listing = context.getProgram().getListing();
CodeUnit codeUnit = listing.getCodeUnitAt(address);
@ -499,6 +495,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
/**
* Get the address set for the selection.
*
* @return null if there is no selection
*/
private AddressSetView getAddressSet(Navigatable searchNavigatable, SearchOptions options) {

View File

@ -75,7 +75,7 @@ public class InstructionMnemonicOperandFieldSearcher extends ProgramDatabaseFiel
@Override
protected Address advance(List<ProgramLocation> currentMatches) {
Instruction instruction = iterator.next();
Instruction instruction = iterator.hasNext() ? iterator.next() : null;
Address nextAddress = null;
if (instruction != null) {
nextAddress = instruction.getMinAddress();

View File

@ -84,7 +84,7 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
this.tableServicePlugin = plugin;
this.navigatable = navigatable;
this.program = plugin.getProgram();
this.program = navigatable.getProgram();
this.model = model;
this.programName = programName;
this.markerService = markerService;
@ -152,7 +152,6 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
selectAction = new MakeProgramSelectionAction(tableServicePlugin, table) {
@Override
protected ProgramSelection makeSelection(ActionContext context) {
ProgramSelection selection = table.getProgramSelection();
navigatable.goTo(program, new ProgramLocation(program, selection.getMinAddress()));
navigatable.setSelection(selection);