mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
1062 lines
41 KiB
Java
1062 lines
41 KiB
Java
/* ###
|
|
* 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 agent.dbgeng.testutil.DummyProc.runProc;
|
|
import static ghidra.lifecycle.Unfinished.TODO;
|
|
import static org.junit.Assert.*;
|
|
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.util.*;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.stream.Collectors;
|
|
|
|
import org.junit.*;
|
|
|
|
import agent.dbgeng.dbgeng.DbgEngTest;
|
|
import agent.dbgeng.model.iface1.DbgModelTargetLauncher;
|
|
import agent.dbgeng.testutil.DummyProc;
|
|
import ghidra.async.*;
|
|
import ghidra.dbg.*;
|
|
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
|
|
import ghidra.dbg.error.DebuggerModelNoSuchPathException;
|
|
import ghidra.dbg.error.DebuggerModelTypeException;
|
|
import ghidra.dbg.target.*;
|
|
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
|
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
|
import ghidra.dbg.target.TargetConsole.Channel;
|
|
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
|
import ghidra.dbg.util.PathUtils;
|
|
import ghidra.program.model.address.Address;
|
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
|
import ghidra.util.*;
|
|
|
|
public abstract class AbstractModelForDbgTest
|
|
extends AbstractGhidraHeadlessIntegrationTest {
|
|
protected static final Map<String, byte[]> AMD64_TEST_REG_VALUES = Map.of( //
|
|
"rax", NumericUtilities.convertStringToBytes("0123456789abcdef"), //
|
|
"ymm0", NumericUtilities.convertStringToBytes("" + //
|
|
"0011223344556677" + "8899aabbccddeeff"));
|
|
// TODO: for some reason, the dbgeng thinks ymm0 is VECTOR128
|
|
//"0123456789abcdef" + "fedcba9876543210" + "0011223344556677" + "8899aabbccddeeff"));
|
|
protected static final long TIMEOUT_MILLISECONDS =
|
|
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
|
|
|
protected static Map<String, String> hexlify(Map<String, byte[]> map) {
|
|
return map.entrySet()
|
|
.stream()
|
|
.collect(Collectors.toMap(Entry::getKey,
|
|
e -> NumericUtilities.convertBytesToString(e.getValue())));
|
|
}
|
|
|
|
public interface ModelHost extends AutoCloseable {
|
|
DebuggerObjectModel getModel();
|
|
|
|
CompletableFuture<Void> init();
|
|
}
|
|
|
|
protected abstract ModelHost modelHost() throws Exception;
|
|
|
|
protected static class CatchOffThread implements AutoCloseable {
|
|
protected Throwable caught;
|
|
|
|
void catching(Runnable runnable) {
|
|
try {
|
|
runnable.run();
|
|
}
|
|
catch (Throwable e) {
|
|
caught = e;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void close() throws Exception {
|
|
if (caught != null) {
|
|
throw new AssertionError("Off-thread exception", caught);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected static <T> T waitOn(CompletableFuture<T> future) throws Throwable {
|
|
try {
|
|
return future.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
|
}
|
|
catch (ExecutionException e) {
|
|
throw e.getCause();
|
|
}
|
|
}
|
|
|
|
@Before
|
|
public void setUpDbgEngTest() {
|
|
DbgEngTest.assumeDbgengDLLLoadable();
|
|
}
|
|
|
|
@Test
|
|
public void testInitFinish() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testNonExistentPathGivesNull() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
model.fetchModelObject("Doesn't exist").handle(seq::next);
|
|
}, TypeSpec.cls(TargetObject.class)).then((obj, seq) -> {
|
|
assertNull(obj);
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testSessionLaunch() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<DbgModelTargetLauncher> launcher = new AtomicReference<>();
|
|
TypeSpec<Map<String, ? extends TargetObject>> t = TypeSpec.auto();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class, root.get())
|
|
.handle(seq::next);
|
|
}, launcher).then(seq -> {
|
|
Msg.debug(this, "Launching...");
|
|
launcher.get().launch("notepad", "junk.txt").handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Processes (after launch)...");
|
|
model.fetchObjectElements(List.of("Sessions", "[0]"))
|
|
.handle(seq::next);
|
|
}, t).then((children, seq) -> {
|
|
Msg.debug(this, "Processes after: " + children);
|
|
assertEquals(1, children.size());
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testProcessLaunch() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<TargetObject> session = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> rootAccess = new AtomicReference<>();
|
|
AtomicReference<DbgModelTargetLauncher> launcher = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> launcherAccess = new AtomicReference<>();
|
|
TypeSpec<Map<String, ? extends TargetObject>> t = TypeSpec.auto();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, rootAccess).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
rootAccess.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
model.fetchModelObject(List.of("Sessions", "[0]")).handle(seq::next);
|
|
}, session).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class,
|
|
session.get()).handle(seq::next);
|
|
}, TypeSpec.cls(TargetObject.class)).then((obj, seq) -> {
|
|
assertTrue(obj.getInterfaceNames().contains("Launcher"));
|
|
launcher.set((DbgModelTargetLauncher) obj);
|
|
Msg.debug(this, "Tracking process access...");
|
|
DebugModelConventions.trackAccessibility(obj).handle(seq::next);
|
|
}, launcherAccess).then(seq -> {
|
|
Msg.debug(this, "Waiting for process access...");
|
|
launcherAccess.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Launching...");
|
|
launcher.get().launch("notepad", "junk.txt").handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again)...");
|
|
rootAccess.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Processes (after launch)...");
|
|
model.fetchObjectElements(List.of("Sessions", "[0]", "Processes"))
|
|
.handle(seq::next);
|
|
}, t).then((elements, seq) -> {
|
|
Msg.debug(this, "Processes after: " + elements);
|
|
assertEquals(1, elements.size());
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testListAvailableProcesses() throws Throwable {
|
|
try (DummyProc np = runProc("notepad"); ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
model.fetchObjectElements(List.of("Sessions", "[0]", "Available"))
|
|
.handle(seq::next);
|
|
}, DebuggerObjectModel.ELEMENT_MAP_TYPE).then((available, seq) -> {
|
|
assertTrue(available.containsKey(Long.toString(np.pid)));
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testListProcesses() throws Throwable {
|
|
try (DummyProc np = runProc("notepad"); ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> proc = new AtomicReference<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(TargetAttacher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, proc).then(seq -> {
|
|
Msg.debug(this, "Attaching to bogus path...");
|
|
TargetAttacher attacher = proc.get().as(TargetAttacher.class);
|
|
TODO();
|
|
seq.next(null, null);
|
|
}).then(seq -> {
|
|
model.fetchObjectElements(List.of("Sessions", "[0]", "Processes"))
|
|
.handle(seq::next);
|
|
// NB: listProcesses will fail if no process is being debugged
|
|
}, DebuggerObjectModel.ELEMENT_MAP_TYPE).then((processes, seq) -> {
|
|
assertEquals(1, processes.size());
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testSessionAttachKill() throws Throwable {
|
|
try (DummyProc np = runProc("notepad"); ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetAttacher> attacher = new AtomicReference<>();
|
|
AtomicReference<TargetAttachable> attachable = new AtomicReference<>();
|
|
TypeSpec<Map<String, ? extends TargetObject>> t = TypeSpec.auto();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
// Msg.debug(this, "Getting Processes (before attach)...");
|
|
// model.getObjectElements(List.of("Sessions", "[0]", "Processes")).handle(seq::next);
|
|
// }, t).then((children, seq) -> {
|
|
// Msg.debug(this, "Processes before: " + children);
|
|
// assertEquals(1, children.size());
|
|
|
|
AsyncFence fence = new AsyncFence();
|
|
Msg.debug(this, "Finding TargetAttacher...");
|
|
fence.include(DebugModelConventions.findSuitable(TargetAttacher.class, root.get())
|
|
.thenAccept(a -> {
|
|
Msg.debug(this, " Got TargetAttacher: " + a);
|
|
attacher.set(a);
|
|
}));
|
|
fence.include(model.fetchModelObject("Sessions", "[0]", "Available",
|
|
"[" + np.pid + "]")
|
|
.thenAccept(o -> {
|
|
Msg.debug(this, " Got Attachable: " + o);
|
|
assertTrue(o.getInterfaceNames().contains("Attachable"));
|
|
attachable.set((TargetAttachable) o);
|
|
}));
|
|
fence.ready().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Attaching...");
|
|
attacher.get().attach(attachable.get()).handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Processes (after attach)...");
|
|
model.fetchObjectElements(List.of("Sessions", "[0]", "Processes"))
|
|
.handle(seq::next);
|
|
}, t).then((elements, seq) -> {
|
|
Msg.debug(this, "Processes after: " + elements);
|
|
assertEquals(1, elements.size());
|
|
Msg.debug(this, "Killing...");
|
|
TargetObject attached = elements.get("0");
|
|
assertTrue(attached.getInterfaceNames().contains("Killable"));
|
|
TargetKillable killable = (TargetKillable) attached;
|
|
killable.kill().handle(seq::next);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testProcessAttachKill() throws Throwable {
|
|
try (DummyProc np = runProc("notepad"); ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> obj = new AtomicReference<>();
|
|
TypeSpec<Map<String, ? extends TargetObject>> t = TypeSpec.auto();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(TargetAttacher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, obj).then(seq -> {
|
|
Msg.debug(this, "Attaching...");
|
|
TargetAttacher attacher = obj.get().as(TargetAttacher.class);
|
|
attacher.attach(np.pid)
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again, again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Processes (after attach)...");
|
|
model.fetchObjectElements(List.of("Sessions", "[0]", "Processes"))
|
|
.handle(seq::next);
|
|
}, t).then((elements, seq) -> {
|
|
Msg.debug(this, "Processes after: " + elements);
|
|
assertEquals(1, elements.size());
|
|
Msg.debug(this, "Killing...");
|
|
TargetObject attached = elements.get("0");
|
|
assertTrue(attached.getInterfaceNames().contains("Killable"));
|
|
TargetKillable killable = (TargetKillable) attached;
|
|
killable.kill().handle(seq::next);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testProcessAttachContKill() throws Throwable {
|
|
try (DummyProc np = runProc("notepad"); ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> obj = new AtomicReference<>();
|
|
TypeSpec<Map<String, ? extends TargetObject>> t = TypeSpec.auto();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(TargetAttacher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, obj).then(seq -> {
|
|
Msg.debug(this, "Attaching...");
|
|
TargetAttacher attacher = obj.get().as(TargetAttacher.class);
|
|
attacher.attach(np.pid)
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again, again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Process 1...");
|
|
model.fetchModelObject("Sessions", "[0]", "Processes", "[0]").handle(seq::next);
|
|
}, obj).then(seq -> {
|
|
Msg.debug(this, "Resuming...");
|
|
TargetResumable resumable = obj.get().as(TargetResumable.class);
|
|
resumable.resume().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (after resume)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Processes (after attach)...");
|
|
model.fetchObjectElements(List.of("Sessions", "[0]", "Processes"))
|
|
.handle(seq::next);
|
|
}, t).then((elements, seq) -> {
|
|
Msg.debug(this, "Processes after: " + elements);
|
|
assertEquals(1, elements.size());
|
|
Msg.debug(this, "Killing...");
|
|
TargetObject attached = elements.get("0");
|
|
assertTrue(attached.getInterfaceNames().contains("Killable"));
|
|
TargetKillable killable = (TargetKillable) attached;
|
|
killable.kill().handle(seq::nextIgnore);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testLaunchContExit() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> obj = new AtomicReference<>();
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class, root.get())
|
|
.handle(seq::next);
|
|
}, obj).then(seq -> {
|
|
Msg.debug(this, "Launching...");
|
|
TargetLauncher launcher = obj.get().as(TargetLauncher.class);
|
|
launcher.launch(Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, "notepad junk.txt"))
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Process 1...");
|
|
model.fetchModelObject("Sessions", "[0]", "Processes", "[0]").handle(seq::next);
|
|
}, obj).then(seq -> {
|
|
Msg.debug(this, "Resuming...");
|
|
TargetResumable resumable = obj.get().as(TargetResumable.class);
|
|
resumable.resume().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (after resume)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test(expected = DebuggerModelNoSuchPathException.class)
|
|
public void testAttachNoObjectErr() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> proc = new AtomicReference<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(TargetAttacher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, proc).then(seq -> {
|
|
Msg.debug(this, "Attaching to bogus path...");
|
|
TargetAttacher attacher = proc.get().as(TargetAttacher.class);
|
|
TODO();
|
|
seq.next(null, null);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test(expected = DebuggerModelTypeException.class)
|
|
public void testAttachNonAttachableErr() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> proc = new AtomicReference<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(TargetAttacher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, proc).then(seq -> {
|
|
Msg.debug(this, "Attaching to bogus path...");
|
|
TargetAttacher attacher = proc.get().as(TargetAttacher.class);
|
|
TODO();
|
|
seq.next(null, null);
|
|
}).finish());
|
|
fail("Exception expected");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@Ignore("for developer workstation")
|
|
public void stressTestExecute() throws Throwable {
|
|
for (int i = 0; i < 100; i++) {
|
|
testExecute();
|
|
}
|
|
}
|
|
|
|
//@Test
|
|
public void testExecute() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AsyncReference<String, Void> lastOut = new AsyncReference<>();
|
|
|
|
DebuggerModelListener l = new DebuggerModelListener() {
|
|
@Override
|
|
public void consoleOutput(TargetObject interpreter, Channel channel,
|
|
byte[] out) {
|
|
String str = new String(out);
|
|
Msg.debug(this, "Got " + channel + " output: " + str);
|
|
lastOut.set(str, null);
|
|
}
|
|
};
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object...");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
root.get().addListener(l);
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Running command...");
|
|
TargetInterpreter interpreter = root.get().as(TargetInterpreter.class);
|
|
interpreter.execute(".echo xyzzy").handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for expected output...");
|
|
lastOut.waitValue("xyzzy\n").handle(seq::next);
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testExecuteCapture() throws Throwable {
|
|
try (ModelHost m = modelHost(); CatchOffThread offThread = new CatchOffThread()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
|
|
DebuggerModelListener l = new DebuggerModelListener() {
|
|
@Override
|
|
public void consoleOutput(TargetObject interpreter, Channel channel,
|
|
String out) {
|
|
String str = new String(out);
|
|
Msg.debug(this, "Got " + channel + " output: " + str);
|
|
if (!str.contains("test")) {
|
|
return;
|
|
}
|
|
offThread.catching(() -> fail("Unexpected output:" + str));
|
|
}
|
|
};
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object...");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
root.get().addListener(l);
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Running command with capture...");
|
|
TargetInterpreter interpreter = root.get().as(TargetInterpreter.class);
|
|
interpreter.executeCapture(".echo xyzzy").handle(seq::next);
|
|
}, TypeSpec.STRING).then((out, seq) -> {
|
|
Msg.debug(this, "Captured: " + out);
|
|
assertTrue(out.contains("xyzzy"));
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testGetBreakKinds() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetBreakpointSpecContainer> breaks = new AtomicReference<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object...");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding breakpoint container...");
|
|
DebugModelConventions.findSuitable(TargetBreakpointSpecContainer.class, root.get())
|
|
.handle(seq::next);
|
|
}, breaks).then(seq -> {
|
|
Msg.debug(this, "Got: " + breaks);
|
|
TargetBreakpointKindSet kinds = breaks.get().getSupportedBreakpointKinds();
|
|
Msg.debug(this, "Supports: " + kinds);
|
|
assertEquals(4, kinds.size());
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
public static final TypeSpec<Collection<? extends TargetBreakpointLocation>> BL_COL_SPEC =
|
|
null;
|
|
|
|
@Test
|
|
public void testPlaceBreakpoint() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<DbgModelTargetLauncher> launcher = new AtomicReference<>();
|
|
AtomicReference<TargetBreakpointSpecContainer> breaks = new AtomicReference<>();
|
|
AtomicReference<TargetBreakpointLocation> loc = new AtomicReference<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object...");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, launcher).then(seq -> {
|
|
Msg.debug(this, "Launching...");
|
|
launcher.get()
|
|
.launch(Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, "notepad junk.txt"))
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding breakpoint container...");
|
|
DebugModelConventions.findSuitable(TargetBreakpointSpecContainer.class, root.get())
|
|
.handle(seq::next);
|
|
}, breaks).then(seq -> {
|
|
Msg.debug(this, "Placing breakpoint...");
|
|
breaks.get()
|
|
.placeBreakpoint("0x7ff7d52c8987",
|
|
Set.of(TargetBreakpointKind.SW_EXECUTE))
|
|
.handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting breakpoint specs...");
|
|
breaks.get()
|
|
.fetchElements()
|
|
.handle(seq::next);
|
|
}, DebuggerObjectModel.ELEMENT_MAP_TYPE).then((specs, seq) -> {
|
|
Msg.debug(this, "Got specs: " + specs);
|
|
assertEquals(1, specs.size());
|
|
TargetBreakpointSpec spec = specs.get("0").as(TargetBreakpointSpec.class);
|
|
spec.getLocations().handle(seq::next);
|
|
}, BL_COL_SPEC).then((es, seq) -> {
|
|
Msg.debug(this, "Got effectives: " + es);
|
|
assertEquals(1, es.size());
|
|
loc.set(es.iterator().next());
|
|
Address addr = loc.get().getAddress();
|
|
Msg.debug(this, "Got address: " + addr);
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testStack() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetBreakpointSpecContainer> breaks = new AtomicReference<>();
|
|
AtomicReference<TargetLauncher> launcher = new AtomicReference<>();
|
|
AtomicReference<TargetObject> obj = new AtomicReference<>();
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(TargetLauncher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, launcher).then(seq -> {
|
|
Msg.debug(this, "Launching...");
|
|
launcher.get()
|
|
.launch(Map.of("args", List.of("notepad", "junk.txt")))
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding breakpoint container...");
|
|
DebugModelConventions.findSuitable(TargetBreakpointSpecContainer.class, root.get())
|
|
.handle(seq::next);
|
|
}, breaks).then(seq -> {
|
|
Msg.debug(this, "Placing breakpoint...");
|
|
breaks.get()
|
|
.placeBreakpoint("0x7ff7d52c8987",
|
|
Set.of(TargetBreakpointKind.SW_EXECUTE))
|
|
.handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Process 1...");
|
|
model.fetchModelObject("Sessions", "[0]", "Processes", "[0]").handle(seq::next);
|
|
}, obj).then(seq -> {
|
|
Msg.debug(this, "Resuming...");
|
|
TargetResumable resumable = obj.get().as(TargetResumable.class);
|
|
resumable.resume().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (after resume)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
obj.get().fetchSubElements("Threads", "[0]", "Stack").handle(seq::next);
|
|
}, DebuggerObjectModel.ELEMENT_MAP_TYPE).then((frames, seq) -> {
|
|
Msg.debug(this, "Got stack:");
|
|
for (Map.Entry<String, ? extends TargetObject> ent : frames.entrySet()) {
|
|
TargetStackFrame frame = ent.getValue().as(TargetStackFrame.class);
|
|
Msg.debug(this, ent.getKey() + ": " + frame.getProgramCounter());
|
|
}
|
|
long offset = frames.get("0")
|
|
.getTypedAttributeNowByName("pc", Address.class,
|
|
null)
|
|
.getOffset();
|
|
assertEquals(0x7fffadd6e890L, offset);
|
|
//assertEquals("main", frames.get("" + (frames.size() - 1))
|
|
// .getTypedAttributeNowByName("function", String.class, null));
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testRegisters() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetLauncher> launcher = new AtomicReference<>();
|
|
AtomicReference<TargetObject> proc = new AtomicReference<>();
|
|
AtomicReference<TargetRegisterBank> bank = new AtomicReference<>();
|
|
Set<TargetRegister> descs = new LinkedHashSet<>();
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, launcher).then(seq -> {
|
|
Msg.debug(this, "Launching...");
|
|
launcher.get()
|
|
.launch(Map.of("args", List.of("notepad", "junk.txt")))
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting Process 1...");
|
|
model.fetchModelObject(List.of("Sessions", "[0]", "Processes", "[0]"))
|
|
.handle(
|
|
seq::next);
|
|
}, proc).then(seq -> {
|
|
proc.get().fetchSuccessor("Threads", "[0]", "Stack", "[0]").thenAccept(top -> {
|
|
bank.set(top.as(TargetRegisterBank.class));
|
|
}).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Got bank: " + bank.get());
|
|
Msg.debug(this, "Descriptions ref: " + bank.get().getDescriptions());
|
|
TargetRegisterContainer cont = bank.get().getDescriptions();
|
|
Msg.debug(this, "Register descriptions: " + cont);
|
|
cont.getRegisters().thenAccept(descs::addAll).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Elements: ");
|
|
for (TargetRegister reg : descs) {
|
|
Msg.debug(this, " " + reg.getIndex() + ": " + reg.getBitLength());
|
|
}
|
|
bank.get().readRegisters(descs).handle(seq::next);
|
|
}, TypeSpec.map(String.class, byte[].class)).then((data, seq) -> {
|
|
Msg.debug(this, "Values: ");
|
|
for (Map.Entry<String, byte[]> ent : data.entrySet()) {
|
|
Msg.debug(this, " " + ent.getKey() + " = " +
|
|
NumericUtilities.convertBytesToString(ent.getValue()));
|
|
}
|
|
// TODO: Implement Environment, and port these tests
|
|
Msg.debug(this, "Writing two registers, general and vector");
|
|
bank.get().writeRegistersNamed(AMD64_TEST_REG_VALUES).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Flushing cache");
|
|
bank.get().invalidateCaches().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Re-reading values");
|
|
// NOTE: Can't reliably step here since rax may be referenced by the instruction
|
|
bank.get().readRegistersNamed(AMD64_TEST_REG_VALUES.keySet()).handle(seq::next);
|
|
}, TypeSpec.map(String.class, byte[].class)).then((data, seq) -> {
|
|
assertEquals(hexlify(AMD64_TEST_REG_VALUES), hexlify(data));
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testFocusProcesses() throws Throwable {
|
|
try (DummyProc np = runProc("notepad"); ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
|
|
AtomicReference<TargetObject> root = new AtomicReference<>();
|
|
AtomicReference<TargetFocusScope> scope = new AtomicReference<>();
|
|
AtomicReference<AllRequiredAccess> access = new AtomicReference<>();
|
|
AtomicReference<TargetObject> processes = new AtomicReference<>();
|
|
AtomicReference<TargetObject> obj1 = new AtomicReference<>();
|
|
AtomicReference<TargetObject> obj2 = new AtomicReference<>();
|
|
AsyncReference<List<String>, Void> focusProcPath = new AsyncReference<>();
|
|
AsyncReference<Integer, Void> processCount = new AsyncReference<>();
|
|
|
|
DebuggerModelListener procListener = new DebuggerModelListener() {
|
|
@Override
|
|
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
|
Map<String, ? extends TargetObject> added) {
|
|
processCount.set(processes.get().getCachedElements().size(), null);
|
|
}
|
|
};
|
|
DebuggerModelListener focusListener =
|
|
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
|
|
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
|
public void focusChanged(TargetObject object, TargetObject focused) {
|
|
// Truncate the path to the parent process
|
|
focusProcPath.set(focused.getPath().subList(0, 2), null);
|
|
}
|
|
};
|
|
|
|
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
|
m.init().handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting session root object");
|
|
model.fetchModelObject("Sessions", "[0]").handle(seq::next);
|
|
}, root).then(seq -> {
|
|
scope.set(root.get().as(TargetFocusScope.class));
|
|
scope.get().addListener(focusListener);
|
|
Msg.debug(this, "Tracking session access...");
|
|
DebugModelConventions.trackAccessibility(root.get()).handle(seq::next);
|
|
}, access).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, obj1).then(seq -> {
|
|
Msg.debug(this, "Attaching...");
|
|
TargetAttacher attacher = obj1.get().as(TargetAttacher.class);
|
|
attacher.attach(np.pid).handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Getting processes container");
|
|
model.fetchModelObject("Sessions", "[0]", "Processes").handle(seq::next);
|
|
}, processes).then(seq -> {
|
|
processes.get().addListener(procListener);
|
|
Msg.debug(this, "Finding TargetLauncher...");
|
|
DebugModelConventions.findSuitable(DbgModelTargetLauncher.class, root.get())
|
|
.handle(
|
|
seq::next);
|
|
}, obj2).then(seq -> {
|
|
Msg.debug(this, "Creating another process");
|
|
TargetLauncher launcher = obj2.get().as(TargetLauncher.class);
|
|
launcher.launch(Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, "notepad junk.txt"))
|
|
.handle(seq::nextIgnore);
|
|
}).then(seq -> {
|
|
Msg.debug(this, "Waiting for session access (again)...");
|
|
access.get().waitValue(true).handle(seq::next);
|
|
}).then(seq -> {
|
|
assertTrue(PathUtils.isAncestor(List.of("Sessions", "[0]", "Processes", "[1]"),
|
|
scope.get().getFocus().getPath()));
|
|
// Redundant, but verifies the listener is keeping up
|
|
assertEquals(List.of("Sessions", "[0]", "Processes", "[1]"), focusProcPath.get());
|
|
|
|
Msg.debug(this, "Requesting focus on process 0");
|
|
AsyncFence fence = new AsyncFence();
|
|
TargetObject p2 = model.getModelObject("Sessions", "[0]", "Processes", "[0]");
|
|
fence.include(focusProcPath.waitValue(p2.getPath()));
|
|
fence.include(scope.get().requestFocus(p2));
|
|
fence.ready().handle(seq::next);
|
|
}).then(seq -> {
|
|
assertTrue(PathUtils.isAncestor(List.of("Sessions", "[0]", "Processes", "[0]"),
|
|
scope.get().getFocus().getPath()));
|
|
// Redundant, but verifies the listener is keeping up
|
|
assertEquals(List.of("Sessions", "[0]", "Processes", "[0]"), focusProcPath.get());
|
|
seq.exit();
|
|
}).finish());
|
|
}
|
|
}
|
|
|
|
protected void init(ModelHost m) throws Throwable {
|
|
waitOn(m.init());
|
|
}
|
|
|
|
@Test
|
|
public void testSerializeSchema() throws Throwable {
|
|
try (ModelHost m = modelHost()) {
|
|
DebuggerObjectModel model = m.getModel();
|
|
init(m);
|
|
|
|
TargetObjectSchema rootSchema = model.getRootSchema();
|
|
String serialized = XmlSchemaContext.serialize(rootSchema.getContext());
|
|
System.out.println(serialized);
|
|
|
|
assertEquals("Debugger", rootSchema.getName().toString());
|
|
}
|
|
}
|
|
}
|