mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 12:00:04 +02:00
GP-857: Added interpreter-based tests for activation, breakpoints.
This commit is contained in:
parent
4d65ccd052
commit
a61c2e1400
55 changed files with 1212 additions and 357 deletions
|
@ -15,21 +15,25 @@
|
|||
*/
|
||||
package agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.test.*;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public abstract class AbstractModelForDbgengBreakpointsTest
|
||||
extends AbstractDebuggerModelBreakpointsTest implements ProvidesTargetViaLaunchSpecimen {
|
||||
|
||||
private static final PathPattern BREAK_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Sessions[0].Processes[].Debug.Breakpoints[]"));
|
||||
private static final int BREAK_ID_POS = 1;
|
||||
|
||||
@Override
|
||||
public AbstractDebuggerModelTest getTest() {
|
||||
return this;
|
||||
|
@ -80,4 +84,88 @@ public abstract class AbstractModelForDbgengBreakpointsTest
|
|||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void placeBreakpointViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
Address min = range.getMinAddress();
|
||||
if (range.getLength() == 4) {
|
||||
switch (kind) {
|
||||
case READ:
|
||||
waitOn(interpreter.execute("ba r4 " + min));
|
||||
break;
|
||||
case WRITE:
|
||||
waitOn(interpreter.execute("ba w4 " + min));
|
||||
break;
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
else if (range.getLength() == 1) {
|
||||
switch (kind) {
|
||||
case SW_EXECUTE:
|
||||
waitOn(interpreter.execute("bp " + min));
|
||||
break;
|
||||
case HW_EXECUTE:
|
||||
waitOn(interpreter.execute("ba e1 " + min));
|
||||
break;
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = BREAK_PATTERN.matchIndices(t.getPath()).get(BREAK_ID_POS);
|
||||
waitOn(interpreter.execute("bd " + bpId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = BREAK_PATTERN.matchIndices(t.getPath()).get(BREAK_ID_POS);
|
||||
waitOn(interpreter.execute("be " + bpId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = BREAK_PATTERN.matchIndices(d.getPath()).get(BREAK_ID_POS);
|
||||
waitOn(interpreter.execute("bc " + bpId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertLocCoversViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetBreakpointLocation loc, TargetInterpreter interpreter) throws Throwable {
|
||||
String bpId = BREAK_PATTERN.matchIndices(loc.getPath()).get(BREAK_ID_POS);
|
||||
String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
// NB. WinDbg numbers breakpoints in base 10, by default
|
||||
assertTrue(line.startsWith(bpId));
|
||||
// TODO: Do I care to parse the details? The ID is confirmed, and details via the object...
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
String bpId = BREAK_PATTERN.matchIndices(t.getPath()).get(BREAK_ID_POS);
|
||||
String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
assertTrue(line.startsWith(bpId));
|
||||
String e = line.split("\\s+")[1];
|
||||
assertEquals(enabled ? "e" : "d", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = BREAK_PATTERN.matchIndices(d.getPath()).get(BREAK_ID_POS);
|
||||
String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim();
|
||||
assertEquals("", line);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,23 +15,29 @@
|
|||
*/
|
||||
package agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import org.junit.Ignore;
|
||||
|
||||
public abstract class AbstractModelForDbgengFrameFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForDbgengFrameActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
private static final PathPattern STACK_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Sessions[0].Processes[].Threads[].Stack.Frames[]"));
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.STACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
|
@ -44,6 +50,8 @@ public abstract class AbstractModelForDbgengFrameFocusTest
|
|||
|
||||
trapAt("expStack!break_here", process);
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetStackFrame> frames =
|
||||
m.findAll(TargetStackFrame.class, seedPath(), true);
|
||||
|
@ -51,4 +59,21 @@ public abstract class AbstractModelForDbgengFrameFocusTest
|
|||
return Set.copyOf(frames.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
// TODO: Should probably assert default focus/activation here
|
||||
|
||||
@Override
|
||||
@Ignore("dbgeng.dll has no event for frame activation")
|
||||
public void testActivateEachViaInterpreter() throws Throwable {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String line = waitOn(interpreter.executeCapture(".frame")).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
int frameId = Integer.parseInt(line.split("\\s+")[0], 16);
|
||||
int expId = Integer.parseInt(STACK_PATTERN.matchIndices(expected.getPath()).get(2), 16);
|
||||
assertEquals(expId, frameId);
|
||||
}
|
||||
}
|
|
@ -18,13 +18,20 @@ package agent.dbgeng.model;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForDbgengProcessFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
public abstract class AbstractModelForDbgengProcessActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
private static final PathPattern PROCESS_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Sessions[0].Processes[]"));
|
||||
|
||||
protected int getCount() {
|
||||
return 3;
|
||||
|
@ -35,13 +42,16 @@ public abstract class AbstractModelForDbgengProcessFocusTest
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetProcess> found =
|
||||
m.findAll(TargetProcess.class, PathUtils.parse("Sessions[0]"), true);
|
||||
|
@ -49,4 +59,22 @@ public abstract class AbstractModelForDbgengProcessFocusTest
|
|||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String id = Unique.assertOne(PROCESS_PATTERN.matchIndices(obj.getPath()));
|
||||
waitOn(interpreter.execute("|" + id + "s"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String output = waitOn(interpreter.executeCapture("|"));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> l.trim().startsWith("."))
|
||||
.collect(Collectors.toList())).trim();
|
||||
String procId = line.split("\\s+")[1];
|
||||
assertEquals(expected.getPath(), PROCESS_PATTERN.applyIndices(procId).getSingletonPath());
|
||||
}
|
||||
}
|
|
@ -20,13 +20,13 @@ import static ghidra.lifecycle.Unfinished.TODO;
|
|||
import java.util.Set;
|
||||
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
|
||||
public abstract class AbstractModelForDbgengSessionFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
public abstract class AbstractModelForDbgengSessionActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
TODO("Don't know how to make multiple sessions");
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/* ###
|
||||
* 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 agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForDbgengThreadActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
private static final PathPattern THREAD_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Sessions[0].Processes[].Threads[]"));
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
protected int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetThread> found =
|
||||
m.findAll(TargetThread.class, PathUtils.parse("Sessions[0]"), true);
|
||||
assertEquals(count, found.size());
|
||||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String threadId = THREAD_PATTERN.matchIndices(obj.getPath()).get(1);
|
||||
// TODO: This test is imperfect, since processes are activated as well
|
||||
waitOn(interpreter.execute("~" + threadId + "s"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String output = waitOn(interpreter.executeCapture("~"));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> l.trim().startsWith("."))
|
||||
.collect(Collectors.toList())).trim();
|
||||
int threadId = Integer.parseInt(line.split("\\s+")[1]); // dbgeng TIDs are base 10
|
||||
int expId = Integer.parseInt(THREAD_PATTERN.matchIndices(expected.getPath()).get(1));
|
||||
assertEquals(expId, threadId);
|
||||
}
|
||||
}
|
|
@ -1,52 +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 agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForDbgengThreadFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
|
||||
protected int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetThread> found =
|
||||
m.findAll(TargetThread.class, PathUtils.parse("Sessions[0]"), true);
|
||||
assertEquals(count, found.size());
|
||||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
}
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameActivationTest;
|
||||
|
||||
public class GadpModelForDbgengFrameFocusTest extends AbstractModelForDbgengFrameFocusTest {
|
||||
public class GadpModelForDbgengFrameFocusTest extends AbstractModelForDbgengFrameActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessActivationTest;
|
||||
|
||||
public class GadpModelForDbgengProcessFocusTest extends AbstractModelForDbgengProcessFocusTest {
|
||||
public class GadpModelForDbgengProcessFocusTest extends AbstractModelForDbgengProcessActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
|
|
|
@ -17,10 +17,10 @@ package agent.dbgeng.model.gadp;
|
|||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||
|
||||
@Ignore("Don't know how to make multiple sessions")
|
||||
public class GadpModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
||||
public class GadpModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadActivationTest;
|
||||
|
||||
public class GadpModelForDbgengThreadFocusTest extends AbstractModelForDbgengThreadFocusTest {
|
||||
public class GadpModelForDbgengThreadFocusTest extends AbstractModelForDbgengThreadActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgeng.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameActivationTest;
|
||||
|
||||
public class InVmModelForDbgengFrameFocusTest extends AbstractModelForDbgengFrameFocusTest {
|
||||
public class InVmModelForDbgengFrameFocusTest extends AbstractModelForDbgengFrameActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgeng.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessActivationTest;
|
||||
|
||||
public class InVmModelForDbgengProcessFocusTest extends AbstractModelForDbgengProcessFocusTest {
|
||||
public class InVmModelForDbgengProcessFocusTest extends AbstractModelForDbgengProcessActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
|
|
|
@ -17,10 +17,10 @@ package agent.dbgeng.model.invm;
|
|||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||
|
||||
@Ignore("Don't know how to make multiple sessions")
|
||||
public class InVmModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
||||
public class InVmModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgeng.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadActivationTest;
|
||||
|
||||
public class InVmModelForDbgengThreadFocusTest extends AbstractModelForDbgengThreadFocusTest {
|
||||
public class InVmModelForDbgengThreadFocusTest extends AbstractModelForDbgengThreadActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameActivationTest;
|
||||
|
||||
public class InVmModelForDbgmodelFrameFocusTest extends AbstractModelForDbgengFrameFocusTest {
|
||||
public class InVmModelForDbgmodelFrameFocusTest extends AbstractModelForDbgengFrameActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessActivationTest;
|
||||
|
||||
public class InVmModelForDbgmodelProcessFocusTest extends AbstractModelForDbgengProcessFocusTest {
|
||||
public class InVmModelForDbgmodelProcessFocusTest extends AbstractModelForDbgengProcessActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
|
|
|
@ -17,10 +17,10 @@ package agent.dbgmodel.model.invm;
|
|||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||
|
||||
@Ignore("Don't know how to make multiple sessions")
|
||||
public class InVmModelForDbgmodelSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
||||
public class InVmModelForDbgmodelSessionFocusTest extends AbstractModelForDbgengSessionActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadActivationTest;
|
||||
|
||||
public class InVmModelForDbgmodelThreadFocusTest extends AbstractModelForDbgengThreadFocusTest {
|
||||
public class InVmModelForDbgmodelThreadFocusTest extends AbstractModelForDbgengThreadActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
|
|
|
@ -152,9 +152,10 @@ public interface GdbInferior extends GdbMemoryOperations {
|
|||
* will apply to this inferior. Commands issued from this handle are always executed with this
|
||||
* inferior in focus, so it is rare to invoke his method directly.
|
||||
*
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when GDB has executed the command
|
||||
*/
|
||||
CompletableFuture<Void> setActive();
|
||||
CompletableFuture<Void> setActive(boolean internal);
|
||||
|
||||
/**
|
||||
* Specify a binary image for execution and debug symbols
|
||||
|
|
|
@ -47,9 +47,10 @@ public interface GdbStackFrame extends GdbStackFrameOperations {
|
|||
/**
|
||||
* Make this frame the current frame
|
||||
*
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when the frame is the current frame
|
||||
*/
|
||||
CompletableFuture<Void> setActive();
|
||||
CompletableFuture<Void> setActive(boolean internal);
|
||||
|
||||
/**
|
||||
* Get the thread for this frame
|
||||
|
|
|
@ -68,9 +68,10 @@ public interface GdbThread
|
|||
/**
|
||||
* Make this thread the current thread
|
||||
*
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when the thread is the current thread
|
||||
*/
|
||||
CompletableFuture<Void> setActive();
|
||||
CompletableFuture<Void> setActive(boolean internal);
|
||||
|
||||
/**
|
||||
* Set the value of an internal GDB variable
|
||||
|
|
|
@ -67,7 +67,7 @@ public interface GdbCommand<T> {
|
|||
* <p>
|
||||
* Complete {@code} pending with a result to short-circuit the execution of this command.
|
||||
*
|
||||
* @param pending the pending command result
|
||||
* @param pending the pend@Override ing command result
|
||||
*/
|
||||
void preCheck(GdbPendingCommand<? super T> pending);
|
||||
|
||||
|
@ -92,6 +92,13 @@ public interface GdbCommand<T> {
|
|||
*/
|
||||
public Integer impliesCurrentFrameId();
|
||||
|
||||
/**
|
||||
* Check if focus announcements from this command should be suppressed
|
||||
*
|
||||
* @return true to suppress announcements
|
||||
*/
|
||||
public boolean isFocusInternallyDriven();
|
||||
|
||||
/**
|
||||
* Handle an event that occurred during the execution of this command
|
||||
*
|
||||
|
|
|
@ -208,7 +208,7 @@ public class GdbInferiorImpl implements GdbInferior {
|
|||
* longer be current for the actual command execution. NB: The select command will cancel
|
||||
* itself if this inferior is already current.
|
||||
*/
|
||||
return setActive().thenCombine(manager.execute(cmd), (s, e) -> e);
|
||||
return setActive(true).thenCombine(manager.execute(cmd), (s, e) -> e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -347,8 +347,8 @@ public class GdbInferiorImpl implements GdbInferior {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return manager.setActiveInferior(this);
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
return manager.setActiveInferior(this, internal);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -961,7 +961,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()),
|
||||
"groupRemoved-sel");
|
||||
// Also cause GDB to generate thread selection events, if applicable
|
||||
setActiveInferior(cur);
|
||||
setActiveInferior(cur, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1527,10 +1527,11 @@ public class GdbManagerImpl implements GdbManager {
|
|||
* This issues a command to GDB to change its focus. It is not just a manager concept.
|
||||
*
|
||||
* @param inferior the inferior to select
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when GDB has executed the command
|
||||
*/
|
||||
CompletableFuture<Void> setActiveInferior(GdbInferior inferior) {
|
||||
return execute(new GdbInferiorSelectCommand(this, inferior.getId()));
|
||||
CompletableFuture<Void> setActiveInferior(GdbInferior inferior, boolean internal) {
|
||||
return execute(new GdbInferiorSelectCommand(this, inferior.getId(), internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -104,8 +104,9 @@ public class GdbStackFrameImpl implements GdbStackFrame {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level));
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
return manager
|
||||
.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level, internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -128,7 +128,7 @@ public class GdbThreadImpl implements GdbThread {
|
|||
protected <T> CompletableFuture<T> execute(AbstractGdbCommand<T> cmd) {
|
||||
switch (cmd.getInterpreter()) {
|
||||
case CLI:
|
||||
return setActive().thenCombine(manager.execute(cmd), (__, v) -> v);
|
||||
return setActive(true).thenCombine(manager.execute(cmd), (__, v) -> v);
|
||||
case MI2:
|
||||
return manager.execute(cmd);
|
||||
default:
|
||||
|
@ -137,9 +137,9 @@ public class GdbThreadImpl implements GdbThread {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
// Bypass the select-me-first logic
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null));
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null, internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -106,4 +106,9 @@ public abstract class AbstractGdbCommand<T> implements GdbCommand<T> {
|
|||
public Integer impliesCurrentFrameId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,4 +78,9 @@ public class GdbConsoleExecCommand extends AbstractGdbCommandWithThreadAndFrameI
|
|||
public Output getOutputTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return to == Output.CAPTURE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,12 @@ import agent.gdb.manager.impl.*;
|
|||
|
||||
public class GdbInferiorSelectCommand extends AbstractGdbCommand<Void> {
|
||||
private final int id;
|
||||
private final boolean internal;
|
||||
|
||||
public GdbInferiorSelectCommand(GdbManagerImpl manager, int id) {
|
||||
public GdbInferiorSelectCommand(GdbManagerImpl manager, int id, boolean internal) {
|
||||
super(manager);
|
||||
this.id = id;
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,4 +60,9 @@ public class GdbInferiorSelectCommand extends AbstractGdbCommand<Void> {
|
|||
pending.checkCompletion(GdbCommandDoneEvent.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return internal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import agent.gdb.manager.impl.*;
|
|||
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
||||
|
||||
public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFrameId<Void> {
|
||||
private final boolean internal;
|
||||
|
||||
/**
|
||||
* Select the given thread and frame level
|
||||
*
|
||||
|
@ -29,9 +31,12 @@ public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFr
|
|||
* @param manager the manager to execute the command
|
||||
* @param threadId the desired thread Id
|
||||
* @param frameId the desired frame level
|
||||
* @param internal true to prevent announcement of the change
|
||||
*/
|
||||
public GdbSetActiveThreadCommand(GdbManagerImpl manager, int threadId, Integer frameId) {
|
||||
public GdbSetActiveThreadCommand(GdbManagerImpl manager, int threadId, Integer frameId,
|
||||
boolean internal) {
|
||||
super(manager, threadId, frameId);
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,4 +78,9 @@ public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFr
|
|||
manager.doThreadSelected(thread, frame, done.getCause());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return internal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -375,7 +375,7 @@ public class GdbModelTargetInferior
|
|||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return impl.gateFuture(inferior.setActive());
|
||||
return impl.gateFuture(inferior.setActive(false));
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = EXIT_CODE_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -21,8 +21,6 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import agent.gdb.manager.*;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand;
|
||||
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.Output;
|
||||
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
|
||||
import agent.gdb.manager.reason.GdbReason;
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
@ -150,6 +148,13 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
|||
// Otherwise, we'll presumably get the =thread-selected event
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This check should be done in the manager? This "internal" concept is either a manager
|
||||
* concept or a model concept. Right now, it breaches the interface.
|
||||
*
|
||||
* @param cause the cause to examine
|
||||
* @return true if internal
|
||||
*/
|
||||
protected boolean isFocusInternallyDriven(GdbCause cause) {
|
||||
if (cause == null || cause == GdbCause.Causes.UNCLAIMED) {
|
||||
return false;
|
||||
|
@ -160,13 +165,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
|||
if (cause instanceof GdbPendingCommand<?>) {
|
||||
GdbPendingCommand<?> pcmd = (GdbPendingCommand<?>) cause;
|
||||
GdbCommand<?> cmd = pcmd.getCommand();
|
||||
if (cmd instanceof GdbConsoleExecCommand) {
|
||||
GdbConsoleExecCommand exec = (GdbConsoleExecCommand) cmd;
|
||||
if (exec.getOutputTo() == Output.CAPTURE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return cmd.isFocusInternallyDriven();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -323,7 +322,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
|||
if (impl.gdb.getKnownThreads().get(thread.getId()) != thread) {
|
||||
return;
|
||||
}
|
||||
thread.setActive().exceptionally(ex -> {
|
||||
thread.setActive(true).exceptionally(ex -> {
|
||||
impl.reportError(this, "Could not restore event thread", ex);
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -115,7 +115,7 @@ public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject,
|
|||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return impl.gateFuture(frame.setActive());
|
||||
return impl.gateFuture(frame.setActive(false));
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -226,7 +226,7 @@ public class GdbModelTargetThread
|
|||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return impl.gateFuture(thread.setActive());
|
||||
return impl.gateFuture(thread.setActive(false));
|
||||
}
|
||||
|
||||
public GdbModelTargetBreakpointLocation breakpointHit(GdbBreakpointHitReason reason) {
|
||||
|
|
|
@ -412,7 +412,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
//waitOn(mgr.waitForPrompt());
|
||||
waitOn(thread.setActive());
|
||||
waitOn(thread.setActive(false));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,21 +15,27 @@
|
|||
*/
|
||||
package agent.gdb.model;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.test.*;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public abstract class AbstractModelForGdbBreakpointsTest
|
||||
extends AbstractDebuggerModelBreakpointsTest implements ProvidesTargetViaLaunchSpecimen {
|
||||
|
||||
private static final PathPattern BREAK_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Breakpoints[]"));
|
||||
|
||||
@Override
|
||||
public AbstractDebuggerModelTest getTest() {
|
||||
return this;
|
||||
|
@ -75,4 +81,97 @@ public abstract class AbstractModelForGdbBreakpointsTest
|
|||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void placeBreakpointViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
Address min = range.getMinAddress();
|
||||
if (range.getLength() == 4) {
|
||||
switch (kind) {
|
||||
case READ:
|
||||
waitOn(interpreter.execute("rwatch -l *((int*) 0x" + min + ")"));
|
||||
break;
|
||||
case WRITE:
|
||||
waitOn(interpreter.execute("watch -l *((int*) 0x" + min + ")"));
|
||||
break;
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
else if (range.getLength() == 1) {
|
||||
switch (kind) {
|
||||
case SW_EXECUTE:
|
||||
waitOn(interpreter.execute("break *0x" + min));
|
||||
break;
|
||||
case HW_EXECUTE:
|
||||
waitOn(interpreter.execute("hbreak *0x" + min));
|
||||
break;
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
assert t instanceof TargetBreakpointSpec; // TODO: or Location
|
||||
String index = Unique.assertOne(BREAK_PATTERN.matchIndices(t.getPath()));
|
||||
waitOn(interpreter.execute("disable " + index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
assert t instanceof TargetBreakpointSpec; // TODO: or Location
|
||||
String index = Unique.assertOne(BREAK_PATTERN.matchIndices(t.getPath()));
|
||||
waitOn(interpreter.execute("enable " + index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
assert d instanceof TargetBreakpointSpec; // TODO: or Location
|
||||
String index = Unique.assertOne(BREAK_PATTERN.matchIndices(d.getPath()));
|
||||
waitOn(interpreter.execute("delete " + index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertLocCoversViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetBreakpointLocation loc, TargetInterpreter interpreter) throws Throwable {
|
||||
String index =
|
||||
Unique.assertOne(BREAK_PATTERN.matchIndices(loc.getSpecification().getPath()));
|
||||
String output = waitOn(interpreter.executeCapture("info break " + index));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> !l.trim().startsWith("Num"))
|
||||
.collect(Collectors.toList())).trim();
|
||||
assertTrue(line.startsWith(index));
|
||||
// TODO: Do I care to parse the details? The ID is confirmed, and details via the object...
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
assert t instanceof TargetBreakpointSpec; // TODO: or Location
|
||||
String index = Unique.assertOne(BREAK_PATTERN.matchIndices(t.getPath()));
|
||||
String output = waitOn(interpreter.executeCapture("info break " + index));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> !l.trim().startsWith("Num"))
|
||||
.collect(Collectors.toList())).trim();
|
||||
assertTrue(line.startsWith(index));
|
||||
String enb = line.split("keep")[1].trim().split("\\s+")[0];
|
||||
assertEquals(enabled ? "y" : "n", enb);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
assert d instanceof TargetBreakpointSpec; // TODO: or Location
|
||||
String index = Unique.assertOne(BREAK_PATTERN.matchIndices(d.getPath()));
|
||||
String output = waitOn(interpreter.executeCapture("info break " + index));
|
||||
assertTrue(output.contains("No breakpoint"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,23 +15,31 @@
|
|||
*/
|
||||
package agent.gdb.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForGdbFrameFocusTest extends AbstractDebuggerModelFocusTest {
|
||||
public abstract class AbstractModelForGdbFrameActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
private static final PathPattern STACK_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Inferiors[1].Threads[1].Stack[]"));
|
||||
|
||||
DebuggerTestSpecimen getSpecimen() {
|
||||
return GdbLinuxSpecimen.STACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
CompletableFuture<?> frame0 =
|
||||
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1].Threads[1].Stack[0]"));
|
||||
CompletableFuture<?> frame1 =
|
||||
|
@ -48,6 +56,8 @@ public abstract class AbstractModelForGdbFrameFocusTest extends AbstractDebugger
|
|||
(TargetResumable) waitOn(m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1]")));
|
||||
waitOn(inf.resume());
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return Set.of(
|
||||
(TargetObject) waitOn(frame0),
|
||||
(TargetObject) waitOn(frame1),
|
||||
|
@ -55,7 +65,24 @@ public abstract class AbstractModelForGdbFrameFocusTest extends AbstractDebugger
|
|||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getExpectedDefaultFocus() {
|
||||
protected List<String> getExpectedDefaultActivePath() {
|
||||
return PathUtils.parse("Inferiors[1].Threads[1].Stack[0]");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String index = Unique.assertOne(STACK_PATTERN.matchIndices(obj.getPath()));
|
||||
waitOn(interpreter.execute("frame " + index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String line = waitOn(interpreter.executeCapture("frame")).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
assertTrue(line.startsWith("#"));
|
||||
String frameLevel = line.substring(1).split("\\s+")[0];
|
||||
assertEquals(expected.getPath(), STACK_PATTERN.applyIndices(frameLevel).getSingletonPath());
|
||||
}
|
||||
}
|
|
@ -15,19 +15,28 @@
|
|||
*/
|
||||
package agent.gdb.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForGdbInferiorFocusTest extends AbstractDebuggerModelFocusTest {
|
||||
public abstract class AbstractModelForGdbInferiorActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
private static final PathPattern INF_PATTERN = new PathPattern(PathUtils.parse("Inferiors[]"));;
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
CompletableFuture<?> inf1 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1]"));
|
||||
CompletableFuture<?> inf2 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[2]"));
|
||||
CompletableFuture<?> inf3 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[3]"));
|
||||
|
@ -37,6 +46,8 @@ public abstract class AbstractModelForGdbInferiorFocusTest extends AbstractDebug
|
|||
waitOn(interpreter.execute("add-inferior"));
|
||||
waitOn(interpreter.execute("add-inferior"));
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return Set.of(
|
||||
(TargetObject) waitOn(inf1),
|
||||
(TargetObject) waitOn(inf2),
|
||||
|
@ -44,7 +55,25 @@ public abstract class AbstractModelForGdbInferiorFocusTest extends AbstractDebug
|
|||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getExpectedDefaultFocus() {
|
||||
protected List<String> getExpectedDefaultActivePath() {
|
||||
return PathUtils.parse("Inferiors[1]");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String index = Unique.assertOne(INF_PATTERN.matchIndices(obj.getPath()));
|
||||
waitOn(interpreter.execute("inferior " + index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String output = waitOn(interpreter.executeCapture("info inferiors"));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> l.trim().startsWith("*"))
|
||||
.collect(Collectors.toList())).trim();
|
||||
String inferiorId = line.split("\\s+")[1];
|
||||
assertEquals(expected.getPath(), INF_PATTERN.applyIndices(inferiorId).getSingletonPath());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForGdbThreadActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
private static final PathPattern THREAD_PATTERN =
|
||||
new PathPattern(PathUtils.parse("Inferiors[].Threads[]"));
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return GdbLinuxSpecimen.ECHO_HW;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
/**
|
||||
* TODO: GDB should really use the 1.1 and 2.1 numbering instead of the GId, but I don't
|
||||
* know a good way via GDB/MI to obtain the thread's per-inferior Id.
|
||||
*
|
||||
* NB: A lot of the test takes advantage of the iid and tid being the same. Don't try to
|
||||
* apply the pattern matching used here in other contexts.
|
||||
*/
|
||||
CompletableFuture<?> inf1 =
|
||||
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1].Threads[1]"));
|
||||
CompletableFuture<?> inf2 =
|
||||
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[2].Threads[2]"));
|
||||
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return Set.of(
|
||||
(TargetObject) waitOn(inf1),
|
||||
(TargetObject) waitOn(inf2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getExpectedDefaultActivePath() {
|
||||
return PathUtils.parse("Inferiors[2].Threads[2].Stack[0]");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String index = Unique.assertOne(Set.copyOf(THREAD_PATTERN.matchIndices(obj.getPath())));
|
||||
// TODO: This test is imperfect, since inferiors are activated as well
|
||||
waitOn(interpreter.execute("thread " + index + ".1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String output = waitOn(interpreter.executeCapture("info threads -gid"));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> l.trim().startsWith("*"))
|
||||
.collect(Collectors.toList()));
|
||||
String threadGid = line.split("\\s+")[2];
|
||||
assertEquals(expected.getPath(),
|
||||
THREAD_PATTERN.applyIndices(threadGid, threadGid).getSingletonPath());
|
||||
}
|
||||
}
|
|
@ -1,54 +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 agent.gdb.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.dbg.target.TargetLauncher;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForGdbThreadFocusTest extends AbstractDebuggerModelFocusTest {
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return GdbLinuxSpecimen.ECHO_HW;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
CompletableFuture<?> inf1 =
|
||||
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1].Threads[1]"));
|
||||
CompletableFuture<?> inf2 =
|
||||
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[2].Threads[2]"));
|
||||
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
|
||||
return Set.of(
|
||||
(TargetObject) waitOn(inf1),
|
||||
(TargetObject) waitOn(inf2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getExpectedDefaultFocus() {
|
||||
return PathUtils.parse("Inferiors[2].Threads[2].Stack[0]");
|
||||
}
|
||||
}
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.gdb.model.invm;
|
||||
|
||||
import agent.gdb.model.AbstractModelForGdbFrameFocusTest;
|
||||
import agent.gdb.model.AbstractModelForGdbFrameActivationTest;
|
||||
|
||||
public class InVmModelForGdbFrameFocusTest extends AbstractModelForGdbFrameFocusTest {
|
||||
public class InVmModelForGdbFrameFocusTest extends AbstractModelForGdbFrameActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmGdbModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.gdb.model.invm;
|
||||
|
||||
import agent.gdb.model.AbstractModelForGdbInferiorFocusTest;
|
||||
import agent.gdb.model.AbstractModelForGdbInferiorActivationTest;
|
||||
|
||||
public class InVmModelForGdbInferiorFocusTest extends AbstractModelForGdbInferiorFocusTest {
|
||||
public class InVmModelForGdbInferiorFocusTest extends AbstractModelForGdbInferiorActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmGdbModelHost();
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package agent.gdb.model.invm;
|
||||
|
||||
import agent.gdb.model.AbstractModelForGdbThreadFocusTest;
|
||||
import agent.gdb.model.AbstractModelForGdbThreadActivationTest;
|
||||
|
||||
public class InVmModelForGdbThreadFocusTest extends AbstractModelForGdbThreadFocusTest {
|
||||
public class InVmModelForGdbThreadFocusTest extends AbstractModelForGdbThreadActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmGdbModelHost();
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.assumeNotNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
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();
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
for (TargetObject obj : activatable) {
|
||||
activateViaInterpreter(obj, interpreter);
|
||||
retryVoid(() -> {
|
||||
assertSuccessorOrExact(obj, focusScope.getFocus());
|
||||
}, List.of(AssertionError.class));
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -162,10 +162,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 +304,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 +367,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 +416,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 +427,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 +439,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 +450,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 +506,19 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpointsViaInterpreter() throws Throwable {
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
runToggleTestViaInterpreter(locs.stream()
|
||||
.map(l -> l.getSpecification().as(TargetTogglable.class))
|
||||
.collect(Collectors.toSet()),
|
||||
interpreter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpointLocations() throws Throwable {
|
||||
assumeTrue(isSupportsTogglableLocations());
|
||||
|
@ -323,15 +529,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();
|
||||
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
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 +582,19 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
|||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpointsViaInterpreter() throws Throwable {
|
||||
assumeTrue(m.hasInterpreter());
|
||||
m.build();
|
||||
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
Set<TargetBreakpointLocation> locs = createLocations();
|
||||
runDeleteTestViaInterpreter(locs.stream()
|
||||
.map(l -> l.getSpecification().as(TargetDeletable.class))
|
||||
.collect(Collectors.toSet()),
|
||||
interpreter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpointLocations() throws Throwable {
|
||||
assumeTrue(isSupportsDeletableLocations());
|
||||
|
@ -354,4 +604,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;
|
||||
|
@ -182,21 +180,6 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
|||
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();
|
||||
|
|
|
@ -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