mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-28 14:45:47 +08:00
GP-0: Fixing test fallout from GP-690
This commit is contained in:
@@ -267,8 +267,8 @@ public class GadpClientServerTest {
|
|||||||
return processes;
|
return processes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLinks() {
|
public void addLinks() throws Throwable {
|
||||||
assertNotNull(available.fetchElements().getNow(null));
|
waitOn(available.fetchElements());
|
||||||
links.setElements(List.of(), Map.of(
|
links.setElements(List.of(), Map.of(
|
||||||
"1", available.getCachedElements().get("2"),
|
"1", available.getCachedElements().get("2"),
|
||||||
"2", available.getCachedElements().get("1")),
|
"2", available.getCachedElements().get("1")),
|
||||||
@@ -309,7 +309,7 @@ public class GadpClientServerTest {
|
|||||||
}
|
}
|
||||||
Object ret = method.testInvoke(invocation.getValue());
|
Object ret = method.testInvoke(invocation.getValue());
|
||||||
changeAttributes(List.of(), Map.of(name, ret), "Invoked " + name);
|
changeAttributes(List.of(), Map.of(name, ret), "Invoked " + name);
|
||||||
return CompletableFuture.completedFuture(ret);
|
return CompletableFuture.completedFuture(ret).thenCompose(model::gateFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -338,8 +338,7 @@ public class GadpClientServerTest {
|
|||||||
implements TargetMethod<TestTargetMethod> {
|
implements TargetMethod<TestTargetMethod> {
|
||||||
private Function<String, ?> method;
|
private Function<String, ?> method;
|
||||||
|
|
||||||
public TestTargetMethod(TargetObject parent, String key,
|
public TestTargetMethod(TargetObject parent, String key, Function<String, ?> method) {
|
||||||
Function<String, ?> method) {
|
|
||||||
super(parent.getModel(), parent, key, "Method");
|
super(parent.getModel(), parent, key, "Method");
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
|
||||||
@@ -401,8 +400,7 @@ public class GadpClientServerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> requestElements(
|
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||||
boolean refresh) {
|
|
||||||
setElements(List.of(
|
setElements(List.of(
|
||||||
new TestGadpTargetAvailable(this, 1, "echo"),
|
new TestGadpTargetAvailable(this, 1, "echo"),
|
||||||
new TestGadpTargetAvailable(this, 2, "dd")),
|
new TestGadpTargetAvailable(this, 2, "dd")),
|
||||||
@@ -619,7 +617,7 @@ public class GadpClientServerTest {
|
|||||||
TargetMethod<?> method = waitOn(methodRef.as(TargetMethod.tclass).fetch());
|
TargetMethod<?> method = waitOn(methodRef.as(TargetMethod.tclass).fetch());
|
||||||
assertNotNull(method);
|
assertNotNull(method);
|
||||||
assertEquals("Hello, World!", waitOn(avail.fetchAttribute("greet(World)")));
|
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)")));
|
assertEquals("Hello, World!", waitOn(avail.fetchAttribute("greet(World)")));
|
||||||
|
|
||||||
// Flush the cache
|
// Flush the cache
|
||||||
@@ -746,6 +744,7 @@ public class GadpClientServerTest {
|
|||||||
(TargetObjectRef) attrs.get(TargetFocusScope.FOCUS_ATTRIBUTE_NAME);
|
(TargetObjectRef) attrs.get(TargetFocusScope.FOCUS_ATTRIBUTE_NAME);
|
||||||
// NOTE: Could be actual object, but need not be
|
// NOTE: Could be actual object, but need not be
|
||||||
assertEquals(List.of("Processes", "[0]"), ref.getPath());
|
assertEquals(List.of("Processes", "[0]"), ref.getPath());
|
||||||
|
waitOn(focusPath);
|
||||||
waitOn(client.close());
|
waitOn(client.close());
|
||||||
|
|
||||||
assertFalse(failed.get());
|
assertFalse(failed.get());
|
||||||
|
|||||||
+21
-13
@@ -60,18 +60,17 @@ import ghidra.util.classfinder.ClassSearcher;
|
|||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger models manager service", //
|
shortDescription = "Debugger models manager service",
|
||||||
description = "Manage debug sessions, connections, and trace recording", //
|
description = "Manage debug sessions, connections, and trace recording",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.HIDDEN, //
|
status = PluginStatus.HIDDEN,
|
||||||
servicesRequired = { //
|
servicesRequired = {
|
||||||
}, //
|
},
|
||||||
servicesProvided = { //
|
servicesProvided = {
|
||||||
DebuggerModelService.class, //
|
DebuggerModelService.class,
|
||||||
} //
|
})
|
||||||
)
|
|
||||||
public class DebuggerModelServicePlugin extends Plugin
|
public class DebuggerModelServicePlugin extends Plugin
|
||||||
implements DebuggerModelServiceInternal, FrontEndOnly {
|
implements DebuggerModelServiceInternal, FrontEndOnly {
|
||||||
|
|
||||||
@@ -125,6 +124,9 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||||||
this.root = r;
|
this.root = r;
|
||||||
}
|
}
|
||||||
r.addListener(this.forRemoval);
|
r.addListener(this.forRemoval);
|
||||||
|
if (!r.isValid()) {
|
||||||
|
forRemoval.invalidated(root, "Who knows?");
|
||||||
|
}
|
||||||
CompletableFuture<? extends TargetFocusScope<?>> findSuitable =
|
CompletableFuture<? extends TargetFocusScope<?>> findSuitable =
|
||||||
DebugModelConventions.findSuitable(TargetFocusScope.tclass, r);
|
DebugModelConventions.findSuitable(TargetFocusScope.tclass, r);
|
||||||
return findSuitable;
|
return findSuitable;
|
||||||
@@ -132,7 +134,9 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.focusScope = fs;
|
this.focusScope = fs;
|
||||||
}
|
}
|
||||||
fs.addListener(this.forFocus);
|
if (fs != null) {
|
||||||
|
fs.addListener(this.forFocus);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +440,10 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized DebuggerObjectModel getCurrentModel() {
|
public synchronized DebuggerObjectModel getCurrentModel() {
|
||||||
|
if (!currentModel.isAlive()) {
|
||||||
|
currentModel = null;
|
||||||
|
|
||||||
|
}
|
||||||
return currentModel;
|
return currentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,20 @@ import ghidra.program.model.lang.Language;
|
|||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
import ghidra.program.model.pcode.Varnode;
|
import ghidra.program.model.pcode.Varnode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An executor which can perform (some of) its work asynchronously
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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 <T> the type of values in the state
|
||||||
|
*/
|
||||||
public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
|
public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
|
||||||
public AsyncPcodeExecutor(Language language, PcodeArithmetic<CompletableFuture<T>> behavior,
|
public AsyncPcodeExecutor(Language language, PcodeArithmetic<CompletableFuture<T>> behavior,
|
||||||
PcodeExecutorStatePiece<CompletableFuture<T>, CompletableFuture<T>> state) {
|
PcodeExecutorStatePiece<CompletableFuture<T>, CompletableFuture<T>> state) {
|
||||||
|
|||||||
+1
-2
@@ -52,8 +52,7 @@ public class AsyncWrappedPcodeExecutorStatePiece<A, T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset,
|
protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset,
|
||||||
int size,
|
int size, boolean truncateAddressableUnit) {
|
||||||
boolean truncateAddressableUnit) {
|
|
||||||
return offset.thenApply(off -> {
|
return offset.thenApply(off -> {
|
||||||
return state.getVar(space, off, size, truncateAddressableUnit);
|
return state.getVar(space, off, size, truncateAddressableUnit);
|
||||||
});
|
});
|
||||||
|
|||||||
+12
-8
@@ -35,6 +35,7 @@ import ghidra.dbg.model.TestTargetMemoryRegion;
|
|||||||
import ghidra.dbg.model.TestTargetProcess;
|
import ghidra.dbg.model.TestTargetProcess;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||||
|
import ghidra.dbg.util.DebuggerModelTestUtils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Bookmark;
|
import ghidra.program.model.listing.Bookmark;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
@@ -49,7 +50,8 @@ import ghidra.util.SystemUtilities;
|
|||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.datastruct.ListenerMap;
|
import ghidra.util.datastruct.ListenerMap;
|
||||||
|
|
||||||
public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest
|
||||||
|
implements DebuggerModelTestUtils {
|
||||||
protected static final long TIMEOUT_MILLIS =
|
protected static final long TIMEOUT_MILLIS =
|
||||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||||
|
|
||||||
@@ -1261,7 +1263,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPlaceDisableStepThenEnableTraceOnly() throws Exception {
|
public void testPlaceDisableStepThenEnableTraceOnly() throws Throwable {
|
||||||
startRecorder1();
|
startRecorder1();
|
||||||
Trace trace = recorder1.getTrace();
|
Trace trace = recorder1.getTrace();
|
||||||
traceManager.openTrace(trace);
|
traceManager.openTrace(trace);
|
||||||
@@ -1274,24 +1276,25 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||||||
assertLogicalBreakpointForLoneSoftwareBreakpoint(trace);
|
assertLogicalBreakpointForLoneSoftwareBreakpoint(trace);
|
||||||
|
|
||||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||||
lb.disable();
|
waitOn(lb.disable());
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(trace);
|
||||||
assertEquals(Enablement.DISABLED, lb.computeEnablement());
|
assertEquals(Enablement.DISABLED, lb.computeEnablement());
|
||||||
|
|
||||||
// Simulate a step, which should also cause snap advance in recorder
|
// Simulate a step, which should also cause snap advance in recorder
|
||||||
long oldSnap = recorder1.getSnap();
|
long oldSnap = recorder1.getSnap();
|
||||||
mb.testModel.session.simulateStep(mb.testThread1);
|
mb.testModel.session.simulateStep(mb.testThread1);
|
||||||
|
waitOn(mb.testModel.getClientExecutor());
|
||||||
assertEquals(oldSnap + 1, recorder1.getSnap());
|
assertEquals(oldSnap + 1, recorder1.getSnap());
|
||||||
|
|
||||||
assertEquals(Enablement.DISABLED, lb.computeEnablement());
|
assertEquals(Enablement.DISABLED, lb.computeEnablement());
|
||||||
|
|
||||||
lb.enable();
|
waitOn(lb.enable());
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(trace);
|
||||||
assertEquals(Enablement.ENABLED, lb.computeEnablement());
|
assertEquals(Enablement.ENABLED, lb.computeEnablement());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteBreakpointTraceOnly() throws Exception {
|
public void testDeleteBreakpointTraceOnly() throws Throwable {
|
||||||
startRecorder1();
|
startRecorder1();
|
||||||
Trace trace = recorder1.getTrace();
|
Trace trace = recorder1.getTrace();
|
||||||
traceManager.openTrace(trace);
|
traceManager.openTrace(trace);
|
||||||
@@ -1305,14 +1308,14 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||||||
|
|
||||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||||
|
|
||||||
lb.delete();
|
waitOn(lb.delete());
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(trace);
|
||||||
|
|
||||||
assertTrue(breakpointService.getAllBreakpoints().isEmpty());
|
assertTrue(breakpointService.getAllBreakpoints().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPlaceStepThenDeleteBreakpointTraceOnly() throws Exception {
|
public void testPlaceStepThenDeleteBreakpointTraceOnly() throws Throwable {
|
||||||
startRecorder1();
|
startRecorder1();
|
||||||
Trace trace = recorder1.getTrace();
|
Trace trace = recorder1.getTrace();
|
||||||
traceManager.openTrace(trace);
|
traceManager.openTrace(trace);
|
||||||
@@ -1329,9 +1332,10 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||||||
// Simulate a step, which should also cause snap advance in recorder
|
// Simulate a step, which should also cause snap advance in recorder
|
||||||
long oldSnap = recorder1.getSnap();
|
long oldSnap = recorder1.getSnap();
|
||||||
mb.testModel.session.simulateStep(mb.testThread1);
|
mb.testModel.session.simulateStep(mb.testThread1);
|
||||||
|
waitOn(mb.testModel.getClientExecutor());
|
||||||
assertEquals(oldSnap + 1, recorder1.getSnap());
|
assertEquals(oldSnap + 1, recorder1.getSnap());
|
||||||
|
|
||||||
lb.delete();
|
waitOn(lb.delete());
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(trace);
|
||||||
|
|
||||||
assertTrue(breakpointService.getAllBreakpoints().isEmpty());
|
assertTrue(breakpointService.getAllBreakpoints().isEmpty());
|
||||||
|
|||||||
+29
-19
@@ -33,8 +33,7 @@ import ghidra.dbg.DebuggerModelFactory;
|
|||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.model.TestDebuggerObjectModel;
|
import ghidra.dbg.model.TestDebuggerObjectModel;
|
||||||
import ghidra.dbg.model.TestLocalDebuggerModelFactory;
|
import ghidra.dbg.model.TestLocalDebuggerModelFactory;
|
||||||
import ghidra.dbg.util.PathMatcher;
|
import ghidra.dbg.util.*;
|
||||||
import ghidra.dbg.util.PathUtils;
|
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
@@ -47,7 +46,8 @@ import mockit.VerificationsInOrder;
|
|||||||
*
|
*
|
||||||
* TODO: Cover cases where multiple recorders are present
|
* 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 =
|
protected static final long TIMEOUT_MILLIS =
|
||||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||||
|
|
||||||
@@ -266,20 +266,23 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordThenCloseStopsRecording() throws Exception {
|
public void testRecordThenCloseStopsRecording() throws Throwable {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
|
||||||
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
|
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
|
||||||
new TestDebuggerTargetTraceMapper(mb.testProcess1));
|
new TestDebuggerTargetTraceMapper(mb.testProcess1));
|
||||||
|
assertNotNull(recorder);
|
||||||
|
waitOn(recorder.init()); // Already initializing, just wait for it to complete
|
||||||
|
|
||||||
CompletableFuture<Void> disconnect = mb.testModel.close();
|
waitOn(mb.testModel.close());
|
||||||
disconnect.get();
|
waitForPass(() -> {
|
||||||
waitForCondition(() -> !recorder.isRecording(), "Still recording");
|
assertFalse("Still recording", recorder.isRecording());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordAndOpenThenCloseModelAndTraceLeavesNoConsumers() throws Exception {
|
public void testRecordAndOpenThenCloseModelAndTraceLeavesNoConsumers() throws Throwable {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
|
||||||
@@ -291,9 +294,10 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||||||
assertNotNull("No active trace", trace);
|
assertNotNull("No active trace", trace);
|
||||||
|
|
||||||
traceManager.closeTrace(trace);
|
traceManager.closeTrace(trace);
|
||||||
CompletableFuture<Void> disconnect = mb.testModel.close();
|
waitOn(mb.testModel.close());
|
||||||
disconnect.get();
|
waitForPass(() -> {
|
||||||
assertEquals(List.of(), trace.getConsumerList());
|
assertEquals(List.of(), trace.getConsumerList());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -359,7 +363,13 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||||||
|
|
||||||
TraceThread traceThread = Unique.assertOne(
|
TraceThread traceThread = Unique.assertOne(
|
||||||
recorder.getTrace().getThreadManager().getThreadsByPath("Processes[1].Threads[1]"));
|
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 {
|
protected void doTestGetTraceThread(Runnable preRec, Runnable postRec) throws Exception {
|
||||||
@@ -429,7 +439,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTargetFocus() throws Exception {
|
public void testTargetFocus() throws Throwable {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
|
||||||
@@ -442,19 +452,19 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||||||
assertNull(modelService.getTargetFocus(mb.testProcess1));
|
assertNull(modelService.getTargetFocus(mb.testProcess1));
|
||||||
assertNull(modelService.getTargetFocus(mb.testProcess3));
|
assertNull(modelService.getTargetFocus(mb.testProcess3));
|
||||||
|
|
||||||
mb.testModel.requestFocus(mb.testThread1);
|
waitOn(mb.testModel.requestFocus(mb.testThread1));
|
||||||
assertEquals(mb.testThread1, modelService.getTargetFocus(mb.testProcess1));
|
assertEquals(mb.testThread1, modelService.getTargetFocus(mb.testProcess1));
|
||||||
assertNull(modelService.getTargetFocus(mb.testProcess3));
|
assertNull(modelService.getTargetFocus(mb.testProcess3));
|
||||||
|
|
||||||
mb.testModel.requestFocus(mb.testThread2);
|
waitOn(mb.testModel.requestFocus(mb.testThread2));
|
||||||
assertEquals(mb.testThread2, modelService.getTargetFocus(mb.testProcess1));
|
assertEquals(mb.testThread2, modelService.getTargetFocus(mb.testProcess1));
|
||||||
assertNull(modelService.getTargetFocus(mb.testProcess3));
|
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.testThread2, modelService.getTargetFocus(mb.testProcess1));
|
||||||
assertEquals(mb.testThread3, modelService.getTargetFocus(mb.testProcess3));
|
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.testThread2, modelService.getTargetFocus(mb.testProcess1));
|
||||||
assertEquals(mb.testThread4, modelService.getTargetFocus(mb.testProcess3));
|
assertEquals(mb.testThread4, modelService.getTargetFocus(mb.testProcess3));
|
||||||
}
|
}
|
||||||
@@ -484,13 +494,13 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentModelNullAfterClose() throws Exception {
|
public void testCurrentModelNullAfterClose() throws Throwable {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
|
|
||||||
modelService.activateModel(mb.testModel);
|
modelService.activateModel(mb.testModel);
|
||||||
assertEquals(mb.testModel, modelService.getCurrentModel());
|
assertEquals(mb.testModel, modelService.getCurrentModel());
|
||||||
|
|
||||||
mb.testModel.close();
|
waitOn(mb.testModel.close());
|
||||||
assertNull(modelService.getCurrentModel());
|
assertNull(modelService.getCurrentModel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -61,8 +61,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
|
|||||||
Language language = trace.getBaseLanguage();
|
Language language = trace.getBaseLanguage();
|
||||||
|
|
||||||
SleighExpression expr = SleighProgramCompiler
|
SleighExpression expr = SleighProgramCompiler
|
||||||
.compileExpression((SleighLanguage) language,
|
.compileExpression((SleighLanguage) language, "r0 + r1");
|
||||||
"r0 + r1");
|
|
||||||
|
|
||||||
AsyncPcodeExecutor<byte[]> executor =
|
AsyncPcodeExecutor<byte[]> executor =
|
||||||
new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language),
|
new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language),
|
||||||
@@ -94,10 +93,12 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
|
|||||||
SleighProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language, "test",
|
SleighProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language, "test",
|
||||||
List.of("r2 = r0 + r1;"), SleighUseropLibrary.NIL);
|
List.of("r2 = r0 + r1;"), SleighUseropLibrary.NIL);
|
||||||
|
|
||||||
AsyncPcodeExecutor<byte[]> executor =
|
TraceRecorderAsyncPcodeExecutorState asyncState =
|
||||||
new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language),
|
new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0);
|
||||||
new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0));
|
AsyncPcodeExecutor<byte[]> executor = new AsyncPcodeExecutor<>(
|
||||||
|
language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState);
|
||||||
waitOn(executor.executeAsync(prog, SleighUseropLibrary.nil()));
|
waitOn(executor.executeAsync(prog, SleighUseropLibrary.nil()));
|
||||||
|
waitOn(asyncState.getVar(language.getRegister("r2")));
|
||||||
|
|
||||||
assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2")));
|
assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2")));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.async;
|
package ghidra.async;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
|
|
||||||
@@ -32,4 +31,8 @@ public interface AsyncTestUtils {
|
|||||||
throw AsyncUtils.unwrapThrowable(e);
|
throw AsyncUtils.unwrapThrowable(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void waitOn(Executor executor) throws Throwable {
|
||||||
|
waitOn(CompletableFuture.supplyAsync(() -> null, executor));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import ghidra.util.classfinder.ExtensionPoint;
|
|||||||
/**
|
/**
|
||||||
* A factory for a debugger model
|
* A factory for a debugger model
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* This provides a discoverable means of creating a debug model.
|
* This provides a discoverable means of creating a debug model.
|
||||||
*/
|
*/
|
||||||
public interface DebuggerModelFactory
|
public interface DebuggerModelFactory
|
||||||
|
|||||||
@@ -509,4 +509,39 @@ public interface DebuggerObjectModel {
|
|||||||
* @return the executor
|
* @return the executor
|
||||||
*/
|
*/
|
||||||
Executor getClientExecutor();
|
Executor getClientExecutor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that dependent computations occur on the client executor
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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 <T> the type of the future value
|
||||||
|
* @param cf the future
|
||||||
|
* @return a future gated via the client executor
|
||||||
|
*/
|
||||||
|
default <T> CompletableFuture<T> gateFuture(CompletableFuture<T> cf) {
|
||||||
|
return cf.thenCompose(this::gateFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that dependent computations occur on the client executor
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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 <T> the type of the future value
|
||||||
|
* @param v the value
|
||||||
|
* @return a future while completes with the given value on the client executor
|
||||||
|
*/
|
||||||
|
default <T> CompletableFuture<T> gateFuture(T v) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> v, getClientExecutor());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -163,7 +163,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||||||
}
|
}
|
||||||
req = curElemsRequest;
|
req = curElemsRequest;
|
||||||
}
|
}
|
||||||
return req.thenApply(__ -> getCachedElements());
|
return req.thenApply(__ -> getCachedElements()).thenCompose(model::gateFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -337,7 +337,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||||||
}
|
}
|
||||||
return getCachedAttributes();
|
return getCachedAttributes();
|
||||||
}
|
}
|
||||||
});
|
}).thenCompose(model::gateFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+5
-14
@@ -20,11 +20,12 @@ import static org.junit.Assert.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
|
import ghidra.async.AsyncTestUtils;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
@@ -34,11 +35,8 @@ import ghidra.dbg.util.ElementsChangedListener.ElementsChangedInvocation;
|
|||||||
import ghidra.dbg.util.InvalidatedListener.InvalidatedInvocation;
|
import ghidra.dbg.util.InvalidatedListener.InvalidatedInvocation;
|
||||||
import ghidra.program.model.address.AddressFactory;
|
import ghidra.program.model.address.AddressFactory;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
|
|
||||||
public class DefaultDebuggerObjectModelTest {
|
public class DefaultDebuggerObjectModelTest implements AsyncTestUtils {
|
||||||
static final long TIMEOUT_MILLISECONDS =
|
|
||||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
|
||||||
|
|
||||||
public static class FakeTargetObject extends DefaultTargetObject<TargetObject, TargetObject> {
|
public static class FakeTargetObject extends DefaultTargetObject<TargetObject, TargetObject> {
|
||||||
public FakeTargetObject(DebuggerObjectModel model, TargetObject parent, String name) {
|
public FakeTargetObject(DebuggerObjectModel model, TargetObject parent, String name) {
|
||||||
@@ -92,15 +90,6 @@ public class DefaultDebuggerObjectModelTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static <T> T waitOn(CompletableFuture<T> future) throws Throwable {
|
|
||||||
try {
|
|
||||||
return future.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
catch (ExecutionException e) {
|
|
||||||
throw e.getCause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FakeDebuggerObjectModel model = new FakeDebuggerObjectModel();
|
FakeDebuggerObjectModel model = new FakeDebuggerObjectModel();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -187,6 +176,7 @@ public class DefaultDebuggerObjectModelTest {
|
|||||||
|
|
||||||
PhonyTargetObject phonyA = new PhonyTargetObject(model, model.root, "A");
|
PhonyTargetObject phonyA = new PhonyTargetObject(model, model.root, "A");
|
||||||
model.root.setAttributes(Map.of("A", phonyA), "Replace");
|
model.root.setAttributes(Map.of("A", phonyA), "Replace");
|
||||||
|
waitOn(model.clientExecutor);
|
||||||
|
|
||||||
// Object-valued attribute replacement requires prior removal
|
// Object-valued attribute replacement requires prior removal
|
||||||
assertSame(fakeA, waitOn(model.fetchModelObject("A")));
|
assertSame(fakeA, waitOn(model.fetchModelObject("A")));
|
||||||
@@ -197,6 +187,7 @@ public class DefaultDebuggerObjectModelTest {
|
|||||||
// TODO: Should I permit custom equality check?
|
// TODO: Should I permit custom equality check?
|
||||||
model.root.setAttributes(Map.of(), "Clear");
|
model.root.setAttributes(Map.of(), "Clear");
|
||||||
model.root.setAttributes(Map.of("A", phonyA), "Replace");
|
model.root.setAttributes(Map.of("A", phonyA), "Replace");
|
||||||
|
waitOn(model.clientExecutor);
|
||||||
|
|
||||||
assertEquals(2, attrL.invocations.size());
|
assertEquals(2, attrL.invocations.size());
|
||||||
AttributesChangedInvocation changed = attrL.invocations.get(0);
|
AttributesChangedInvocation changed = attrL.invocations.get(0);
|
||||||
|
|||||||
+2
-2
@@ -57,7 +57,7 @@ public abstract class AbstractTestTargetRegisterBank<T extends AbstractTestTarge
|
|||||||
return regs.getModel().future(result).thenApply(__ -> {
|
return regs.getModel().future(result).thenApply(__ -> {
|
||||||
listeners.fire(TargetRegisterBankListener.class).registersUpdated(this, result);
|
listeners.fire(TargetRegisterBankListener.class).registersUpdated(this, result);
|
||||||
return result;
|
return result;
|
||||||
});
|
}).thenCompose(model::gateFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompletableFuture<Void> writeRegs(Map<String, byte[]> values,
|
protected CompletableFuture<Void> writeRegs(Map<String, byte[]> values,
|
||||||
@@ -81,7 +81,7 @@ public abstract class AbstractTestTargetRegisterBank<T extends AbstractTestTarge
|
|||||||
}
|
}
|
||||||
future.thenAccept(__ -> {
|
future.thenAccept(__ -> {
|
||||||
listeners.fire(TargetRegisterBankListener.class).registersUpdated(this, updates);
|
listeners.fire(TargetRegisterBankListener.class).registersUpdated(this, updates);
|
||||||
});
|
}).thenCompose(model::gateFuture);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-12
@@ -32,7 +32,7 @@ public class TestDebuggerObjectModel extends AbstractDebuggerObjectModel {
|
|||||||
CompletableFuture.delayedExecutor(DELAY_MILLIS, TimeUnit.MILLISECONDS);
|
CompletableFuture.delayedExecutor(DELAY_MILLIS, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
enum FutureMode {
|
enum FutureMode {
|
||||||
SYNC, ASYNC, DELAYED;
|
ASYNC, DELAYED;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final AddressSpace ram =
|
protected final AddressSpace ram =
|
||||||
@@ -40,8 +40,6 @@ public class TestDebuggerObjectModel extends AbstractDebuggerObjectModel {
|
|||||||
protected final AddressFactory factory = new DefaultAddressFactory(new AddressSpace[] { ram });
|
protected final AddressFactory factory = new DefaultAddressFactory(new AddressSpace[] { ram });
|
||||||
public final TestTargetSession session;
|
public final TestTargetSession session;
|
||||||
|
|
||||||
protected TestDebuggerObjectModel.FutureMode futureMode = FutureMode.SYNC;
|
|
||||||
|
|
||||||
protected int invalidateCachesCount;
|
protected int invalidateCachesCount;
|
||||||
|
|
||||||
public TestDebuggerObjectModel() {
|
public TestDebuggerObjectModel() {
|
||||||
@@ -78,15 +76,7 @@ public class TestDebuggerObjectModel extends AbstractDebuggerObjectModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> future(T t) {
|
public <T> CompletableFuture<T> future(T t) {
|
||||||
switch (futureMode) {
|
return CompletableFuture.supplyAsync(() -> t, getClientExecutor());
|
||||||
case SYNC:
|
|
||||||
return CompletableFuture.completedFuture(t);
|
|
||||||
case ASYNC:
|
|
||||||
return CompletableFuture.supplyAsync(() -> t);
|
|
||||||
case DELAYED:
|
|
||||||
return CompletableFuture.supplyAsync(() -> t, DELAYED_EXECUTOR);
|
|
||||||
}
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> requestFocus(TargetObjectRef obj) {
|
public CompletableFuture<Void> requestFocus(TargetObjectRef obj) {
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ public class TestTargetSession extends DefaultTargetModelRoot
|
|||||||
FOCUS_ATTRIBUTE_NAME, obj //
|
FOCUS_ATTRIBUTE_NAME, obj //
|
||||||
), "Focus requested");
|
), "Focus requested");
|
||||||
listeners.fire(TargetFocusScopeListener.class).focusChanged(this, obj);
|
listeners.fire(TargetFocusScopeListener.class).focusChanged(this, obj);
|
||||||
});
|
}).thenCompose(model::gateFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void simulateStep(TestTargetThread eventThread) {
|
public void simulateStep(TestTargetThread eventThread) {
|
||||||
|
|||||||
+12
@@ -17,12 +17,18 @@ package ghidra.pcode.exec;
|
|||||||
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.pcode.Varnode;
|
import ghidra.program.model.pcode.Varnode;
|
||||||
|
|
||||||
public interface PcodeExecutorStatePiece<A, T> {
|
public interface PcodeExecutorStatePiece<A, T> {
|
||||||
|
|
||||||
A longToOffset(AddressSpace space, long l);
|
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) {
|
default void setVar(Varnode var, T val) {
|
||||||
Address address = var.getAddress();
|
Address address = var.getAddress();
|
||||||
setVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, val);
|
setVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, val);
|
||||||
@@ -35,6 +41,12 @@ public interface PcodeExecutorStatePiece<A, T> {
|
|||||||
setVar(space, longToOffset(space, offset), size, truncateAddressableUnit, val);
|
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) {
|
default T getVar(Varnode var) {
|
||||||
Address address = var.getAddress();
|
Address address = var.getAddress();
|
||||||
return getVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true);
|
return getVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true);
|
||||||
|
|||||||
Reference in New Issue
Block a user