diff --git a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java index fdde6da5c0..407f249823 100644 --- a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java +++ b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java @@ -267,8 +267,8 @@ public class GadpClientServerTest { return processes; } - public void addLinks() { - assertNotNull(available.fetchElements().getNow(null)); + public void addLinks() throws Throwable { + waitOn(available.fetchElements()); links.setElements(List.of(), Map.of( "1", available.getCachedElements().get("2"), "2", available.getCachedElements().get("1")), @@ -309,7 +309,7 @@ public class GadpClientServerTest { } Object ret = method.testInvoke(invocation.getValue()); changeAttributes(List.of(), Map.of(name, ret), "Invoked " + name); - return CompletableFuture.completedFuture(ret); + return CompletableFuture.completedFuture(ret).thenCompose(model::gateFuture); } @Override @@ -338,8 +338,7 @@ public class GadpClientServerTest { implements TargetMethod { private Function method; - public TestTargetMethod(TargetObject parent, String key, - Function method) { + public TestTargetMethod(TargetObject parent, String key, Function method) { super(parent.getModel(), parent, key, "Method"); this.method = method; @@ -401,8 +400,7 @@ public class GadpClientServerTest { } @Override - public CompletableFuture requestElements( - boolean refresh) { + public CompletableFuture requestElements(boolean refresh) { setElements(List.of( new TestGadpTargetAvailable(this, 1, "echo"), new TestGadpTargetAvailable(this, 2, "dd")), @@ -619,7 +617,7 @@ public class GadpClientServerTest { TargetMethod method = waitOn(methodRef.as(TargetMethod.tclass).fetch()); assertNotNull(method); assertEquals("Hello, World!", waitOn(avail.fetchAttribute("greet(World)"))); - runner.server.model.session.available.punct = '?'; + runner.server.model.session.available.punct = '?'; // No effect before flush assertEquals("Hello, World!", waitOn(avail.fetchAttribute("greet(World)"))); // Flush the cache @@ -746,6 +744,7 @@ public class GadpClientServerTest { (TargetObjectRef) attrs.get(TargetFocusScope.FOCUS_ATTRIBUTE_NAME); // NOTE: Could be actual object, but need not be assertEquals(List.of("Processes", "[0]"), ref.getPath()); + waitOn(focusPath); waitOn(client.close()); assertFalse(failed.get()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 73d951588e..353d1340e8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -60,18 +60,17 @@ import ghidra.util.classfinder.ClassSearcher; import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.ListenerSet; -@PluginInfo( // - shortDescription = "Debugger models manager service", // - description = "Manage debug sessions, connections, and trace recording", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.HIDDEN, // - servicesRequired = { // - }, // - servicesProvided = { // - DebuggerModelService.class, // - } // -) +@PluginInfo( + shortDescription = "Debugger models manager service", + description = "Manage debug sessions, connections, and trace recording", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.HIDDEN, + servicesRequired = { + }, + servicesProvided = { + DebuggerModelService.class, + }) public class DebuggerModelServicePlugin extends Plugin implements DebuggerModelServiceInternal, FrontEndOnly { @@ -125,6 +124,9 @@ public class DebuggerModelServicePlugin extends Plugin this.root = r; } r.addListener(this.forRemoval); + if (!r.isValid()) { + forRemoval.invalidated(root, "Who knows?"); + } CompletableFuture> findSuitable = DebugModelConventions.findSuitable(TargetFocusScope.tclass, r); return findSuitable; @@ -132,7 +134,9 @@ public class DebuggerModelServicePlugin extends Plugin synchronized (this) { this.focusScope = fs; } - fs.addListener(this.forFocus); + if (fs != null) { + fs.addListener(this.forFocus); + } }); } @@ -436,6 +440,10 @@ public class DebuggerModelServicePlugin extends Plugin @Override public synchronized DebuggerObjectModel getCurrentModel() { + if (!currentModel.isAlive()) { + currentModel = null; + + } return currentModel; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java index af09e09783..bea3660262 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java @@ -24,6 +24,20 @@ import ghidra.program.model.lang.Language; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; +/** + * An executor which can perform (some of) its work asynchronously + * + *

+ * Note that a future returned from, e.g., {@link #executeAsync(SleighProgram, SleighUseropLibrary)} + * may complete before the computation has actually been performed. They complete when all of the + * operations have been scheduled, and the last future has been written into the state. (This + * typically happens when any branch conditions have completed). Instead, a caller should read from + * the async state, which will return a future. The state will ensure that future does not complete + * until the computation has been performed -- assuming the requested variable actually depends on + * that computation. + * + * @param the type of values in the state + */ public class AsyncPcodeExecutor extends PcodeExecutor> { public AsyncPcodeExecutor(Language language, PcodeArithmetic> behavior, PcodeExecutorStatePiece, CompletableFuture> state) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java index 18f1054abc..a9f0ea72d3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java @@ -52,8 +52,7 @@ public class AsyncWrappedPcodeExecutorStatePiece } protected CompletableFuture doGetVar(AddressSpace space, CompletableFuture offset, - int size, - boolean truncateAddressableUnit) { + int size, boolean truncateAddressableUnit) { return offset.thenApply(off -> { return state.getVar(space, off, size, truncateAddressableUnit); }); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java index a9b914bf12..504b957271 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java @@ -35,6 +35,7 @@ import ghidra.dbg.model.TestTargetMemoryRegion; import ghidra.dbg.model.TestTargetProcess; import ghidra.dbg.target.*; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; +import ghidra.dbg.util.DebuggerModelTestUtils; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Bookmark; import ghidra.program.model.listing.Program; @@ -49,7 +50,8 @@ import ghidra.util.SystemUtilities; import ghidra.util.database.UndoableTransaction; import ghidra.util.datastruct.ListenerMap; -public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest { +public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest + implements DebuggerModelTestUtils { protected static final long TIMEOUT_MILLIS = SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE; @@ -1261,7 +1263,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe } @Test - public void testPlaceDisableStepThenEnableTraceOnly() throws Exception { + public void testPlaceDisableStepThenEnableTraceOnly() throws Throwable { startRecorder1(); Trace trace = recorder1.getTrace(); traceManager.openTrace(trace); @@ -1274,24 +1276,25 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe assertLogicalBreakpointForLoneSoftwareBreakpoint(trace); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); - lb.disable(); + waitOn(lb.disable()); waitForDomainObject(trace); assertEquals(Enablement.DISABLED, lb.computeEnablement()); // Simulate a step, which should also cause snap advance in recorder long oldSnap = recorder1.getSnap(); mb.testModel.session.simulateStep(mb.testThread1); + waitOn(mb.testModel.getClientExecutor()); assertEquals(oldSnap + 1, recorder1.getSnap()); assertEquals(Enablement.DISABLED, lb.computeEnablement()); - lb.enable(); + waitOn(lb.enable()); waitForDomainObject(trace); assertEquals(Enablement.ENABLED, lb.computeEnablement()); } @Test - public void testDeleteBreakpointTraceOnly() throws Exception { + public void testDeleteBreakpointTraceOnly() throws Throwable { startRecorder1(); Trace trace = recorder1.getTrace(); traceManager.openTrace(trace); @@ -1305,14 +1308,14 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); - lb.delete(); + waitOn(lb.delete()); waitForDomainObject(trace); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @Test - public void testPlaceStepThenDeleteBreakpointTraceOnly() throws Exception { + public void testPlaceStepThenDeleteBreakpointTraceOnly() throws Throwable { startRecorder1(); Trace trace = recorder1.getTrace(); traceManager.openTrace(trace); @@ -1329,9 +1332,10 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe // Simulate a step, which should also cause snap advance in recorder long oldSnap = recorder1.getSnap(); mb.testModel.session.simulateStep(mb.testThread1); + waitOn(mb.testModel.getClientExecutor()); assertEquals(oldSnap + 1, recorder1.getSnap()); - lb.delete(); + waitOn(lb.delete()); waitForDomainObject(trace); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java index baebfe96e4..038f079ef9 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java @@ -33,8 +33,7 @@ import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.model.TestDebuggerObjectModel; import ghidra.dbg.model.TestLocalDebuggerModelFactory; -import ghidra.dbg.util.PathMatcher; -import ghidra.dbg.util.PathUtils; +import ghidra.dbg.util.*; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; import ghidra.util.SystemUtilities; @@ -47,7 +46,8 @@ import mockit.VerificationsInOrder; * * TODO: Cover cases where multiple recorders are present */ -public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITest { +public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITest + implements DebuggerModelTestUtils { protected static final long TIMEOUT_MILLIS = SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE; @@ -266,20 +266,23 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes } @Test - public void testRecordThenCloseStopsRecording() throws Exception { + public void testRecordThenCloseStopsRecording() throws Throwable { createTestModel(); mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, new TestDebuggerTargetTraceMapper(mb.testProcess1)); + assertNotNull(recorder); + waitOn(recorder.init()); // Already initializing, just wait for it to complete - CompletableFuture disconnect = mb.testModel.close(); - disconnect.get(); - waitForCondition(() -> !recorder.isRecording(), "Still recording"); + waitOn(mb.testModel.close()); + waitForPass(() -> { + assertFalse("Still recording", recorder.isRecording()); + }); } @Test - public void testRecordAndOpenThenCloseModelAndTraceLeavesNoConsumers() throws Exception { + public void testRecordAndOpenThenCloseModelAndTraceLeavesNoConsumers() throws Throwable { createTestModel(); mb.createTestProcessesAndThreads(); @@ -291,9 +294,10 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes assertNotNull("No active trace", trace); traceManager.closeTrace(trace); - CompletableFuture disconnect = mb.testModel.close(); - disconnect.get(); - assertEquals(List.of(), trace.getConsumerList()); + waitOn(mb.testModel.close()); + waitForPass(() -> { + assertEquals(List.of(), trace.getConsumerList()); + }); } @Test @@ -359,7 +363,13 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes TraceThread traceThread = Unique.assertOne( recorder.getTrace().getThreadManager().getThreadsByPath("Processes[1].Threads[1]")); - assertEquals(mb.testThread1, modelService.getTargetThread(traceThread)); + /** + * There's a brief period where the trace thread exists, but it hasn't been entered into the + * recorder's internal map, yet. So, we have to wait. + */ + waitForPass(() -> { + assertEquals(mb.testThread1, modelService.getTargetThread(traceThread)); + }); } protected void doTestGetTraceThread(Runnable preRec, Runnable postRec) throws Exception { @@ -429,7 +439,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes } @Test - public void testTargetFocus() throws Exception { + public void testTargetFocus() throws Throwable { createTestModel(); mb.createTestProcessesAndThreads(); @@ -442,19 +452,19 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes assertNull(modelService.getTargetFocus(mb.testProcess1)); assertNull(modelService.getTargetFocus(mb.testProcess3)); - mb.testModel.requestFocus(mb.testThread1); + waitOn(mb.testModel.requestFocus(mb.testThread1)); assertEquals(mb.testThread1, modelService.getTargetFocus(mb.testProcess1)); assertNull(modelService.getTargetFocus(mb.testProcess3)); - mb.testModel.requestFocus(mb.testThread2); + waitOn(mb.testModel.requestFocus(mb.testThread2)); assertEquals(mb.testThread2, modelService.getTargetFocus(mb.testProcess1)); assertNull(modelService.getTargetFocus(mb.testProcess3)); - mb.testModel.requestFocus(mb.testThread3); + waitOn(mb.testModel.requestFocus(mb.testThread3)); assertEquals(mb.testThread2, modelService.getTargetFocus(mb.testProcess1)); assertEquals(mb.testThread3, modelService.getTargetFocus(mb.testProcess3)); - mb.testModel.requestFocus(mb.testThread4); + waitOn(mb.testModel.requestFocus(mb.testThread4)); assertEquals(mb.testThread2, modelService.getTargetFocus(mb.testProcess1)); assertEquals(mb.testThread4, modelService.getTargetFocus(mb.testProcess3)); } @@ -484,13 +494,13 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes } @Test - public void testCurrentModelNullAfterClose() throws Exception { + public void testCurrentModelNullAfterClose() throws Throwable { createTestModel(); modelService.activateModel(mb.testModel); assertEquals(mb.testModel, modelService.getCurrentModel()); - mb.testModel.close(); + waitOn(mb.testModel.close()); assertNull(modelService.getCurrentModel()); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java index c59a102a97..f747db9ce0 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java @@ -61,8 +61,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge Language language = trace.getBaseLanguage(); SleighExpression expr = SleighProgramCompiler - .compileExpression((SleighLanguage) language, - "r0 + r1"); + .compileExpression((SleighLanguage) language, "r0 + r1"); AsyncPcodeExecutor executor = new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language), @@ -94,10 +93,12 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge SleighProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language, "test", List.of("r2 = r0 + r1;"), SleighUseropLibrary.NIL); - AsyncPcodeExecutor executor = - new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language), - new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0)); + TraceRecorderAsyncPcodeExecutorState asyncState = + new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0); + AsyncPcodeExecutor executor = new AsyncPcodeExecutor<>( + language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState); waitOn(executor.executeAsync(prog, SleighUseropLibrary.nil())); + waitOn(asyncState.getVar(language.getRegister("r2"))); assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2"))); } diff --git a/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncTestUtils.java b/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncTestUtils.java index 52405d7cea..2af3b1d6dd 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncTestUtils.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/test/java/ghidra/async/AsyncTestUtils.java @@ -15,8 +15,7 @@ */ package ghidra.async; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import ghidra.util.SystemUtilities; @@ -32,4 +31,8 @@ public interface AsyncTestUtils { throw AsyncUtils.unwrapThrowable(e); } } + + default void waitOn(Executor executor) throws Throwable { + waitOn(CompletableFuture.supplyAsync(() -> null, executor)); + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java index b7a074b4af..c876b26de7 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java @@ -21,6 +21,7 @@ import ghidra.util.classfinder.ExtensionPoint; /** * A factory for a debugger model * + *

* This provides a discoverable means of creating a debug model. */ public interface DebuggerModelFactory diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java index bc61b34256..acaca6a010 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java @@ -509,4 +509,39 @@ public interface DebuggerObjectModel { * @return the executor */ Executor getClientExecutor(); + + /** + * Ensure that dependent computations occur on the client executor + * + *

+ * This also preserves scheduling order on the executor. Using just + * {@link CompletableFuture#thenApplyAsync(java.util.function.Function)} makes no guarantees + * about execution order, because that invocation could occur before invocations in the chained + * actions. This one instead uses + * {@link CompletableFuture#thenCompose(java.util.function.Function)} to schedule a final action + * which performs the actual completion via the executor. + * + * @param the type of the future value + * @param cf the future + * @return a future gated via the client executor + */ + default CompletableFuture gateFuture(CompletableFuture cf) { + return cf.thenCompose(this::gateFuture); + } + + /** + * Ensure that dependent computations occur on the client executor + * + *

+ * Use as a method reference in a final call to + * {@link CompletableFuture#thenCompose(java.util.function.Function)} to ensure the final stage + * completes on the client executor. + * + * @param the type of the future value + * @param v the value + * @return a future while completes with the given value on the client executor + */ + default CompletableFuture gateFuture(T v) { + return CompletableFuture.supplyAsync(() -> v, getClientExecutor()); + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java index 0b443a2bca..9c3c1b22ed 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java @@ -163,7 +163,7 @@ public class DefaultTargetObject } req = curElemsRequest; } - return req.thenApply(__ -> getCachedElements()); + return req.thenApply(__ -> getCachedElements()).thenCompose(model::gateFuture); } @Override @@ -337,7 +337,7 @@ public class DefaultTargetObject } return getCachedAttributes(); } - }); + }).thenCompose(model::gateFuture); } @Override diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/agent/DefaultDebuggerObjectModelTest.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/agent/DefaultDebuggerObjectModelTest.java index e0a8c3fc9f..dcc07dddea 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/agent/DefaultDebuggerObjectModelTest.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/agent/DefaultDebuggerObjectModelTest.java @@ -20,11 +20,12 @@ import static org.junit.Assert.*; import java.util.List; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; import org.junit.Test; import generic.Unique; +import ghidra.async.AsyncTestUtils; import ghidra.async.AsyncUtils; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.target.TargetObject; @@ -34,11 +35,8 @@ import ghidra.dbg.util.ElementsChangedListener.ElementsChangedInvocation; import ghidra.dbg.util.InvalidatedListener.InvalidatedInvocation; import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressSpace; -import ghidra.util.SystemUtilities; -public class DefaultDebuggerObjectModelTest { - static final long TIMEOUT_MILLISECONDS = - SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE; +public class DefaultDebuggerObjectModelTest implements AsyncTestUtils { public static class FakeTargetObject extends DefaultTargetObject { public FakeTargetObject(DebuggerObjectModel model, TargetObject parent, String name) { @@ -92,15 +90,6 @@ public class DefaultDebuggerObjectModelTest { } } - protected static T waitOn(CompletableFuture future) throws Throwable { - try { - return future.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); - } - catch (ExecutionException e) { - throw e.getCause(); - } - } - FakeDebuggerObjectModel model = new FakeDebuggerObjectModel(); @Test @@ -187,6 +176,7 @@ public class DefaultDebuggerObjectModelTest { PhonyTargetObject phonyA = new PhonyTargetObject(model, model.root, "A"); model.root.setAttributes(Map.of("A", phonyA), "Replace"); + waitOn(model.clientExecutor); // Object-valued attribute replacement requires prior removal assertSame(fakeA, waitOn(model.fetchModelObject("A"))); @@ -197,6 +187,7 @@ public class DefaultDebuggerObjectModelTest { // TODO: Should I permit custom equality check? model.root.setAttributes(Map.of(), "Clear"); model.root.setAttributes(Map.of("A", phonyA), "Replace"); + waitOn(model.clientExecutor); assertEquals(2, attrL.invocations.size()); AttributesChangedInvocation changed = attrL.invocations.get(0); diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java index a4b8975f60..42141d12d9 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java @@ -57,7 +57,7 @@ public abstract class AbstractTestTargetRegisterBank { listeners.fire(TargetRegisterBankListener.class).registersUpdated(this, result); return result; - }); + }).thenCompose(model::gateFuture); } protected CompletableFuture writeRegs(Map values, @@ -81,7 +81,7 @@ public abstract class AbstractTestTargetRegisterBank { listeners.fire(TargetRegisterBankListener.class).registersUpdated(this, updates); - }); + }).thenCompose(model::gateFuture); return future; } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java index 141bdd2603..a5af500f20 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java @@ -32,7 +32,7 @@ public class TestDebuggerObjectModel extends AbstractDebuggerObjectModel { CompletableFuture.delayedExecutor(DELAY_MILLIS, TimeUnit.MILLISECONDS); enum FutureMode { - SYNC, ASYNC, DELAYED; + ASYNC, DELAYED; } protected final AddressSpace ram = @@ -40,8 +40,6 @@ public class TestDebuggerObjectModel extends AbstractDebuggerObjectModel { protected final AddressFactory factory = new DefaultAddressFactory(new AddressSpace[] { ram }); public final TestTargetSession session; - protected TestDebuggerObjectModel.FutureMode futureMode = FutureMode.SYNC; - protected int invalidateCachesCount; public TestDebuggerObjectModel() { @@ -78,15 +76,7 @@ public class TestDebuggerObjectModel extends AbstractDebuggerObjectModel { } public CompletableFuture future(T t) { - switch (futureMode) { - case SYNC: - return CompletableFuture.completedFuture(t); - case ASYNC: - return CompletableFuture.supplyAsync(() -> t); - case DELAYED: - return CompletableFuture.supplyAsync(() -> t, DELAYED_EXECUTOR); - } - throw new AssertionError(); + return CompletableFuture.supplyAsync(() -> t, getClientExecutor()); } public CompletableFuture requestFocus(TargetObjectRef obj) { diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java index 6cad3a89f7..ee24da2006 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetSession.java @@ -64,7 +64,7 @@ public class TestTargetSession extends DefaultTargetModelRoot FOCUS_ATTRIBUTE_NAME, obj // ), "Focus requested"); listeners.fire(TargetFocusScopeListener.class).focusChanged(this, obj); - }); + }).thenCompose(model::gateFuture); } public void simulateStep(TestTargetThread eventThread) { diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java index 6ceddbd9dc..55afd4c956 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java @@ -17,12 +17,18 @@ package ghidra.pcode.exec; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; import ghidra.program.model.pcode.Varnode; public interface PcodeExecutorStatePiece { A longToOffset(AddressSpace space, long l); + default void setVar(Register reg, T val) { + Address address = reg.getAddress(); + setVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(), true, val); + } + default void setVar(Varnode var, T val) { Address address = var.getAddress(); setVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, val); @@ -35,6 +41,12 @@ public interface PcodeExecutorStatePiece { setVar(space, longToOffset(space, offset), size, truncateAddressableUnit, val); } + default T getVar(Register reg) { + Address address = reg.getAddress(); + return getVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(), + true); + } + default T getVar(Varnode var) { Address address = var.getAddress(); return getVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true);