diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java index 6c17dfcf21..932c0d2a2f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java index c8651b9a17..5699a01209 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java @@ -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) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java index f19f904aea..184434e219 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java @@ -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); + } + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java index 1200d261a1..ef19929a83 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java @@ -369,6 +369,11 @@ public class DBTraceObjectBreakpointLocation } } + @Override + public boolean isValid(long snap) { + return object.getCanonicalParent(snap) != null; + } + @Override public TraceObject getObject() { return object; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java index 2f15203814..570d5b5fd8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java @@ -223,6 +223,11 @@ public class DBTraceObjectBreakpointSpec } } + @Override + public boolean isValid(long snap) { + return object.getCanonicalParent(snap) != null; + } + @Override public TraceObject getObject() { return object; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java index 57108d4dad..4927dc49c9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java @@ -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); + } + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java index 49da0b8f79..814e874383 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java @@ -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; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueWriteBehindCache.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueWriteBehindCache.java index 0f0b3cd537..421da8b434 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueWriteBehindCache.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueWriteBehindCache.java @@ -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 streamSub( NavigableMap map, Lifespan span, boolean forward) { - Long floor = map.floorKey(span.min()); - if (floor == null) { - floor = span.min(); + long min; + Entry 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(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java index 910891afeb..b035b92939 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java @@ -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); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThread.java index 4a31a438f5..24fb33238a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThread.java @@ -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); + } + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java index 320cf37b48..13949f3af1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java @@ -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 { * *

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

- * The default is simply "{@link PcodeEmulationLibrary#emu_swi() emu_swi()}; - * {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};", 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: + *

+ * + *
+	 * {@link PcodeEmulationLibrary#emu_swi() emu_swi()};
+	 * {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};
+	 * 
+ *

+ * 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. * *

- * NOTE: This current has no effect on access breakpoints, but only execution + * NOTE: This currently has no effect on access breakpoints, but only execution * breakpoints. * *

* 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 + * + *

+ * 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); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java index 339074d7c5..4079445e72 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java @@ -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 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 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 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 + * + *

+ * 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); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java index 66ce88cdfb..e95c31574a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java @@ -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 + * + *

+ * 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); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java index 6028127218..95b3e1dcc4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java @@ -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 { + 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)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java index 4534d56479..6df25f222f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java @@ -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 { + 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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java index 083465a82d..d0f74c34f0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java @@ -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 { + 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 diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java index 42b9e37519..72e2a08498 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java @@ -38,6 +38,8 @@ import ghidra.util.classfinder.ClassSearcher; *

* 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 the type of objects in the machine's state */ public abstract class AbstractPcodeMachine implements PcodeMachine { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java index 85f18461af..d5dc7d5595 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java @@ -198,7 +198,12 @@ public interface PcodeMachine { /** * Set the suspension state of the machine * + *

+ * 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 { * Check the suspension state of the machine * * @see PcodeThread#isSuspended() + * @return true if suspended */ boolean isSuspended(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java index a86216224a..e5c0deb26f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java @@ -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 { * 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 { * The memory part of this state is shared among all threads in the same machine. See * {@link PcodeMachine#getSharedState()}. * + * @return the state */ ThreadPcodeExecutorState getState(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java index aa8dbe11bc..f72060b79d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java @@ -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() {