GP-0: Fix javodocs. Fix tests. Fix streamSub.

This commit is contained in:
Dan 2024-03-04 09:43:29 -05:00
parent c2bb47d45a
commit ddea132049
20 changed files with 262 additions and 34 deletions

View File

@ -22,7 +22,6 @@ import db.Transaction;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathPattern;
import ghidra.program.model.address.*;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;

View File

@ -98,7 +98,7 @@ class TraceBreakpointSet {
/**
* Get the trace
*
* @return
* @return the trace
*/
public Trace getTrace() {
return trace;
@ -237,7 +237,7 @@ class TraceBreakpointSet {
* The caller should first call {@link #canMerge(TraceBreakpoint)} to check if the breakpoint
* "fits."
*
* @param bpt
* @param bpt the breakpoint
* @return true if the set actually changed as a result
*/
public boolean add(TraceBreakpoint bpt) {

View File

@ -408,7 +408,7 @@ public class DBTraceBreakpoint
@Override
public boolean isEnabled(long snap) {
// NB. Only object mode support per-snap enablement
// NB. Only object mode supports per-snap enablement
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return enabled;
}
@ -491,4 +491,11 @@ public class DBTraceBreakpoint
public void delete() {
space.deleteBreakpoint(this);
}
@Override
public boolean isValid(long snap) {
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return lifespan.contains(snap);
}
}
}

View File

@ -369,6 +369,11 @@ public class DBTraceObjectBreakpointLocation
}
}
@Override
public boolean isValid(long snap) {
return object.getCanonicalParent(snap) != null;
}
@Override
public TraceObject getObject() {
return object;

View File

@ -223,6 +223,11 @@ public class DBTraceObjectBreakpointSpec
}
}
@Override
public boolean isValid(long snap) {
return object.getCanonicalParent(snap) != null;
}
@Override
public TraceObject getObject() {
return object;

View File

@ -307,4 +307,11 @@ public class DBTraceMemoryRegion
public void delete() {
space.deleteRegion(this);
}
@Override
public boolean isValid(long snap) {
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return lifespan.contains(snap);
}
}
}

View File

@ -386,6 +386,11 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
}
}
@Override
public boolean isValid(long snap) {
return object.getCanonicalParent(snap) != null;
}
@Override
public TraceObject getObject() {
return object;

View File

@ -16,6 +16,7 @@
package ghidra.trace.database.target;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import java.util.stream.Stream;
@ -210,11 +211,15 @@ class DBTraceObjectValueWriteBehindCache {
private Stream<DBTraceObjectValueBehind> streamSub(
NavigableMap<Long, DBTraceObjectValueBehind> map, Lifespan span, boolean forward) {
Long floor = map.floorKey(span.min());
if (floor == null) {
floor = span.min();
long min;
Entry<Long, DBTraceObjectValueBehind> floor = map.floorEntry(span.min());
if (floor != null && floor.getValue().getLifespan().contains(span.min())) {
min = floor.getKey();
}
var sub = map.subMap(floor, true, span.max(), true);
else {
min = span.min();
}
var sub = map.subMap(min, true, span.max(), true);
if (!forward) {
sub = sub.descendingMap();
}

View File

@ -175,6 +175,11 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
}
}
@Override
public boolean isValid(long snap) {
return object.getCanonicalParent(snap) != null;
}
@Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
return translator.translate(rec);

View File

@ -184,4 +184,11 @@ public class DBTraceThread extends DBAnnotatedObject implements TraceThread {
public void delete() {
manager.deleteThread(this);
}
@Override
public boolean isValid(long snap) {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return lifespan.contains(snap);
}
}
}

View File

@ -18,6 +18,8 @@ package ghidra.trace.model.breakpoint;
import java.util.Collection;
import java.util.Set;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.*;
@ -51,6 +53,8 @@ public interface TraceBreakpoint extends TraceUniqueObject {
*
* <p>
* This should be a name suitable for display on the screen
*
* @param name the new name
*/
void setName(String name);
@ -75,12 +79,18 @@ public interface TraceBreakpoint extends TraceUniqueObject {
AddressRange getRange();
/**
* Get the minimum address in this breakpoint's range
*
* @see #getRange()
* @return the minimum address
*/
Address getMinAddress();
/**
* Get the maximum address in this breakpoint's range
*
* @see #getRange()
* @return the maximum address
*/
Address getMaxAddress();
@ -109,6 +119,7 @@ public interface TraceBreakpoint extends TraceUniqueObject {
* Set the cleared snap of this breakpoint
*
* @param clearedSnap the cleared snap, or {@link Long#MAX_VALUE} for "to the end of time"
* @throws DuplicateNameException if extending the lifespan would cause a naming collision
*/
void setClearedSnap(long clearedSnap) throws DuplicateNameException;
@ -236,21 +247,27 @@ public interface TraceBreakpoint extends TraceUniqueObject {
* Set Sleigh source to replace the breakpointed instruction in emulation
*
* <p>
* The default is simply "<code>{@link PcodeEmulationLibrary#emu_swi() emu_swi()};
* {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};</code>", effectively a
* non-conditional breakpoint followed by execution of the actual instruction. Modifying this
* allows clients to create conditional breakpoints or simply override or inject additional
* logic into an emulated target.
* The default is simply:
* </p>
*
* <pre>
* {@link PcodeEmulationLibrary#emu_swi() emu_swi()};
* {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};
* </pre>
* <p>
* That is effectively a non-conditional breakpoint followed by execution of the actual
* instruction. Modifying this allows clients to create conditional breakpoints or simply
* override or inject additional logic into an emulated target.
*
* <p>
* <b>NOTE:</b> This current has no effect on access breakpoints, but only execution
* <b>NOTE:</b> This currently has no effect on access breakpoints, but only execution
* breakpoints.
*
* <p>
* If the specified source fails to compile during emulator set-up, this will fall back to
* {@link PcodeEmulationLibrary#emu_err
* {@link PcodeEmulationLibrary#emu_swi()}
*
* @see #DEFAULT_SLEIGH
* @see SleighUtils#UNCONDITIONAL_BREAK
* @param sleigh the Sleigh source
*/
void setEmuSleigh(String sleigh);
@ -266,4 +283,18 @@ public interface TraceBreakpoint extends TraceUniqueObject {
* Delete this breakpoint from the trace
*/
void delete();
/**
* Check if the breakpoint is valid at the given snapshot
*
* <p>
* In object mode, a breakpoint's life may be disjoint, so checking if the snap occurs between
* creation and destruction is not quite sufficient. This method encapsulates validity. In
* object mode, it checks that the breakpoint object has a canonical parent at the given
* snapshot. In table mode, it checks that the lifespan contains the snap.
*
* @param snap the snapshot key
* @return true if valid, false if not
*/
boolean isValid(long snap);
}

View File

@ -89,6 +89,9 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
*
* @param creationSnap the creation snap, or {@link Long#MIN_VALUE} for "since the beginning of
* time"
* @throws DuplicateNameException if extending the region would cause a naming conflict
* @throws TraceOverlappedRegionException if extending the region would cause it to overlap
* another
*/
void setCreationSnap(long creationSnap)
throws DuplicateNameException, TraceOverlappedRegionException;
@ -105,6 +108,9 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
*
* @param destructionSnap the destruction snap, or {@link Long#MAX_VALUE} for "to the end of
* time"
* @throws DuplicateNameException if extending the region would cause a naming conflict
* @throws TraceOverlappedRegionException if extending the region would cause it to overlap
* another
*/
void setDestructionSnap(long destructionSnap)
throws DuplicateNameException, TraceOverlappedRegionException;
@ -138,22 +144,38 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
AddressRange getRange();
/**
* Set the minimum address of the range
*
* @see #setRange(AddressRange)
* @param min the new minimum
* @throws TraceOverlappedRegionException if extending the region would cause it to overlap
* another
*/
void setMinAddress(Address min) throws TraceOverlappedRegionException;
/**
* Get the minimum address of the range
*
* @see #getRange()
* @return the minimum address
*/
Address getMinAddress();
/**
* Set the maximum address of the range
*
* @see #setRange(AddressRange)
* @param max the new minimum
* @throws TraceOverlappedRegionException if extending the region would cause it to overlap
* another
*/
void setMaxAddress(Address max) throws TraceOverlappedRegionException;
/**
* Get the maximum address of the range
*
* @see #getRange()
* @return the maximum address
*/
Address getMaxAddress();
@ -164,6 +186,11 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
* This adjusts the max address of the range so that its length becomes that given
*
* @see #setRange(AddressRange)
* @param length the desired length of the range
* @throws AddressOverflowException if extending the range would cause the max address to
* overflow
* @throws TraceOverlappedRegionException if extending the region would cause it to overlap
* another
*/
void setLength(long length) throws AddressOverflowException, TraceOverlappedRegionException;
@ -182,7 +209,9 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
void setFlags(Collection<TraceMemoryFlag> flags);
/**
* @see #setFlags(Collection)
* Set the flags, e.g., permissions, of this region
*
* @param flags the flags
*/
default void setFlags(TraceMemoryFlag... flags) {
setFlags(Arrays.asList(flags));
@ -191,12 +220,14 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
/**
* Add the given flags, e.g., permissions, to this region
*
* @see #setFlags(Collection)
* @param flags the flags
*/
void addFlags(Collection<TraceMemoryFlag> flags);
/**
* @see #addFlags(Collection)
* Add the given flags, e.g., permissions, to this region
*
* @param flags the flags
*/
default void addFlags(TraceMemoryFlag... flags) {
addFlags(Arrays.asList(flags));
@ -205,12 +236,14 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
/**
* Remove the given flags, e.g., permissions, from this region
*
* @see #setFlags(Collection)
* @param flags the flags
*/
void clearFlags(Collection<TraceMemoryFlag> flags);
/**
* @see #clearFlags(Collection)
* Remove the given flags, e.g., permissions, from this region
*
* @param flags the flags
*/
default void clearFlags(TraceMemoryFlag... flags) {
clearFlags(Arrays.asList(flags));
@ -249,7 +282,7 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
/**
* Add or clear the {@link TraceMemoryFlag#WRITE} flag
*
* @param read true to add, false to clear
* @param write true to add, false to clear
*/
default void setWrite(boolean write) {
if (write) {
@ -272,7 +305,7 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
/**
* Add or clear the {@link TraceMemoryFlag#EXECUTE} flag
*
* @param read true to add, false to clear
* @param execute true to add, false to clear
*/
default void setExecute(boolean execute) {
if (execute) {
@ -295,7 +328,7 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
/**
* Add or clear the {@link TraceMemoryFlag#VOLATILE} flag
*
* @param read true to add, false to clear
* @param vol true to add, false to clear
*/
default void setVolatile(boolean vol) {
if (vol) {
@ -319,4 +352,18 @@ public interface TraceMemoryRegion extends TraceUniqueObject {
* Delete this region from the trace
*/
void delete();
/**
* Check if the region is valid at the given snapshot
*
* <p>
* In object mode, a region's life may be disjoint, so checking if the snap occurs between
* creation and destruction is not quite sufficient. This method encapsulates validity. In
* object mode, it checks that the region object has a canonical parent at the given snapshot.
* In table mode, it checks that the lifespan contains the snap.
*
* @param snap the snapshot key
* @return true if valid, false if not
*/
boolean isValid(long snap);
}

View File

@ -66,6 +66,7 @@ public interface TraceThread extends TraceUniqueObject {
*
* @param creationSnap the creation snap, or {@link Long#MIN_VALUE} for "since the beginning of
* time"
* @throws DuplicateNameException if extending the thread's life would cause a naming conflict
*/
void setCreationSnap(long creationSnap) throws DuplicateNameException;
@ -81,6 +82,7 @@ public interface TraceThread extends TraceUniqueObject {
*
* @param destructionSnap the destruction snap, or {@link Long#MAX_VALUE} for "to the end of
* time"
* @throws DuplicateNameException if extending the thread's life would cause a naming conflict
*/
void setDestructionSnap(long destructionSnap) throws DuplicateNameException;
@ -144,4 +146,18 @@ public interface TraceThread extends TraceUniqueObject {
* Delete this thread from the trace
*/
void delete();
/**
* Check if the thread is valid at the given snapshot
*
* <p>
* In object mode, a thread's life may be disjoint, so checking if the snap occurs between
* creation and destruction is not quite sufficient. This method encapsulates validity. In
* object mode, it checks that the thread object has a canonical parent at the given snapshot.
* In table mode, it checks that the lifespan contains the snap.
*
* @param snap the snapshot key
* @return true if valid, false if not
*/
boolean isValid(long snap);
}

View File

@ -20,6 +20,8 @@ import static org.junit.Assert.*;
import java.util.List;
import java.util.Set;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.*;
import db.Transaction;
@ -257,14 +259,38 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat
assertEquals("WinMain", breakMain.getComment());
}
protected static class InvalidBreakpointMatcher extends BaseMatcher<TraceBreakpoint> {
private final long snap;
public InvalidBreakpointMatcher(long snap) {
this.snap = snap;
}
@Override
public boolean matches(Object actual) {
return actual == null || actual instanceof TraceBreakpoint bpt && !bpt.isValid(snap);
}
@Override
public void describeTo(Description description) {
description.appendText("An invalid or null breakpoint");
}
}
protected static InvalidBreakpointMatcher invalidBreakpoint(long snap) {
return new InvalidBreakpointMatcher(snap);
}
@Test
public void testDelete() throws Exception {
addBreakpoints();
assertEquals(breakMain, breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"));
try (Transaction tx = b.startTransaction()) {
breakMain.delete();
assertNull(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"));
assertThat(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"),
invalidBreakpoint(0));
}
assertNull(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"));
assertThat(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"),
invalidBreakpoint(0));
}
}

View File

@ -15,12 +15,13 @@
*/
package ghidra.trace.database.memory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Set;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.*;
import db.Transaction;
@ -79,6 +80,29 @@ public abstract class AbstractDBTraceMemoryManagerRegionsTest
assertEquals(Set.of(region), Set.copyOf(memory.getAllRegions()));
}
protected static class InvalidRegionMatcher extends BaseMatcher<TraceMemoryRegion> {
private final long snap;
public InvalidRegionMatcher(long snap) {
this.snap = snap;
}
@Override
public boolean matches(Object actual) {
return actual == null ||
actual instanceof TraceMemoryRegion region && !region.isValid(snap);
}
@Override
public void describeTo(Description description) {
description.appendText("An invalid or null region");
}
}
protected static InvalidRegionMatcher invalidRegion(long snap) {
return new InvalidRegionMatcher(snap);
}
@Test
public void testGetLiveRegionByPath() throws Exception {
assertNull(memory.getLiveRegionByPath(0, "Regions[0x1000]"));
@ -90,8 +114,8 @@ public abstract class AbstractDBTraceMemoryManagerRegionsTest
}
assertEquals(region, memory.getLiveRegionByPath(0, "Regions[0x1000]"));
assertNull(memory.getLiveRegionByPath(0, "Regions[0x1001]"));
assertNull(memory.getLiveRegionByPath(-1, "Regions[0x1000]"));
assertThat(memory.getLiveRegionByPath(0, "Regions[0x1001]"), invalidRegion(0));
assertThat(memory.getLiveRegionByPath(-1, "Regions[0x1000]"), invalidRegion(-1));
}
@Test

View File

@ -19,6 +19,8 @@ import static org.junit.Assert.*;
import java.util.Set;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.*;
import db.Transaction;
@ -86,6 +88,28 @@ public class DBTraceThreadManagerTest extends AbstractGhidraHeadlessIntegrationT
assertEquals(Set.of(thread2), Set.copyOf(threadManager.getThreadsByPath("Threads[2]")));
}
protected static class InvalidThreadMatcher extends BaseMatcher<TraceThread> {
private final long snap;
public InvalidThreadMatcher(long snap) {
this.snap = snap;
}
@Override
public boolean matches(Object actual) {
return actual == null || actual instanceof TraceThread thread && !thread.isValid(snap);
}
@Override
public void describeTo(Description description) {
description.appendText("An invalid or null thread");
}
}
protected static InvalidThreadMatcher invalidThread(long snap) {
return new InvalidThreadMatcher(snap);
}
@Test
public void testLiveThreadByPath() throws Exception {
assertNull(threadManager.getLiveThreadByPath(0, "Threads[1]"));
@ -95,8 +119,8 @@ public class DBTraceThreadManagerTest extends AbstractGhidraHeadlessIntegrationT
assertEquals(thread2, threadManager.getLiveThreadByPath(0, "Threads[2]"));
assertEquals(thread2, threadManager.getLiveThreadByPath(10, "Threads[2]"));
assertNull(threadManager.getLiveThreadByPath(0, "Threads[3]"));
assertNull(threadManager.getLiveThreadByPath(-1, "Threads[2]"));
assertNull(threadManager.getLiveThreadByPath(11, "Threads[2]"));
assertThat(threadManager.getLiveThreadByPath(-1, "Threads[2]"), invalidThread(-1));
assertThat(threadManager.getLiveThreadByPath(11, "Threads[2]"), invalidThread(11));
}
@Test

View File

@ -38,6 +38,8 @@ import ghidra.util.classfinder.ClassSearcher;
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}. For an alternative
* implementation incorporating an abstract piece, see the Taint Analyzer.
*
* @param <T> the type of objects in the machine's state
*/
public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {

View File

@ -198,7 +198,12 @@ public interface PcodeMachine<T> {
/**
* Set the suspension state of the machine
*
* <p>
* This does not simply suspend all threads, but sets a machine-wide flag. A thread is suspended
* if either the thread's flag is set, or the machine's flag is set.
*
* @see PcodeThread#setSuspended(boolean)
* @param suspended true to suspend the machine, false to let it run
*/
void setSuspended(boolean suspended);
@ -206,6 +211,7 @@ public interface PcodeMachine<T> {
* Check the suspension state of the machine
*
* @see PcodeThread#isSuspended()
* @return true if suspended
*/
boolean isSuspended();

View File

@ -15,8 +15,6 @@
*/
package ghidra.pcode.emu;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.exec.*;
@ -289,6 +287,9 @@ public interface PcodeThread<T> {
* reliable way to halt execution. Note the emulator may halt mid instruction. If this is not
* desired, then upon catching the exception, un-suspend the p-code thread and call
* {@link #finishInstruction()} or {@link #dropInstruction()}.
*
* @see PcodeMachine#setSuspended(boolean)
* @param suspended true to suspend the machine, false to let it run
*/
void setSuspended(boolean suspended);
@ -341,6 +342,7 @@ public interface PcodeThread<T> {
* The memory part of this state is shared among all threads in the same machine. See
* {@link PcodeMachine#getSharedState()}.
*
* @return the state
*/
ThreadPcodeExecutorState<T> getState();

View File

@ -40,6 +40,11 @@ public enum SleighUtils {
/**
* A Sleigh parsing error
*
* @param header the header / title for the message
* @param message the detail message
* @param start the character position where the syntax error starts
* @param stop the character position where the syntax error ends
*/
public record SleighParseErrorEntry(String header, String message, int start, int stop) {
public String fullMessage() {