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;
|
package agent.dbgeng.model;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||||
import ghidra.dbg.target.TargetObject;
|
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
|
||||||
import ghidra.dbg.test.*;
|
import ghidra.dbg.test.*;
|
||||||
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
|
||||||
public abstract class AbstractModelForDbgengBreakpointsTest
|
public abstract class AbstractModelForDbgengBreakpointsTest
|
||||||
extends AbstractDebuggerModelBreakpointsTest implements ProvidesTargetViaLaunchSpecimen {
|
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
|
@Override
|
||||||
public AbstractDebuggerModelTest getTest() {
|
public AbstractDebuggerModelTest getTest() {
|
||||||
return this;
|
return this;
|
||||||
|
@ -80,4 +84,88 @@ public abstract class AbstractModelForDbgengBreakpointsTest
|
||||||
throw new AssertionError();
|
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;
|
package agent.dbgeng.model;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.dbg.target.*;
|
import org.junit.Ignore;
|
||||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
|
||||||
|
|
||||||
public abstract class AbstractModelForDbgengFrameFocusTest
|
import ghidra.dbg.target.*;
|
||||||
extends AbstractDebuggerModelFocusTest {
|
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() {
|
protected DebuggerTestSpecimen getSpecimen() {
|
||||||
return WindowsSpecimen.STACK;
|
return WindowsSpecimen.STACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||||
DebuggerTestSpecimen specimen = getSpecimen();
|
DebuggerTestSpecimen specimen = getSpecimen();
|
||||||
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
||||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||||
|
@ -44,6 +50,8 @@ public abstract class AbstractModelForDbgengFrameFocusTest
|
||||||
|
|
||||||
trapAt("expStack!break_here", process);
|
trapAt("expStack!break_here", process);
|
||||||
|
|
||||||
|
waitSettled(m.getModel(), 200);
|
||||||
|
|
||||||
return retry(() -> {
|
return retry(() -> {
|
||||||
Map<List<String>, TargetStackFrame> frames =
|
Map<List<String>, TargetStackFrame> frames =
|
||||||
m.findAll(TargetStackFrame.class, seedPath(), true);
|
m.findAll(TargetStackFrame.class, seedPath(), true);
|
||||||
|
@ -51,4 +59,21 @@ public abstract class AbstractModelForDbgengFrameFocusTest
|
||||||
return Set.copyOf(frames.values());
|
return Set.copyOf(frames.values());
|
||||||
}, List.of(AssertionError.class));
|
}, 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 static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||||
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
|
||||||
public abstract class AbstractModelForDbgengProcessFocusTest
|
public abstract class AbstractModelForDbgengProcessActivationTest
|
||||||
extends AbstractDebuggerModelFocusTest {
|
extends AbstractDebuggerModelActivationTest {
|
||||||
|
|
||||||
|
private static final PathPattern PROCESS_PATTERN =
|
||||||
|
new PathPattern(PathUtils.parse("Sessions[0].Processes[]"));
|
||||||
|
|
||||||
protected int getCount() {
|
protected int getCount() {
|
||||||
return 3;
|
return 3;
|
||||||
|
@ -35,13 +42,16 @@ public abstract class AbstractModelForDbgengProcessFocusTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||||
DebuggerTestSpecimen specimen = getSpecimen();
|
DebuggerTestSpecimen specimen = getSpecimen();
|
||||||
TargetLauncher launcher = findLauncher();
|
TargetLauncher launcher = findLauncher();
|
||||||
int count = getCount();
|
int count = getCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waitSettled(m.getModel(), 200);
|
||||||
|
|
||||||
return retry(() -> {
|
return retry(() -> {
|
||||||
Map<List<String>, TargetProcess> found =
|
Map<List<String>, TargetProcess> found =
|
||||||
m.findAll(TargetProcess.class, PathUtils.parse("Sessions[0]"), true);
|
m.findAll(TargetProcess.class, PathUtils.parse("Sessions[0]"), true);
|
||||||
|
@ -49,4 +59,22 @@ public abstract class AbstractModelForDbgengProcessFocusTest
|
||||||
return Set.copyOf(found.values());
|
return Set.copyOf(found.values());
|
||||||
}, List.of(AssertionError.class));
|
}, 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 java.util.Set;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||||
|
|
||||||
public abstract class AbstractModelForDbgengSessionFocusTest
|
public abstract class AbstractModelForDbgengSessionActivationTest
|
||||||
extends AbstractDebuggerModelFocusTest {
|
extends AbstractDebuggerModelActivationTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||||
TODO("Don't know how to make multiple sessions");
|
TODO("Don't know how to make multiple sessions");
|
||||||
return null;
|
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;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new GadpDbgengModelHost();
|
return new GadpDbgengModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.model.gadp;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new GadpDbgengModelHost();
|
return new GadpDbgengModelHost();
|
||||||
|
|
|
@ -17,10 +17,10 @@ package agent.dbgeng.model.gadp;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
||||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||||
|
|
||||||
@Ignore("Don't know how to make multiple sessions")
|
@Ignore("Don't know how to make multiple sessions")
|
||||||
public class GadpModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
public class GadpModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionActivationTest {
|
||||||
@Override
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new GadpDbgengModelHost();
|
return new GadpDbgengModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.model.gadp;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new GadpDbgengModelHost();
|
return new GadpDbgengModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgengModelHost();
|
return new InVmDbgengModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgengModelHost();
|
return new InVmDbgengModelHost();
|
||||||
|
|
|
@ -17,10 +17,10 @@ package agent.dbgeng.model.invm;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
||||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||||
|
|
||||||
@Ignore("Don't know how to make multiple sessions")
|
@Ignore("Don't know how to make multiple sessions")
|
||||||
public class InVmModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
public class InVmModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionActivationTest {
|
||||||
@Override
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgengModelHost();
|
return new InVmDbgengModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgengModelHost();
|
return new InVmDbgengModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgmodel.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgmodelModelHost();
|
return new InVmDbgmodelModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgmodel.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgmodelModelHost();
|
return new InVmDbgmodelModelHost();
|
||||||
|
|
|
@ -17,10 +17,10 @@ package agent.dbgmodel.model.invm;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
||||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||||
|
|
||||||
@Ignore("Don't know how to make multiple sessions")
|
@Ignore("Don't know how to make multiple sessions")
|
||||||
public class InVmModelForDbgmodelSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
public class InVmModelForDbgmodelSessionFocusTest extends AbstractModelForDbgengSessionActivationTest {
|
||||||
@Override
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgmodelModelHost();
|
return new InVmDbgmodelModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgmodel.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmDbgmodelModelHost();
|
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
|
* 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.
|
* 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
|
* @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
|
* Specify a binary image for execution and debug symbols
|
||||||
|
|
|
@ -47,9 +47,10 @@ public interface GdbStackFrame extends GdbStackFrameOperations {
|
||||||
/**
|
/**
|
||||||
* Make this frame the current frame
|
* 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
|
* @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
|
* Get the thread for this frame
|
||||||
|
|
|
@ -68,9 +68,10 @@ public interface GdbThread
|
||||||
/**
|
/**
|
||||||
* Make this thread the current thread
|
* 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
|
* @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
|
* Set the value of an internal GDB variable
|
||||||
|
|
|
@ -67,7 +67,7 @@ public interface GdbCommand<T> {
|
||||||
* <p>
|
* <p>
|
||||||
* Complete {@code} pending with a result to short-circuit the execution of this command.
|
* 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);
|
void preCheck(GdbPendingCommand<? super T> pending);
|
||||||
|
|
||||||
|
@ -92,6 +92,13 @@ public interface GdbCommand<T> {
|
||||||
*/
|
*/
|
||||||
public Integer impliesCurrentFrameId();
|
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
|
* 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
|
* longer be current for the actual command execution. NB: The select command will cancel
|
||||||
* itself if this inferior is already current.
|
* 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
|
@Override
|
||||||
|
@ -347,8 +347,8 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setActive() {
|
public CompletableFuture<Void> setActive(boolean internal) {
|
||||||
return manager.setActiveInferior(this);
|
return manager.setActiveInferior(this, internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -961,7 +961,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||||
event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()),
|
event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()),
|
||||||
"groupRemoved-sel");
|
"groupRemoved-sel");
|
||||||
// Also cause GDB to generate thread selection events, if applicable
|
// 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.
|
* This issues a command to GDB to change its focus. It is not just a manager concept.
|
||||||
*
|
*
|
||||||
* @param inferior the inferior to select
|
* @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
|
* @return a future that completes when GDB has executed the command
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Void> setActiveInferior(GdbInferior inferior) {
|
CompletableFuture<Void> setActiveInferior(GdbInferior inferior, boolean internal) {
|
||||||
return execute(new GdbInferiorSelectCommand(this, inferior.getId()));
|
return execute(new GdbInferiorSelectCommand(this, inferior.getId(), internal));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -104,8 +104,9 @@ public class GdbStackFrameImpl implements GdbStackFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setActive() {
|
public CompletableFuture<Void> setActive(boolean internal) {
|
||||||
return manager.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level));
|
return manager
|
||||||
|
.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level, internal));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -128,7 +128,7 @@ public class GdbThreadImpl implements GdbThread {
|
||||||
protected <T> CompletableFuture<T> execute(AbstractGdbCommand<T> cmd) {
|
protected <T> CompletableFuture<T> execute(AbstractGdbCommand<T> cmd) {
|
||||||
switch (cmd.getInterpreter()) {
|
switch (cmd.getInterpreter()) {
|
||||||
case CLI:
|
case CLI:
|
||||||
return setActive().thenCombine(manager.execute(cmd), (__, v) -> v);
|
return setActive(true).thenCombine(manager.execute(cmd), (__, v) -> v);
|
||||||
case MI2:
|
case MI2:
|
||||||
return manager.execute(cmd);
|
return manager.execute(cmd);
|
||||||
default:
|
default:
|
||||||
|
@ -137,9 +137,9 @@ public class GdbThreadImpl implements GdbThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setActive() {
|
public CompletableFuture<Void> setActive(boolean internal) {
|
||||||
// Bypass the select-me-first logic
|
// Bypass the select-me-first logic
|
||||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null));
|
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null, internal));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -106,4 +106,9 @@ public abstract class AbstractGdbCommand<T> implements GdbCommand<T> {
|
||||||
public Integer impliesCurrentFrameId() {
|
public Integer impliesCurrentFrameId() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFocusInternallyDriven() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,4 +78,9 @@ public class GdbConsoleExecCommand extends AbstractGdbCommandWithThreadAndFrameI
|
||||||
public Output getOutputTo() {
|
public Output getOutputTo() {
|
||||||
return to;
|
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> {
|
public class GdbInferiorSelectCommand extends AbstractGdbCommand<Void> {
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private final boolean internal;
|
||||||
|
|
||||||
public GdbInferiorSelectCommand(GdbManagerImpl manager, int id) {
|
public GdbInferiorSelectCommand(GdbManagerImpl manager, int id, boolean internal) {
|
||||||
super(manager);
|
super(manager);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.internal = internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,4 +60,9 @@ public class GdbInferiorSelectCommand extends AbstractGdbCommand<Void> {
|
||||||
pending.checkCompletion(GdbCommandDoneEvent.class);
|
pending.checkCompletion(GdbCommandDoneEvent.class);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFocusInternallyDriven() {
|
||||||
|
return internal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import agent.gdb.manager.impl.*;
|
||||||
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
||||||
|
|
||||||
public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFrameId<Void> {
|
public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFrameId<Void> {
|
||||||
|
private final boolean internal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select the given thread and frame level
|
* 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 manager the manager to execute the command
|
||||||
* @param threadId the desired thread Id
|
* @param threadId the desired thread Id
|
||||||
* @param frameId the desired frame level
|
* @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);
|
super(manager, threadId, frameId);
|
||||||
|
this.internal = internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,4 +78,9 @@ public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFr
|
||||||
manager.doThreadSelected(thread, frame, done.getCause());
|
manager.doThreadSelected(thread, frame, done.getCause());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFocusInternallyDriven() {
|
||||||
|
return internal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,7 +375,7 @@ public class GdbModelTargetInferior
|
||||||
@Override
|
@Override
|
||||||
@Internal
|
@Internal
|
||||||
public CompletableFuture<Void> setActive() {
|
public CompletableFuture<Void> setActive() {
|
||||||
return impl.gateFuture(inferior.setActive());
|
return impl.gateFuture(inferior.setActive(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetAttributeType(name = EXIT_CODE_ATTRIBUTE_NAME)
|
@TargetAttributeType(name = EXIT_CODE_ATTRIBUTE_NAME)
|
||||||
|
|
|
@ -21,8 +21,6 @@ import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.gdb.manager.*;
|
import agent.gdb.manager.*;
|
||||||
import agent.gdb.manager.impl.*;
|
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.impl.cmd.GdbStateChangeRecord;
|
||||||
import agent.gdb.manager.reason.GdbReason;
|
import agent.gdb.manager.reason.GdbReason;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
|
@ -150,6 +148,13 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||||
// Otherwise, we'll presumably get the =thread-selected event
|
// 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) {
|
protected boolean isFocusInternallyDriven(GdbCause cause) {
|
||||||
if (cause == null || cause == GdbCause.Causes.UNCLAIMED) {
|
if (cause == null || cause == GdbCause.Causes.UNCLAIMED) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -160,13 +165,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||||
if (cause instanceof GdbPendingCommand<?>) {
|
if (cause instanceof GdbPendingCommand<?>) {
|
||||||
GdbPendingCommand<?> pcmd = (GdbPendingCommand<?>) cause;
|
GdbPendingCommand<?> pcmd = (GdbPendingCommand<?>) cause;
|
||||||
GdbCommand<?> cmd = pcmd.getCommand();
|
GdbCommand<?> cmd = pcmd.getCommand();
|
||||||
if (cmd instanceof GdbConsoleExecCommand) {
|
return cmd.isFocusInternallyDriven();
|
||||||
GdbConsoleExecCommand exec = (GdbConsoleExecCommand) cmd;
|
|
||||||
if (exec.getOutputTo() == Output.CAPTURE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -323,7 +322,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||||
if (impl.gdb.getKnownThreads().get(thread.getId()) != thread) {
|
if (impl.gdb.getKnownThreads().get(thread.getId()) != thread) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
thread.setActive().exceptionally(ex -> {
|
thread.setActive(true).exceptionally(ex -> {
|
||||||
impl.reportError(this, "Could not restore event thread", ex);
|
impl.reportError(this, "Could not restore event thread", ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -115,7 +115,7 @@ public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject,
|
||||||
@Override
|
@Override
|
||||||
@Internal
|
@Internal
|
||||||
public CompletableFuture<Void> setActive() {
|
public CompletableFuture<Void> setActive() {
|
||||||
return impl.gateFuture(frame.setActive());
|
return impl.gateFuture(frame.setActive(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME)
|
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME)
|
||||||
|
|
|
@ -226,7 +226,7 @@ public class GdbModelTargetThread
|
||||||
@Override
|
@Override
|
||||||
@Internal
|
@Internal
|
||||||
public CompletableFuture<Void> setActive() {
|
public CompletableFuture<Void> setActive() {
|
||||||
return impl.gateFuture(thread.setActive());
|
return impl.gateFuture(thread.setActive(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GdbModelTargetBreakpointLocation breakpointHit(GdbBreakpointHitReason reason) {
|
public GdbModelTargetBreakpointLocation breakpointHit(GdbBreakpointHitReason reason) {
|
||||||
|
|
|
@ -412,7 +412,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
||||||
GdbThread thread = waitOn(mgr.currentInferior().run());
|
GdbThread thread = waitOn(mgr.currentInferior().run());
|
||||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||||
//waitOn(mgr.waitForPrompt());
|
//waitOn(mgr.waitForPrompt());
|
||||||
waitOn(thread.setActive());
|
waitOn(thread.setActive(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,21 +15,27 @@
|
||||||
*/
|
*/
|
||||||
package agent.gdb.model;
|
package agent.gdb.model;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.List;
|
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.TargetBreakpointSpec.TargetBreakpointKind;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||||
import ghidra.dbg.target.TargetObject;
|
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
|
||||||
import ghidra.dbg.test.*;
|
import ghidra.dbg.test.*;
|
||||||
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
|
||||||
public abstract class AbstractModelForGdbBreakpointsTest
|
public abstract class AbstractModelForGdbBreakpointsTest
|
||||||
extends AbstractDebuggerModelBreakpointsTest implements ProvidesTargetViaLaunchSpecimen {
|
extends AbstractDebuggerModelBreakpointsTest implements ProvidesTargetViaLaunchSpecimen {
|
||||||
|
|
||||||
|
private static final PathPattern BREAK_PATTERN =
|
||||||
|
new PathPattern(PathUtils.parse("Breakpoints[]"));
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AbstractDebuggerModelTest getTest() {
|
public AbstractDebuggerModelTest getTest() {
|
||||||
return this;
|
return this;
|
||||||
|
@ -75,4 +81,97 @@ public abstract class AbstractModelForGdbBreakpointsTest
|
||||||
throw new AssertionError();
|
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;
|
package agent.gdb.model;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
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;
|
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() {
|
DebuggerTestSpecimen getSpecimen() {
|
||||||
return GdbLinuxSpecimen.STACK;
|
return GdbLinuxSpecimen.STACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||||
CompletableFuture<?> frame0 =
|
CompletableFuture<?> frame0 =
|
||||||
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1].Threads[1].Stack[0]"));
|
m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1].Threads[1].Stack[0]"));
|
||||||
CompletableFuture<?> frame1 =
|
CompletableFuture<?> frame1 =
|
||||||
|
@ -48,6 +56,8 @@ public abstract class AbstractModelForGdbFrameFocusTest extends AbstractDebugger
|
||||||
(TargetResumable) waitOn(m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1]")));
|
(TargetResumable) waitOn(m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1]")));
|
||||||
waitOn(inf.resume());
|
waitOn(inf.resume());
|
||||||
|
|
||||||
|
waitSettled(m.getModel(), 200);
|
||||||
|
|
||||||
return Set.of(
|
return Set.of(
|
||||||
(TargetObject) waitOn(frame0),
|
(TargetObject) waitOn(frame0),
|
||||||
(TargetObject) waitOn(frame1),
|
(TargetObject) waitOn(frame1),
|
||||||
|
@ -55,7 +65,24 @@ public abstract class AbstractModelForGdbFrameFocusTest extends AbstractDebugger
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getExpectedDefaultFocus() {
|
protected List<String> getExpectedDefaultActivePath() {
|
||||||
return PathUtils.parse("Inferiors[1].Threads[1].Stack[0]");
|
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;
|
package agent.gdb.model;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
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.TargetInterpreter;
|
||||||
import ghidra.dbg.target.TargetObject;
|
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;
|
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
|
@Override
|
||||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||||
CompletableFuture<?> inf1 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1]"));
|
CompletableFuture<?> inf1 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[1]"));
|
||||||
CompletableFuture<?> inf2 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[2]"));
|
CompletableFuture<?> inf2 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[2]"));
|
||||||
CompletableFuture<?> inf3 = m.getAddedWaiter().wait(PathUtils.parse("Inferiors[3]"));
|
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"));
|
||||||
waitOn(interpreter.execute("add-inferior"));
|
waitOn(interpreter.execute("add-inferior"));
|
||||||
|
|
||||||
|
waitSettled(m.getModel(), 200);
|
||||||
|
|
||||||
return Set.of(
|
return Set.of(
|
||||||
(TargetObject) waitOn(inf1),
|
(TargetObject) waitOn(inf1),
|
||||||
(TargetObject) waitOn(inf2),
|
(TargetObject) waitOn(inf2),
|
||||||
|
@ -44,7 +55,25 @@ public abstract class AbstractModelForGdbInferiorFocusTest extends AbstractDebug
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getExpectedDefaultFocus() {
|
protected List<String> getExpectedDefaultActivePath() {
|
||||||
return PathUtils.parse("Inferiors[1]");
|
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;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmGdbModelHost();
|
return new InVmGdbModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.gdb.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmGdbModelHost();
|
return new InVmGdbModelHost();
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.gdb.model.invm;
|
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
|
@Override
|
||||||
public ModelHost modelHost() throws Throwable {
|
public ModelHost modelHost() throws Throwable {
|
||||||
return new InVmGdbModelHost();
|
return new InVmGdbModelHost();
|
||||||
|
|
|
@ -742,9 +742,8 @@ public enum DebugModelConventions {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.obj = obj;
|
this.obj = obj;
|
||||||
obj.addListener(this);
|
obj.addListener(this);
|
||||||
obj.fetchAttribute(name).thenAccept(t -> {
|
set((T) obj.getCachedAttribute(name), null);
|
||||||
set((T) t, null);
|
obj.fetchAttribute(name).exceptionally(ex -> {
|
||||||
}).exceptionally(ex -> {
|
|
||||||
Msg.error(this, "Could not get initial value of " + name + " for " + obj, ex);
|
Msg.error(this, "Could not get initial value of " + name + " for " + obj, ex);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
||||||
/**
|
/**
|
||||||
* An object made active
|
* An object made active
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* "Active" here describes which object in a given class the target should operate on
|
* "Active" here describes which object in a given class the target should operate on
|
||||||
*/
|
*/
|
||||||
@DebuggerTargetObjectIface("ActiveScope")
|
@DebuggerTargetObjectIface("ActiveScope")
|
||||||
|
|
|
@ -194,4 +194,40 @@ public class PathPattern implements PathPredicates {
|
||||||
}
|
}
|
||||||
return new PathPattern(result);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void runTestPlaceBreakpoint(TargetBreakpointKind kind) throws Throwable {
|
/**
|
||||||
assumeTrue(getExpectedSupportedKinds().contains(kind));
|
* Verify that the given breakpoint location covers the required range and kind, using the
|
||||||
m.build();
|
* 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() {
|
var monitor = new DebuggerModelListener() {
|
||||||
DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
|
DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
|
||||||
|
|
||||||
|
@ -217,25 +304,56 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
m.getModel().addModelListener(monitor.reorderer, true);
|
m.getModel().addModelListener(monitor.reorderer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void runTestPlaceBreakpoint(TargetBreakpointKind kind) throws Throwable {
|
||||||
|
assumeTrue(getExpectedSupportedKinds().contains(kind));
|
||||||
|
m.build();
|
||||||
|
|
||||||
|
addMonitor();
|
||||||
|
|
||||||
TargetObject target = obtainTarget();
|
TargetObject target = obtainTarget();
|
||||||
TargetBreakpointSpecContainer container = findBreakpointSpecContainer(target.getPath());
|
TargetBreakpointSpecContainer container = findBreakpointSpecContainer(target.getPath());
|
||||||
AddressRange range = getSuitableRangeForBreakpoint(target, kind);
|
AddressRange range = getSuitableRangeForBreakpoint(target, kind);
|
||||||
waitOn(container.placeBreakpoint(range, Set.of(kind)));
|
waitOn(container.placeBreakpoint(range, Set.of(kind)));
|
||||||
retryVoid(() -> {
|
TargetBreakpointLocation loc = retry(() -> {
|
||||||
Collection<? extends TargetBreakpointLocation> found =
|
Collection<? extends TargetBreakpointLocation> found =
|
||||||
m.findAll(TargetBreakpointLocation.class, target.getPath(), true).values();
|
m.findAll(TargetBreakpointLocation.class, target.getPath(), true).values();
|
||||||
assertAtLeastOneLocCovers(found, range, kind);
|
return assertAtLeastOneLocCovers(found, range, kind);
|
||||||
}, List.of(AssertionError.class));
|
}, 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
|
@Test
|
||||||
public void testPlaceSoftwareBreakpoint() throws Throwable {
|
public void testPlaceSoftwareExecuteBreakpoint() throws Throwable {
|
||||||
runTestPlaceBreakpoint(TargetBreakpointKind.SW_EXECUTE);
|
runTestPlaceBreakpoint(TargetBreakpointKind.SW_EXECUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPlaceHardwareBreakpoint() throws Throwable {
|
public void testPlaceHardwareExecuteBreakpoint() throws Throwable {
|
||||||
runTestPlaceBreakpoint(TargetBreakpointKind.HW_EXECUTE);
|
runTestPlaceBreakpoint(TargetBreakpointKind.HW_EXECUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +367,26 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
runTestPlaceBreakpoint(TargetBreakpointKind.WRITE);
|
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 {
|
protected Set<TargetBreakpointLocation> createLocations() throws Throwable {
|
||||||
// TODO: Test with multiple targets?
|
// TODO: Test with multiple targets?
|
||||||
TargetObject target = obtainTarget();
|
TargetObject target = obtainTarget();
|
||||||
|
@ -278,6 +416,10 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
assertFalse(t.isEnabled());
|
assertFalse(t.isEnabled());
|
||||||
}, List.of(AssertionError.class));
|
}, List.of(AssertionError.class));
|
||||||
|
if (m.hasInterpreter()) {
|
||||||
|
TargetInterpreter interpreter = findInterpreter();
|
||||||
|
assertEnabledViaInterpreter(t, false, interpreter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Repeat it for fun. Should have no effect
|
// Repeat it for fun. Should have no effect
|
||||||
for (TargetTogglable t : order) {
|
for (TargetTogglable t : order) {
|
||||||
|
@ -285,6 +427,10 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
assertFalse(t.isEnabled());
|
assertFalse(t.isEnabled());
|
||||||
}, List.of(AssertionError.class));
|
}, List.of(AssertionError.class));
|
||||||
|
if (m.hasInterpreter()) {
|
||||||
|
TargetInterpreter interpreter = findInterpreter();
|
||||||
|
assertEnabledViaInterpreter(t, false, interpreter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable each
|
// Enable each
|
||||||
|
@ -293,6 +439,10 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
assertTrue(t.isEnabled());
|
assertTrue(t.isEnabled());
|
||||||
}, List.of(AssertionError.class));
|
}, List.of(AssertionError.class));
|
||||||
|
if (m.hasInterpreter()) {
|
||||||
|
TargetInterpreter interpreter = findInterpreter();
|
||||||
|
assertEnabledViaInterpreter(t, true, interpreter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Repeat it for fun. Should have no effect
|
// Repeat it for fun. Should have no effect
|
||||||
for (TargetTogglable t : order) {
|
for (TargetTogglable t : order) {
|
||||||
|
@ -300,6 +450,49 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
assertTrue(t.isEnabled());
|
assertTrue(t.isEnabled());
|
||||||
}, List.of(AssertionError.class));
|
}, 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()));
|
.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
|
@Test
|
||||||
public void testToggleBreakpointLocations() throws Throwable {
|
public void testToggleBreakpointLocations() throws Throwable {
|
||||||
assumeTrue(isSupportsTogglableLocations());
|
assumeTrue(isSupportsTogglableLocations());
|
||||||
|
@ -323,15 +529,46 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
locs.stream().map(l -> l.as(TargetTogglable.class)).collect(Collectors.toSet()));
|
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 {
|
protected void runDeleteTest(Set<TargetDeletable> set) throws Throwable {
|
||||||
List<TargetDeletable> order = new ArrayList<>(set);
|
List<TargetDeletable> order = new ArrayList<>(set);
|
||||||
Collections.shuffle(order);
|
Collections.shuffle(order);
|
||||||
// Disable each
|
// Delete each
|
||||||
for (TargetDeletable d : order) {
|
for (TargetDeletable d : order) {
|
||||||
waitOn(d.delete());
|
waitOn(d.delete());
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
assertFalse(d.isValid());
|
assertFalse(d.isValid());
|
||||||
}, List.of(AssertionError.class));
|
}, 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()));
|
.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
|
@Test
|
||||||
public void testDeleteBreakpointLocations() throws Throwable {
|
public void testDeleteBreakpointLocations() throws Throwable {
|
||||||
assumeTrue(isSupportsDeletableLocations());
|
assumeTrue(isSupportsDeletableLocations());
|
||||||
|
@ -354,4 +604,17 @@ public abstract class AbstractDebuggerModelBreakpointsTest extends AbstractDebug
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
locs.stream().map(l -> l.as(TargetDeletable.class)).collect(Collectors.toSet()));
|
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;
|
package ghidra.dbg.test;
|
||||||
|
|
||||||
import static ghidra.lifecycle.Unfinished.TODO;
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assume.assumeNotNull;
|
import static org.junit.Assume.assumeNotNull;
|
||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
@ -24,7 +23,6 @@ import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ghidra.async.AsyncReference;
|
import ghidra.async.AsyncReference;
|
||||||
|
@ -182,21 +180,6 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
||||||
runTestExecute(interpreter, cmd);
|
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(
|
protected TargetProcess runTestLaunchViaInterpreterShowsInProcessContainer(
|
||||||
TargetInterpreter interpreter) throws Throwable {
|
TargetInterpreter interpreter) throws Throwable {
|
||||||
DebuggerTestSpecimen specimen = getLaunchSpecimen();
|
DebuggerTestSpecimen specimen = getLaunchSpecimen();
|
||||||
|
|
|
@ -56,6 +56,10 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadlessIn
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TargetActiveScope findActiveScope() throws Throwable {
|
||||||
|
return m.find(TargetActiveScope.class, seedPath());
|
||||||
|
}
|
||||||
|
|
||||||
protected TargetObject findAttachableContainer() throws Throwable {
|
protected TargetObject findAttachableContainer() throws Throwable {
|
||||||
return m.findContainer(TargetAttachable.class, seedPath());
|
return m.findContainer(TargetAttachable.class, seedPath());
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,11 @@ public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestU
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasInterpreter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasInterruptibleProcesses() {
|
public boolean hasInterruptibleProcesses() {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
||||||
|
|
||||||
public final DebuggerObjectModel model;
|
public final DebuggerObjectModel model;
|
||||||
public Thread thread = null;
|
public Thread thread = null;
|
||||||
public Set<TargetObject> valid = new HashSet<>();
|
public Map<TargetObject, TargetObject> valid = new HashMap<>();
|
||||||
|
|
||||||
// Knobs
|
// Knobs
|
||||||
// TODO: Make these methods instead?
|
// TODO: Make these methods instead?
|
||||||
|
@ -85,12 +85,8 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
||||||
thread = Thread.currentThread();
|
thread = Thread.currentThread();
|
||||||
}
|
}
|
||||||
if (requireSameThread) {
|
if (requireSameThread) {
|
||||||
if (thread != Thread.currentThread()) {
|
assertEquals("Completion came from an unexpected thread. Probably forgot gateFuture()",
|
||||||
throw new AssertionError("Completion came from an unexpected thread expected:" +
|
thread, Thread.currentThread());
|
||||||
thread + " but got " + Thread.currentThread());
|
|
||||||
}
|
|
||||||
assertEquals("Completion came from an unexpected thread", thread,
|
|
||||||
Thread.currentThread());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,14 +109,14 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
|
||||||
public void validateObjectValid(String callback, TargetObject obj) {
|
public void validateObjectValid(String callback, TargetObject obj) {
|
||||||
if (requireValid) {
|
if (requireValid) {
|
||||||
assertTrue("Object " + obj.getJoinedPath(".") + " invalid during callback " + callback,
|
assertTrue("Object " + obj.getJoinedPath(".") + " invalid during callback " + callback,
|
||||||
valid.contains(obj));
|
valid.containsKey(obj));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateObjectInvalid(String callback, TargetObject obj) {
|
public void validateObjectInvalid(String callback, TargetObject obj) {
|
||||||
if (requireValid) {
|
if (requireValid) {
|
||||||
assertFalse("Object " + obj.getJoinedPath(".") + " valid during callback " + callback +
|
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) {
|
if (log) {
|
||||||
Msg.info(this, "created(object=" + object + ")");
|
Msg.info(this, "created(object=" + object + ")");
|
||||||
}
|
}
|
||||||
valid.add(object);
|
TargetObject exists = valid.put(object, object);
|
||||||
off.catching(() -> {
|
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");
|
validateCallbackThread("created");
|
||||||
validateObject("created", object);
|
validateObject("created", object);
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,15 +23,19 @@ import java.util.Map.Entry;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ghidra.async.AsyncReference;
|
import ghidra.async.*;
|
||||||
import ghidra.async.AsyncTestUtils;
|
import ghidra.dbg.*;
|
||||||
import ghidra.dbg.DebugModelConventions;
|
|
||||||
import ghidra.dbg.DebugModelConventions.AsyncAccess;
|
import ghidra.dbg.DebugModelConventions.AsyncAccess;
|
||||||
|
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
||||||
import ghidra.dbg.target.*;
|
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.target.TargetSteppable.TargetStepKind;
|
||||||
import ghidra.dbg.test.AbstractDebuggerModelTest;
|
import ghidra.dbg.test.AbstractDebuggerModelTest;
|
||||||
import ghidra.dbg.test.AbstractDebuggerModelTest.DebuggerTestSpecimen;
|
import ghidra.dbg.test.AbstractDebuggerModelTest.DebuggerTestSpecimen;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.util.NumericUtilities;
|
import ghidra.util.NumericUtilities;
|
||||||
|
|
||||||
public interface DebuggerModelTestUtils extends AsyncTestUtils {
|
public interface DebuggerModelTestUtils extends AsyncTestUtils {
|
||||||
|
@ -204,4 +208,78 @@ public interface DebuggerModelTestUtils extends AsyncTestUtils {
|
||||||
return process;
|
return process;
|
||||||
}, List.of(AssertionError.class));
|
}, 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 hasDetachableProcesses();
|
||||||
|
|
||||||
|
boolean hasInterpreter();
|
||||||
|
|
||||||
boolean hasInterruptibleProcesses();
|
boolean hasInterruptibleProcesses();
|
||||||
|
|
||||||
boolean hasKillableProcesses();
|
boolean hasKillableProcesses();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue