mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
Merge remote-tracking branch 'origin/debugger'
This commit is contained in:
commit
ac85e4efbb
200 changed files with 5446 additions and 1424 deletions
|
@ -742,9 +742,8 @@ public enum DebugModelConventions {
|
|||
this.name = name;
|
||||
this.obj = obj;
|
||||
obj.addListener(this);
|
||||
obj.fetchAttribute(name).thenAccept(t -> {
|
||||
set((T) t, null);
|
||||
}).exceptionally(ex -> {
|
||||
set((T) obj.getCachedAttribute(name), null);
|
||||
obj.fetchAttribute(name).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not get initial value of " + name + " for " + obj, ex);
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -342,22 +342,6 @@ public interface DebuggerObjectModel {
|
|||
* are refreshed; and {@code A}'s, {@code B[1]}'s, and {@code C[2]}'s attribute caches are
|
||||
* refreshed.
|
||||
*
|
||||
* @implNote The returned value cannot be a {@link TargetObjectRef} unless the value represents
|
||||
* a link. In other words, if the path refers to an object, the model must return the
|
||||
* object, not a ref. When the value is a link, the implementation may optionally
|
||||
* resolve the object, but should only do so if it doesn't incur a significant cost.
|
||||
* Furthermore, such links cannot be resolved -- though they can be substituted for
|
||||
* the target object at the linked path. In other words, the path of the returned ref
|
||||
* (or object) must represent the link's target. Suppose {@code A[1]} is a link to
|
||||
* {@code B[1]}, which is in turn a link to {@code C[1]} -- honestly, linked links
|
||||
* ought to be a rare occurrence -- then fetching {@code A[1]} must return a ref to
|
||||
* {@code B[1]}. It must not return {@code C[1]} nor a ref to it. The reason deals
|
||||
* with caching and updates. If a request for {@code A[1]} were to return
|
||||
* {@code C[1]}, a client may cache that result. Suppose that client then observes a
|
||||
* change causing {@code B[1]} to link to {@code C[2]}. This implies that {@code A[1]}
|
||||
* now resolves to {@code C[2]}; however, the client has not received enough
|
||||
* information to update or invalidate its cache.
|
||||
*
|
||||
* @param path the path
|
||||
* @param refresh true to refresh caches
|
||||
* @return the found value, or {@code null} if it does not exist
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg;
|
||||
|
||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
||||
|
||||
/**
|
||||
* A factory for a local debugger model
|
||||
*
|
||||
* <p>
|
||||
* These factories are searched when attempting to create a new default debug model targeting the
|
||||
* local environment.
|
||||
*/
|
||||
public interface LocalDebuggerModelFactory extends DebuggerModelFactory {
|
||||
/**
|
||||
* Get the priority of this factory
|
||||
*
|
||||
* <p>
|
||||
* In the event multiple compatible factories are discovered, the one with the highest priority
|
||||
* is selected, breaking ties arbitrarily.
|
||||
*
|
||||
* <p>
|
||||
* The default implementation returns the priority given by {@link ExtensionPointProperties}. If
|
||||
* the priority must be determined dynamically, then override this implementation.
|
||||
*
|
||||
* @return the priority, where lower values indicate higher priority.
|
||||
*/
|
||||
default int getPriority() {
|
||||
return ExtensionPointProperties.Util.getPriority(getClass());
|
||||
}
|
||||
}
|
|
@ -218,7 +218,7 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
|
|||
|
||||
public void removeExisting(List<String> path) {
|
||||
TargetObject existing = getModelObject(path);
|
||||
// It had better be. This also checks for null
|
||||
// It's best if the implementation has already removed it, but just in case....
|
||||
if (existing == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -230,15 +230,27 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
|
|||
if (!path.equals(existing.getPath())) {
|
||||
return; // Is a link
|
||||
}
|
||||
if (parent instanceof DefaultTargetObject<?, ?>) { // It had better be
|
||||
DefaultTargetObject<?, ?> dtoParent = (DefaultTargetObject<?, ?>) parent;
|
||||
if (PathUtils.isIndex(path)) {
|
||||
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), "Replaced");
|
||||
}
|
||||
else {
|
||||
assert PathUtils.isName(path);
|
||||
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), "Replaced");
|
||||
}
|
||||
if (!(parent instanceof SpiTargetObject)) { // It had better be
|
||||
Msg.error(this, "Could not remove existing object " + existing +
|
||||
", because parent is not an SpiTargetObject");
|
||||
return;
|
||||
}
|
||||
SpiTargetObject spiParent = (SpiTargetObject) parent;
|
||||
SpiTargetObject delegate = spiParent.getDelegate();
|
||||
if (!(delegate instanceof DefaultTargetObject<?, ?>)) { // It had better be :)
|
||||
Msg.error(this, "Could not remove existing object " + existing +
|
||||
", because its parent's delegate is not a DefaultTargetObject");
|
||||
return;
|
||||
}
|
||||
DefaultTargetObject<?, ?> dtoParent = (DefaultTargetObject<?, ?>) delegate;
|
||||
if (PathUtils.isIndex(path)) {
|
||||
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(),
|
||||
"Replaced");
|
||||
}
|
||||
else {
|
||||
assert PathUtils.isName(path);
|
||||
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(),
|
||||
"Replaced");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -319,9 +319,9 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
Delta<E, E> delta;
|
||||
synchronized (model.lock) {
|
||||
delta = Delta.computeAndSet(this.elements, elements, Delta.SAME);
|
||||
getSchema().validateElementDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateElements(delta.removed, reason);
|
||||
}
|
||||
getSchema().validateElementDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateElements(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
updateCallbackElements(delta);
|
||||
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
@ -361,9 +361,9 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
Delta<E, E> delta;
|
||||
synchronized (model.lock) {
|
||||
delta = Delta.apply(this.elements, remove, add, Delta.SAME);
|
||||
getSchema().validateElementDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateElements(delta.removed, reason);
|
||||
}
|
||||
getSchema().validateElementDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateElements(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
updateCallbackElements(delta);
|
||||
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
@ -497,9 +497,9 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
Delta<Object, ?> delta;
|
||||
synchronized (model.lock) {
|
||||
delta = Delta.computeAndSet(this.attributes, attributes, Delta.EQUAL);
|
||||
getSchema().validateAttributeDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
}
|
||||
getSchema().validateAttributeDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
updateCallbackAttributes(delta);
|
||||
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
@ -556,9 +556,9 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
Delta<Object, ?> delta;
|
||||
synchronized (model.lock) {
|
||||
delta = Delta.apply(this.attributes, remove, add, Delta.EQUAL);
|
||||
getSchema().validateAttributeDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
}
|
||||
getSchema().validateAttributeDelta(getPath(), delta, enforcesStrictSchema());
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
if (!delta.isEmpty()/* && !reason.equals("Default")*/) {
|
||||
updateCallbackAttributes(delta);
|
||||
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
|
|
@ -26,4 +26,13 @@ public interface SpiTargetObject extends TargetObject, InvalidatableTargetObject
|
|||
//Map<String, ? extends SpiTargetObject> getCachedElements();
|
||||
|
||||
boolean enforcesStrictSchema();
|
||||
|
||||
/**
|
||||
* If this internal implementation is a proxy, get its delegate
|
||||
*
|
||||
* @return the delegate, or this same object
|
||||
*/
|
||||
default SpiTargetObject getDelegate() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
/**
|
||||
* An object made active
|
||||
*
|
||||
* <p>
|
||||
* "Active" here describes which object in a given class the target should operate on
|
||||
*/
|
||||
@DebuggerTargetObjectIface("ActiveScope")
|
||||
|
|
|
@ -98,6 +98,7 @@ public interface TargetLauncher extends TargetObject {
|
|||
|
||||
public CmdLineParser(Reader r) {
|
||||
super(r);
|
||||
resetSyntax();
|
||||
wordChars(0, 255);
|
||||
whitespaceChars(' ', ' ');
|
||||
quoteChar('"');
|
||||
|
|
|
@ -194,4 +194,40 @@ public class PathPattern implements PathPredicates {
|
|||
}
|
||||
return new PathPattern(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given path matches, extract indices where matched by wildcards
|
||||
*
|
||||
* <p>
|
||||
* This is essentially the inverse of {@link #applyIndices(List)}, but can only be asked of one
|
||||
* pattern. The keys are returned from left to right, in the order matched by the pattern. Only
|
||||
* those keys matched by a wildcard are included in the result. Indices are extracted with the
|
||||
* brackets {@code []} removed.
|
||||
*
|
||||
* @param path the path to match
|
||||
* @return the list of matched indices or {@code null} if not matched
|
||||
*/
|
||||
public List<String> matchIndices(List<String> path) {
|
||||
int length = pattern.size();
|
||||
if (length != path.size()) {
|
||||
return null;
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
for (int i = 0; i < length; i++) {
|
||||
String pat = pattern.get(i);
|
||||
String key = path.get(i);
|
||||
if (!keyMatches(pat, key)) {
|
||||
return null;
|
||||
}
|
||||
if (isWildcard(pat)) {
|
||||
if (PathUtils.isIndex(pat)) {
|
||||
result.add(PathUtils.parseIndex(key));
|
||||
}
|
||||
else {
|
||||
result.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg.model;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
|
||||
@FactoryDescription(brief = "Mocked Local Client", htmlDetails = TestDebuggerModelFactory.FAKE_DETAILS)
|
||||
public class TestLocalDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||
protected final Deque<CompletableFuture<DebuggerObjectModel>> buildQueue =
|
||||
new LinkedList<>();
|
||||
|
||||
public TestLocalDebuggerModelFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
CompletableFuture<DebuggerObjectModel> future = new CompletableFuture<>();
|
||||
buildQueue.offer(future);
|
||||
return future;
|
||||
}
|
||||
|
||||
public CompletableFuture<DebuggerObjectModel> pollBuild() {
|
||||
return buildQueue.poll();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg.target;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.dbg.target.TargetLauncher.CmdLineParser;
|
||||
|
||||
public class TargetLauncherTest {
|
||||
@Test
|
||||
public void testCmdLineParser() {
|
||||
assertEquals(List.of("C:\\Windows", "10\\notepad.exe"),
|
||||
CmdLineParser.tokenize("C:\\Windows 10\\notepad.exe"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
/**
|
||||
* Test model object activation and focus
|
||||
*
|
||||
* <p>
|
||||
* Activation and focus are related but separate concepts. Focus is a little looser, and is allowed
|
||||
* by the model to exactly match the client's notion of focus, usually indicating the object of the
|
||||
* user's interest. Activation, however, commands the model to make the given object the "current"
|
||||
* object. This implies any commands issued to the CLI will affect the active object. The model
|
||||
* reflects the active object back to the client via focus. This allows the model and client to
|
||||
* synchronize their "active" objects, while reducing the likelihood of event feedback loops.
|
||||
* Furthermore, not every object can be activated. For example, activating a register will likely
|
||||
* result in the containing thread or frame becoming active instead. Or, activating a thread may
|
||||
* result in its innermost frame becoming active as well.
|
||||
*/
|
||||
public abstract class AbstractDebuggerModelActivationTest extends AbstractDebuggerModelTest {
|
||||
|
||||
/**
|
||||
* Use the interpreter to activate the given object
|
||||
*
|
||||
* @param obj the object to activate
|
||||
* @param interpreter the interpreter to use
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the interpreter to verify the given object is active/current
|
||||
*
|
||||
* <p>
|
||||
* Note, it may be necessary to run and capture several commands, depending on what's being
|
||||
* verified and what sort of commands the interpreter makes available. For example, to verify a
|
||||
* frame is active, the test should check that the containing thread and process are active,
|
||||
* too.
|
||||
*
|
||||
* @param expected the expected active or current object
|
||||
* @param interpreter the interpreter to use
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void assertActiveViaInterpreter(TargetObject expected,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get (possibly generate) things for this focus test to try out
|
||||
*
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected abstract Set<TargetObject> getActivatableThings() throws Throwable;
|
||||
|
||||
/**
|
||||
* Governs whether assertions permit the actual object to be a successor of the expected object
|
||||
*
|
||||
* @return true to permit successors, false to require exact
|
||||
*/
|
||||
protected boolean permitSuccessor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void assertSuccessorOrExact(TargetObject expected, TargetObject actual) {
|
||||
assertNotNull(actual);
|
||||
if (permitSuccessor()) {
|
||||
assertTrue("Expected successor of '" + expected.getJoinedPath(".") +
|
||||
"' got '" + actual.getJoinedPath(".") + "'",
|
||||
PathUtils.isAncestor(expected.getPath(), actual.getPath()));
|
||||
}
|
||||
else {
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the default focus is one of the activatable things (after generation), assert its path
|
||||
*
|
||||
* @return the path of the expected default focus, or {@code null} for no assertion
|
||||
*/
|
||||
protected List<String> getExpectedDefaultActivePath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultFocusIsAsExpected() throws Throwable {
|
||||
List<String> expectedDefaultFocus = getExpectedDefaultActivePath();
|
||||
assumeNotNull(expectedDefaultFocus);
|
||||
m.build();
|
||||
|
||||
TargetFocusScope focusScope = findFocusScope();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
// The default must be one of the activatable objects
|
||||
TargetObject obj = Unique.assertOne(activatable.stream()
|
||||
.filter(f -> PathUtils.isAncestor(f.getPath(), expectedDefaultFocus))
|
||||
.collect(Collectors.toList()));
|
||||
retryVoid(() -> {
|
||||
assertEquals(expectedDefaultFocus, focusScope.getFocus().getPath());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateEachOnce() throws Throwable {
|
||||
m.build();
|
||||
|
||||
TargetFocusScope focusScope = findFocusScope();
|
||||
TargetActiveScope activeScope = findActiveScope();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
for (TargetObject obj : activatable) {
|
||||
waitOn(activeScope.requestActivation(obj));
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, focusScope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateEachTwice() throws Throwable {
|
||||
m.build();
|
||||
|
||||
TargetFocusScope focusScope = findFocusScope();
|
||||
TargetActiveScope activeScope = findActiveScope();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
for (TargetObject obj : activatable) {
|
||||
waitOn(activeScope.requestActivation(obj));
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, focusScope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
waitOn(activeScope.requestActivation(obj));
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, focusScope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateEachViaInterpreter() throws Throwable {
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
TargetFocusScope focusScope = findFocusScope();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
for (TargetObject obj : activatable) {
|
||||
activateViaInterpreter(obj, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, focusScope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,8 +16,7 @@
|
|||
package ghidra.dbg.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
@ -162,10 +161,97 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
return null;
|
||||
}
|
||||
|
||||
protected void runTestPlaceBreakpoint(TargetBreakpointKind kind) throws Throwable {
|
||||
assumeTrue(getExpectedSupportedKinds().contains(kind));
|
||||
m.build();
|
||||
/**
|
||||
* Verify that the given breakpoint location covers the required range and kind, using the
|
||||
* interpreter
|
||||
*
|
||||
* @param range the requested range of the breakpoint
|
||||
* @param kind the requested kind of the breakpoint
|
||||
* @param loc the location object
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void assertLocCoversViaInterpreter(AddressRange range,
|
||||
TargetBreakpointKind kind, TargetBreakpointLocation loc,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the given spec and/or location is in the given state, using the interpreter
|
||||
*
|
||||
* @param t the spec or location
|
||||
* @param enabled the expected state: true for enabled, false for disabled
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the given spec and/or location no longer exists, using the interpreter
|
||||
*
|
||||
* @param d the spec or location
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the given breakpoint using the interpreter
|
||||
*
|
||||
* @param range the requested range
|
||||
* @param kind the requested kind
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void placeBreakpointViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the given spec and/or location using the interpreter
|
||||
*
|
||||
* @param t the spec and/or location
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the given spec and/or location using the interpreter
|
||||
*
|
||||
* @param t the spec and/or location
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given spec and/or location using the interpreter
|
||||
*
|
||||
* @param d the spec and/or location
|
||||
* @param interpreter the interpreter
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
fail("Unless hasInterpreter is false, the test must implement this method");
|
||||
}
|
||||
|
||||
protected void addMonitor() {
|
||||
var monitor = new DebuggerModelListener() {
|
||||
DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
|
||||
|
||||
|
@ -217,25 +303,56 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
}
|
||||
};
|
||||
m.getModel().addModelListener(monitor.reorderer, true);
|
||||
}
|
||||
|
||||
protected void runTestPlaceBreakpoint(TargetBreakpointKind kind) throws Throwable {
|
||||
assumeTrue(getExpectedSupportedKinds().contains(kind));
|
||||
m.build();
|
||||
|
||||
addMonitor();
|
||||
|
||||
TargetObject target = obtainTarget();
|
||||
TargetBreakpointSpecContainer container = findBreakpointSpecContainer(target.getPath());
|
||||
AddressRange range = getSuitableRangeForBreakpoint(target, kind);
|
||||
waitOn(container.placeBreakpoint(range, Set.of(kind)));
|
||||
retryVoid(() -> {
|
||||
TargetBreakpointLocation loc = retry(() -> {
|
||||
Collection<? extends TargetBreakpointLocation> found =
|
||||
m.findAll(TargetBreakpointLocation.class, target.getPath(), true).values();
|
||||
assertAtLeastOneLocCovers(found, range, kind);
|
||||
return assertAtLeastOneLocCovers(found, range, kind);
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertLocCoversViaInterpreter(range, kind, loc, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runTestPlaceBreakpointViaInterpreter(TargetBreakpointKind kind)
|
||||
throws Throwable {
|
||||
assumeTrue(getExpectedSupportedKinds().contains(kind));
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
addMonitor();
|
||||
|
||||
TargetObject target = obtainTarget();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
AddressRange range = getSuitableRangeForBreakpoint(target, kind);
|
||||
placeBreakpointViaInterpreter(range, kind, interpreter);
|
||||
TargetBreakpointLocation loc = retry(() -> {
|
||||
Collection<? extends TargetBreakpointLocation> found =
|
||||
m.findAll(TargetBreakpointLocation.class, target.getPath(), true).values();
|
||||
return assertAtLeastOneLocCovers(found, range, kind);
|
||||
}, List.of(AssertionError.class));
|
||||
assertLocCoversViaInterpreter(range, kind, loc, interpreter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaceSoftwareBreakpoint() throws Throwable {
|
||||
public void testPlaceSoftwareExecuteBreakpoint() throws Throwable {
|
||||
runTestPlaceBreakpoint(TargetBreakpointKind.SW_EXECUTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaceHardwareBreakpoint() throws Throwable {
|
||||
public void testPlaceHardwareExecuteBreakpoint() throws Throwable {
|
||||
runTestPlaceBreakpoint(TargetBreakpointKind.HW_EXECUTE);
|
||||
}
|
||||
|
||||
|
@ -249,6 +366,26 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
runTestPlaceBreakpoint(TargetBreakpointKind.WRITE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaceSoftwareExecuteBreakpointViaInterpreter() throws Throwable {
|
||||
runTestPlaceBreakpointViaInterpreter(TargetBreakpointKind.SW_EXECUTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaceHardwareExecuteBreakpointViaInterpreter() throws Throwable {
|
||||
runTestPlaceBreakpointViaInterpreter(TargetBreakpointKind.HW_EXECUTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaceReadBreakpointViaInterpreter() throws Throwable {
|
||||
runTestPlaceBreakpointViaInterpreter(TargetBreakpointKind.READ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaceWriteBreakpointViaInterpreter() throws Throwable {
|
||||
runTestPlaceBreakpointViaInterpreter(TargetBreakpointKind.WRITE);
|
||||
}
|
||||
|
||||
protected Set<TargetBreakpointLocation> createLocations() throws Throwable {
|
||||
// TODO: Test with multiple targets?
|
||||
TargetObject target = obtainTarget();
|
||||
|
@ -278,6 +415,10 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
retryVoid(() -> {
|
||||
assertFalse(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertEnabledViaInterpreter(t, false, interpreter);
|
||||
}
|
||||
}
|
||||
// Repeat it for fun. Should have no effect
|
||||
for (TargetTogglable t : order) {
|
||||
|
@ -285,6 +426,10 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
retryVoid(() -> {
|
||||
assertFalse(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertEnabledViaInterpreter(t, false, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable each
|
||||
|
@ -293,6 +438,10 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
retryVoid(() -> {
|
||||
assertTrue(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertEnabledViaInterpreter(t, true, interpreter);
|
||||
}
|
||||
}
|
||||
// Repeat it for fun. Should have no effect
|
||||
for (TargetTogglable t : order) {
|
||||
|
@ -300,6 +449,49 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
retryVoid(() -> {
|
||||
assertTrue(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertEnabledViaInterpreter(t, true, interpreter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void runToggleTestViaInterpreter(Set<TargetTogglable> set,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
List<TargetTogglable> order = new ArrayList<>(set);
|
||||
Collections.shuffle(order);
|
||||
// Disable each
|
||||
for (TargetTogglable t : order) {
|
||||
disableViaInterpreter(t, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertFalse(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
assertEnabledViaInterpreter(t, false, interpreter);
|
||||
}
|
||||
// Repeat it for fun. Should have no effect
|
||||
for (TargetTogglable t : order) {
|
||||
disableViaInterpreter(t, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertFalse(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
assertEnabledViaInterpreter(t, false, interpreter);
|
||||
}
|
||||
|
||||
// Enable each
|
||||
for (TargetTogglable t : order) {
|
||||
enableViaInterpreter(t, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertTrue(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
assertEnabledViaInterpreter(t, true, interpreter);
|
||||
}
|
||||
// Repeat it for fun. Should have no effect
|
||||
for (TargetTogglable t : order) {
|
||||
enableViaInterpreter(t, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertTrue(t.isEnabled());
|
||||
}, List.of(AssertionError.class));
|
||||
assertEnabledViaInterpreter(t, true, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,6 +505,19 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpointsViaInterpreter() throws Throwable {
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
runToggleTestViaInterpreter(locs.stream()
|
||||
.map(l -> l.getSpecification().as(TargetTogglable.class))
|
||||
.collect(Collectors.toSet()),
|
||||
interpreter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpointLocations() throws Throwable {
|
||||
assumeTrue(isSupportsTogglableLocations());
|
||||
|
@ -323,15 +528,46 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
locs.stream().map(l -> l.as(TargetTogglable.class)).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpointLocationsViaInterpreter() throws Throwable {
|
||||
assumeTrue(isSupportsTogglableLocations());
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
runToggleTestViaInterpreter(
|
||||
locs.stream().map(l -> l.as(TargetTogglable.class)).collect(Collectors.toSet()),
|
||||
interpreter);
|
||||
}
|
||||
|
||||
protected void runDeleteTest(Set<TargetDeletable> set) throws Throwable {
|
||||
List<TargetDeletable> order = new ArrayList<>(set);
|
||||
Collections.shuffle(order);
|
||||
// Disable each
|
||||
// Delete each
|
||||
for (TargetDeletable d : order) {
|
||||
waitOn(d.delete());
|
||||
retryVoid(() -> {
|
||||
assertFalse(d.isValid());
|
||||
}, List.of(AssertionError.class));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertDeletedViaInterpreter(d, interpreter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void runDeleteTestViaInterpreter(Set<TargetDeletable> set,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
List<TargetDeletable> order = new ArrayList<>(set);
|
||||
Collections.shuffle(order);
|
||||
// Delete each
|
||||
for (TargetDeletable d : order) {
|
||||
deleteViaInterpreter(d, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertFalse(d.isValid());
|
||||
}, List.of(AssertionError.class));
|
||||
assertDeletedViaInterpreter(d, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,6 +581,19 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpointsViaInterpreter() throws Throwable {
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
runDeleteTestViaInterpreter(locs.stream()
|
||||
.map(l -> l.getSpecification().as(TargetDeletable.class))
|
||||
.collect(Collectors.toSet()),
|
||||
interpreter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpointLocations() throws Throwable {
|
||||
assumeTrue(isSupportsDeletableLocations());
|
||||
|
@ -354,4 +603,17 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
runDeleteTest(
|
||||
locs.stream().map(l -> l.as(TargetDeletable.class)).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpointLocationsViaInterpreter() throws Throwable {
|
||||
assumeTrue(isSupportsDeletableLocations());
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
runDeleteTestViaInterpreter(
|
||||
locs.stream().map(l -> l.as(TargetDeletable.class)).collect(Collectors.toSet()),
|
||||
interpreter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
/**
|
||||
* TODO: Since activation and focus are separate concepts, we need to fix the terminology here and
|
||||
* ensure we're testing the right things.
|
||||
*/
|
||||
public abstract class AbstractDebuggerModelFocusTest extends AbstractDebuggerModelTest {
|
||||
|
||||
/**
|
||||
* Get (possibly generate) things for this focus test to try out
|
||||
*
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected abstract Set<TargetObject> getFocusableThings() throws Throwable;
|
||||
|
||||
/**
|
||||
* Governs whether assertions permit the actual object to be a successor of the expected object
|
||||
*
|
||||
* @return true to permit successors, false to require exact
|
||||
*/
|
||||
protected boolean permitSuccessor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void assertSuccessorOrExact(TargetObject expected, TargetObject actual) {
|
||||
assertNotNull(actual);
|
||||
if (permitSuccessor()) {
|
||||
assertTrue("Expected successor of '" + expected.getJoinedPath(".") +
|
||||
"' got '" + actual.getJoinedPath(".") + "'",
|
||||
PathUtils.isAncestor(expected.getPath(), actual.getPath()));
|
||||
}
|
||||
else {
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the default focus is one of the focusable things (after generation), assert its path
|
||||
*
|
||||
* @return the path of the expected default focus, or {@code null} for no assertion
|
||||
*/
|
||||
protected List<String> getExpectedDefaultFocus() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultFocusIsAsExpected() throws Throwable {
|
||||
List<String> expectedDefaultFocus = getExpectedDefaultFocus();
|
||||
assumeNotNull(expectedDefaultFocus);
|
||||
m.build();
|
||||
|
||||
TargetFocusScope scope = findFocusScope();
|
||||
Set<TargetObject> focusable = getFocusableThings();
|
||||
// The default must be one of the focusable objects
|
||||
assertTrue(focusable.stream()
|
||||
.anyMatch(f -> PathUtils.isAncestor(f.getPath(), expectedDefaultFocus)));
|
||||
retryVoid(() -> {
|
||||
assertEquals(expectedDefaultFocus, scope.getFocus().getPath());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFocusEachOnce() throws Throwable {
|
||||
m.build();
|
||||
|
||||
TargetFocusScope scope = findFocusScope();
|
||||
Set<TargetObject> focusable = getFocusableThings();
|
||||
for (TargetObject obj : focusable) {
|
||||
waitOn(scope.requestFocus(obj));
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, scope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFocusEachTwice() throws Throwable {
|
||||
m.build();
|
||||
|
||||
TargetFocusScope scope = findFocusScope();
|
||||
Set<TargetObject> focusable = getFocusableThings();
|
||||
for (TargetObject obj : focusable) {
|
||||
waitOn(scope.requestFocus(obj));
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, scope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
waitOn(scope.requestFocus(obj));
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, scope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package ghidra.dbg.test;
|
||||
|
||||
import static ghidra.lifecycle.Unfinished.TODO;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
@ -24,7 +23,6 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.async.AsyncReference;
|
||||
|
@ -94,13 +92,26 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
*/
|
||||
protected abstract String getKillCommand(TargetProcess process);
|
||||
|
||||
/**
|
||||
* Perform an pre-test actions to ensure an interpreter exists where expected
|
||||
*
|
||||
* <p>
|
||||
* The model will have been built already. This method is invoked immediately preceding
|
||||
* {@link #findInterpreter()}
|
||||
*
|
||||
* @throws Throwable if anything goes wrong
|
||||
*/
|
||||
protected void ensureInterpreterAvailable() throws Throwable {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterpreterIsWhereExpected() throws Throwable {
|
||||
List<String> expectedInterpreterPath = getExpectedInterpreterPath();
|
||||
assumeNotNull(expectedInterpreterPath);
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = m.find(TargetInterpreter.class, List.of());
|
||||
ensureInterpreterAvailable();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertEquals(expectedInterpreterPath, interpreter.getPath());
|
||||
}
|
||||
|
||||
|
@ -128,7 +139,8 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
assumeNotNull(cmd);
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = m.find(TargetInterpreter.class, List.of());
|
||||
ensureInterpreterAvailable();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
runTestExecute(interpreter, cmd);
|
||||
}
|
||||
|
||||
|
@ -163,7 +175,8 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
assumeNotNull(cmd);
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = m.find(TargetInterpreter.class, List.of());
|
||||
ensureInterpreterAvailable();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
runTestExecuteCapture(interpreter, cmd);
|
||||
}
|
||||
|
||||
|
@ -178,25 +191,11 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
assumeNotNull(cmd);
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = m.find(TargetInterpreter.class, List.of());
|
||||
ensureInterpreterAvailable();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
runTestExecute(interpreter, cmd);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testFocusIsSynced() throws Throwable {
|
||||
TODO();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testBreakpointsAreSynced() throws Throwable {
|
||||
TODO();
|
||||
// TODO: Place different kinds
|
||||
// TODO: Enable/disable
|
||||
// TODO: Delete (spec vs. loc?)
|
||||
}
|
||||
|
||||
protected TargetProcess runTestLaunchViaInterpreterShowsInProcessContainer(
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getLaunchSpecimen();
|
||||
|
@ -220,6 +219,7 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
assumeTrue(m.hasProcessContainer());
|
||||
m.build();
|
||||
|
||||
ensureInterpreterAvailable();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
TargetProcess process = runTestLaunchViaInterpreterShowsInProcessContainer(interpreter);
|
||||
|
||||
|
@ -250,6 +250,7 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
m.build();
|
||||
dummy = specimen.runDummy();
|
||||
|
||||
ensureInterpreterAvailable();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
TargetProcess process = runTestAttachViaInterpreterShowsInProcessContainer(interpreter);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
package ghidra.dbg.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -109,7 +109,7 @@ public abstract class AbstractDebuggerModelSteppableTest extends AbstractDebugge
|
|||
* @return the window in milliseconds
|
||||
*/
|
||||
protected long getDebounceWindowMs() {
|
||||
return 1000;
|
||||
return 5000;
|
||||
}
|
||||
|
||||
enum CallbackType {
|
||||
|
|
|
@ -31,7 +31,7 @@ import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
|||
import ghidra.dbg.target.TargetEventScope.TargetEventType;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.testutil.*;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
|
@ -41,7 +41,7 @@ import ghidra.util.Msg;
|
|||
* <li>TODO: ensure registersUpdated(RegisterBank) immediately upon created(RegisterBank) ?</li>
|
||||
* </ul>
|
||||
*/
|
||||
public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIntegrationTest
|
||||
public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
implements TestDebuggerModelProvider, DebuggerModelTestUtils {
|
||||
|
||||
protected DummyProc dummy;
|
||||
|
@ -56,6 +56,10 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
|
|||
return List.of();
|
||||
}
|
||||
|
||||
protected TargetActiveScope findActiveScope() throws Throwable {
|
||||
return m.find(TargetActiveScope.class, seedPath());
|
||||
}
|
||||
|
||||
protected TargetObject findAttachableContainer() throws Throwable {
|
||||
return m.findContainer(TargetAttachable.class, seedPath());
|
||||
}
|
||||
|
|
|
@ -124,6 +124,11 @@ public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestU
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInterpreter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInterruptibleProcesses() {
|
||||
return true;
|
||||
|
|
|
@ -47,7 +47,7 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
|||
|
||||
public final DebuggerObjectModel model;
|
||||
public Thread thread = null;
|
||||
public Set<TargetObject> valid = new HashSet<>();
|
||||
public Map<TargetObject, TargetObject> valid = new HashMap<>();
|
||||
|
||||
// Knobs
|
||||
// TODO: Make these methods instead?
|
||||
|
@ -85,12 +85,8 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
|||
thread = Thread.currentThread();
|
||||
}
|
||||
if (requireSameThread) {
|
||||
if (thread != Thread.currentThread()) {
|
||||
throw new AssertionError("Completion came from an unexpected thread expected:" +
|
||||
thread + " but got " + Thread.currentThread());
|
||||
}
|
||||
assertEquals("Completion came from an unexpected thread", thread,
|
||||
Thread.currentThread());
|
||||
assertEquals("Completion came from an unexpected thread. Probably forgot gateFuture()",
|
||||
thread, Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,14 +109,14 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
|||
public void validateObjectValid(String callback, TargetObject obj) {
|
||||
if (requireValid) {
|
||||
assertTrue("Object " + obj.getJoinedPath(".") + " invalid during callback " + callback,
|
||||
valid.contains(obj));
|
||||
valid.containsKey(obj));
|
||||
}
|
||||
}
|
||||
|
||||
public void validateObjectInvalid(String callback, TargetObject obj) {
|
||||
if (requireValid) {
|
||||
assertFalse("Object " + obj.getJoinedPath(".") + " valid during callback " + callback +
|
||||
", but should have been invalid", valid.contains(obj));
|
||||
", but should have been invalid", valid.containsKey(obj));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,8 +256,16 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
|||
if (log) {
|
||||
Msg.info(this, "created(object=" + object + ")");
|
||||
}
|
||||
valid.add(object);
|
||||
TargetObject exists = valid.put(object, object);
|
||||
off.catching(() -> {
|
||||
if (exists != null) {
|
||||
if (exists == object) {
|
||||
fail("created twice (same object): " + object.getJoinedPath("."));
|
||||
}
|
||||
else {
|
||||
fail("replaced before invalidation. old= " + exists + ", new=" + object);
|
||||
}
|
||||
}
|
||||
validateCallbackThread("created");
|
||||
validateObject("created", object);
|
||||
});
|
||||
|
|
|
@ -23,15 +23,19 @@ import java.util.Map.Entry;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.async.AsyncReference;
|
||||
import ghidra.async.AsyncTestUtils;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.async.*;
|
||||
import ghidra.dbg.*;
|
||||
import ghidra.dbg.DebugModelConventions.AsyncAccess;
|
||||
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetConsole.Channel;
|
||||
import ghidra.dbg.target.TargetEventScope.TargetEventType;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelTest;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelTest.DebuggerTestSpecimen;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public interface DebuggerModelTestUtils extends AsyncTestUtils {
|
||||
|
@ -204,4 +208,78 @@ public interface DebuggerModelTestUtils extends AsyncTestUtils {
|
|||
return process;
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
default void waitSettled(DebuggerObjectModel model, int ms) throws Throwable {
|
||||
AsyncDebouncer<Void> debouncer = new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, ms);
|
||||
var listener = new DebuggerModelListener() {
|
||||
@Override
|
||||
public void attributesChanged(TargetObject object, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void breakpointHit(TargetObject container, TargetObject trapped,
|
||||
TargetStackFrame frame, TargetBreakpointSpec spec,
|
||||
TargetBreakpointLocation breakpoint) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleOutput(TargetObject console, Channel channel, byte[] data) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(TargetObject object) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsChanged(TargetObject object, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
|
||||
String description, List<Object> parameters) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCacheRequested(TargetObject object) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryReadError(TargetObject memory, AddressRange range,
|
||||
DebuggerMemoryAccessException e) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rootAdded(TargetObject root) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
};
|
||||
model.addModelListener(listener);
|
||||
debouncer.contact(null);
|
||||
waitOnNoValidate(debouncer.settled());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ public interface TestDebuggerModelProvider {
|
|||
|
||||
boolean hasDetachableProcesses();
|
||||
|
||||
boolean hasInterpreter();
|
||||
|
||||
boolean hasInterruptibleProcesses();
|
||||
|
||||
boolean hasKillableProcesses();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue