mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 14:11:59 +00:00
GP-3872: Port scripting API to Trace RMI
This commit is contained in:
parent
77923fa693
commit
ad6cb5892d
@ -16,6 +16,7 @@
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
@ -35,4 +36,12 @@ public interface TraceRmiLauncherService {
|
||||
* @return the offers
|
||||
*/
|
||||
Collection<TraceRmiLaunchOffer> getOffers(Program program);
|
||||
|
||||
/**
|
||||
* Get offers with a saved configuration, ordered by most-recently-saved
|
||||
*
|
||||
* @param program the program
|
||||
* @return the offers
|
||||
*/
|
||||
List<TraceRmiLaunchOffer> getSavedOffers(Program program);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.model;
|
||||
package ghidra.debug.api.model;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Collection;
|
@ -0,0 +1,34 @@
|
||||
/* ###
|
||||
* 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.debug.api.model;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
|
||||
/**
|
||||
* Really just used by scripts to get a path into an action context
|
||||
*/
|
||||
public class DebuggerSingleObjectPathActionContext extends DefaultActionContext {
|
||||
private final TraceObjectKeyPath path;
|
||||
|
||||
public DebuggerSingleObjectPathActionContext(TraceObjectKeyPath path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public TraceObjectKeyPath getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
@ -169,6 +169,20 @@ public interface Target {
|
||||
*/
|
||||
Map<String, ActionEntry> collectActions(ActionName name, ActionContext context);
|
||||
|
||||
/**
|
||||
* Execute a command as if in the CLI
|
||||
*
|
||||
* @param command the command
|
||||
* @param toString true to capture the output and return it, false to print to the terminal
|
||||
* @return the captured output, or null if {@code toString} is false
|
||||
*/
|
||||
CompletableFuture<String> executeAsync(String command, boolean toString);
|
||||
|
||||
/**
|
||||
* @see #executeAsync(String, boolean)
|
||||
*/
|
||||
String execute(String command, boolean toString);
|
||||
|
||||
/**
|
||||
* Get the trace thread that contains the given object
|
||||
*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,656 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@Deprecated
|
||||
public interface FlatDebuggerRecorderAPI extends FlatDebuggerAPI {
|
||||
|
||||
/**
|
||||
* Get the model (legacy target) service
|
||||
*
|
||||
* @return the service
|
||||
*/
|
||||
default DebuggerModelService getModelService() {
|
||||
return requireService(DebuggerModelService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target for a given trace
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @return the target, or null if not alive
|
||||
*/
|
||||
default TargetObject getTarget(Trace trace) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(trace);
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
return recorder.getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target thread for a given trace thread
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @return the target thread, or null if not alive
|
||||
*/
|
||||
default TargetThread getTargetThread(TraceThread thread) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(thread.getTrace());
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
return recorder.getTargetThread(thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user focus for a given trace
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @return the target, or null if not alive
|
||||
*/
|
||||
default TargetObject getTargetFocus(Trace trace) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(trace);
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
TargetObject focus = recorder.getFocus();
|
||||
return focus != null ? focus : recorder.getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most suitable object related to the given object implementing the given interface
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param seed the seed object
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws ClassCastException if the model violated its schema wrt. the requested interface
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends TargetObject> T findInterface(TargetObject seed, Class<T> iface) {
|
||||
DebuggerObjectModel model = seed.getModel();
|
||||
List<String> found = model
|
||||
.getRootSchema()
|
||||
.searchForSuitable(iface, seed.getPath());
|
||||
if (found == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object value = waitOn(model.fetchModelValue(found));
|
||||
return (T) value;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most suitable object related to the given thread implementing the given interface
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param thread the thread
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws ClassCastException if the model violated its schema wrt. the requested interface
|
||||
*/
|
||||
default <T extends TargetObject> T findInterface(TraceThread thread, Class<T> iface) {
|
||||
TargetThread targetThread = getTargetThread(thread);
|
||||
if (targetThread == null) {
|
||||
return null;
|
||||
}
|
||||
return findInterface(targetThread, iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most suitable object related to the given trace's focus implementing the given
|
||||
* interface
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param trace the trace
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws ClassCastException if the model violated its schema wrt. the requested interface
|
||||
*/
|
||||
default <T extends TargetObject> T findInterface(Trace trace, Class<T> iface) {
|
||||
TargetObject focus = getTargetFocus(trace);
|
||||
if (focus == null) {
|
||||
return null;
|
||||
}
|
||||
return findInterface(focus, iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the interface related to the current thread or trace
|
||||
*
|
||||
* <p>
|
||||
* This first attempts to find the most suitable object related to the current trace thread. If
|
||||
* that fails, or if there is no current thread, it tries to find the one related to the current
|
||||
* trace (or its focus). If there is no current trace, this throws an exception.
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws IllegalStateException if there is no current trace
|
||||
*/
|
||||
default <T extends TargetObject> T findInterface(Class<T> iface) {
|
||||
TraceThread thread = getCurrentThread();
|
||||
T t = thread == null ? null : findInterface(thread, iface);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
return findInterface(requireCurrentTrace(), iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the given target object
|
||||
*
|
||||
* @param steppable the steppable target object
|
||||
* @param kind the kind of step to take
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean step(TargetSteppable steppable, TargetStepKind kind) {
|
||||
if (steppable == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(steppable.step(kind));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the given thread on target according to the given kind
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @param kind the kind of step to take
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean step(TraceThread thread, TargetStepKind kind) {
|
||||
if (thread == null) {
|
||||
return false;
|
||||
}
|
||||
return step(findInterface(thread, TargetSteppable.class), kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume execution of the given target object
|
||||
*
|
||||
* @param resumable the resumable target object
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean resume(TargetResumable resumable) {
|
||||
if (resumable == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(resumable.resume());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupt execution of the given target object
|
||||
*
|
||||
* @param interruptible the interruptible target object
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean interrupt(TargetInterruptible interruptible) {
|
||||
if (interruptible == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(interruptible.interrupt());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate execution of the given target object
|
||||
*
|
||||
* @param interruptible the interruptible target object
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean kill(TargetKillable killable) {
|
||||
if (killable == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(killable.kill());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of the given target
|
||||
*
|
||||
* <p>
|
||||
* Any invalidated object is considered {@link TargetExecutionState#TERMINATED}. Otherwise, it's
|
||||
* at least considered {@link TargetExecutionState#ALIVE}. A more specific state may be
|
||||
* determined by searching the model for the conventionally-related object implementing
|
||||
* {@link TargetObjectStateful}. This method applies this convention.
|
||||
*
|
||||
* @param target the target object
|
||||
* @return the target object's execution state
|
||||
*/
|
||||
default TargetExecutionState getExecutionState(TargetObject target) {
|
||||
if (!target.isValid()) {
|
||||
return TargetExecutionState.TERMINATED;
|
||||
}
|
||||
TargetExecutionStateful stateful = findInterface(target, TargetExecutionStateful.class);
|
||||
return stateful == null ? TargetExecutionState.ALIVE : stateful.getExecutionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given target to exit the {@link TargetExecutionState#RUNNING} state
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> There may be subtleties depending on the target debugger. For the most part, if
|
||||
* the connection is handling a single target, things will work as expected. However, if there
|
||||
* are multiple targets on one connection, it is possible for the given target to break, but for
|
||||
* the target debugger to remain unresponsive to commands. This would happen, e.g., if a second
|
||||
* target on the same connection is still running.
|
||||
*
|
||||
* @param target the target
|
||||
* @param timeout the maximum amount of time to wait
|
||||
* @param unit the units for time
|
||||
* @throws TimeoutException if the timeout expires
|
||||
*/
|
||||
default void waitForBreak(TargetObject target, long timeout, TimeUnit unit)
|
||||
throws TimeoutException {
|
||||
TargetExecutionStateful stateful = findInterface(target, TargetExecutionStateful.class);
|
||||
if (stateful == null) {
|
||||
throw new IllegalArgumentException("Given target is not stateful");
|
||||
}
|
||||
var listener = new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
||||
private void stateChanged(TargetObject parent, TargetExecutionState state) {
|
||||
if (parent == stateful && !state.isRunning()) {
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
target.getModel().addModelListener(listener);
|
||||
try {
|
||||
if (!stateful.getExecutionState().isRunning()) {
|
||||
return;
|
||||
}
|
||||
listener.future.get(timeout, unit);
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
target.getModel().removeModelListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void waitForBreak(Trace trace, long timeout, TimeUnit unit) throws TimeoutException {
|
||||
TargetObject target = getTarget(trace);
|
||||
if (target == null || !target.isValid()) {
|
||||
return;
|
||||
}
|
||||
waitForBreak(target, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a connection's interpreter, capturing the output
|
||||
*
|
||||
* <p>
|
||||
* This executes a raw command in the given interpreter. The command could have arbitrary
|
||||
* effects, so it may be necessary to wait for those effects to be handled by the tool's
|
||||
* services and plugins before proceeding.
|
||||
*
|
||||
* @param interpreter the interpreter
|
||||
* @param command the command
|
||||
* @return the output, or null if there is no interpreter
|
||||
*/
|
||||
default String executeCapture(TargetInterpreter interpreter, String command) {
|
||||
if (interpreter == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return waitOn(interpreter.executeCapture(command));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a connection's interpreter
|
||||
*
|
||||
* <p>
|
||||
* This executes a raw command in the given interpreter. The command could have arbitrary
|
||||
* effects, so it may be necessary to wait for those effects to be handled by the tool's
|
||||
* services and plugins before proceeding.
|
||||
*
|
||||
* @param interpreter the interpreter
|
||||
* @param command the command
|
||||
* @return true if successful
|
||||
*/
|
||||
default boolean execute(TargetInterpreter interpreter, String command) {
|
||||
if (interpreter == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(interpreter.executeCapture(command));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at the given path for the given model
|
||||
*
|
||||
* @param model the model
|
||||
* @param path the path
|
||||
* @return the avlue, or null if the trace is not live or if the path does not exist
|
||||
*/
|
||||
default Object getModelValue(DebuggerObjectModel model, String path) {
|
||||
try {
|
||||
return waitOn(model.fetchModelValue(PathUtils.parse(path)));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at the given path for the current trace's model
|
||||
*
|
||||
* @param path the path
|
||||
* @return the value, or null if the trace is not live or if the path does not exist
|
||||
*/
|
||||
default Object getModelValue(String path) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(getCurrentTrace());
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
return getModelValue(recorder.getTarget().getModel(), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the given objects children (elements and attributes)
|
||||
*
|
||||
* @param object the object
|
||||
* @return the set of children, excluding primitive-valued attributes
|
||||
*/
|
||||
default Set<TargetObject> refreshObjectChildren(TargetObject object) {
|
||||
try {
|
||||
// Refresh both children and memory/register values
|
||||
waitOn(object.invalidateCaches());
|
||||
waitOn(object.resync());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
Set<TargetObject> result = new LinkedHashSet<>();
|
||||
result.addAll(object.getCachedElements().values());
|
||||
for (Object v : object.getCachedAttributes().values()) {
|
||||
if (v instanceof TargetObject) {
|
||||
result.add((TargetObject) v);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the given object and its children, recursively
|
||||
*
|
||||
* <p>
|
||||
* The objects are traversed in depth-first pre-order. Links are traversed, even if the object
|
||||
* is not part of the specified subtree, but an object is skipped if it has already been
|
||||
* visited.
|
||||
*
|
||||
* @param object the seed object
|
||||
* @return true if the traversal completed successfully
|
||||
*/
|
||||
default boolean refreshSubtree(TargetObject object) {
|
||||
var util = new Object() {
|
||||
Set<TargetObject> visited = new HashSet<>();
|
||||
|
||||
boolean visit(TargetObject object) {
|
||||
if (!visited.add(object)) {
|
||||
return true;
|
||||
}
|
||||
for (TargetObject child : refreshObjectChildren(object)) {
|
||||
if (!visit(child)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return util.visit(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* This override includes flushing the recorder's event and transaction queues.
|
||||
*/
|
||||
@Override
|
||||
default boolean flushAsyncPipelines(Trace trace) {
|
||||
try {
|
||||
TraceRecorder recorder = getModelService().getRecorder(trace);
|
||||
if (recorder != null) {
|
||||
waitOn(recorder.getTarget().getModel().flushEvents());
|
||||
waitOn(recorder.flushTransactions());
|
||||
}
|
||||
trace.flushEvents();
|
||||
waitOn(getMappingService().changesSettled());
|
||||
waitOn(getBreakpointService().changesSettled());
|
||||
Swing.allowSwingToProcessEvents();
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the given program
|
||||
*
|
||||
* @param program the program
|
||||
* @return the offers
|
||||
*/
|
||||
default List<DebuggerProgramLaunchOffer> getLaunchOffers(Program program) {
|
||||
return getModelService().getProgramLaunchOffers(program).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the current program
|
||||
*
|
||||
* @return the offers
|
||||
*/
|
||||
default List<DebuggerProgramLaunchOffer> getLaunchOffers() {
|
||||
return getLaunchOffers(requireCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best launch offer for a program, throwing an exception if there is no offer
|
||||
*
|
||||
* @param program the program
|
||||
* @return the offer
|
||||
* @throws NoSuchElementException if there is no offer
|
||||
*/
|
||||
default DebuggerProgramLaunchOffer requireLaunchOffer(Program program) {
|
||||
Optional<DebuggerProgramLaunchOffer> offer =
|
||||
getModelService().getProgramLaunchOffers(program).findFirst();
|
||||
if (offer.isEmpty()) {
|
||||
throw new NoSuchElementException("No offers to launch " + program);
|
||||
}
|
||||
return offer.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer, overriding its command line
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> Most offers take a command line, but not all do. If this is used for an offer
|
||||
* that does not, it's behavior is undefined.
|
||||
*
|
||||
* <p>
|
||||
* Launches are not always successful, and may in fact fail frequently, usually because of
|
||||
* configuration errors or missing components on the target platform. This may leave stale
|
||||
* connections and/or target debuggers, processes, etc., in strange states. Furthermore, even if
|
||||
* launching the target is successful, starting the recorder may not succeed, typically because
|
||||
* Ghidra cannot identify and map the target platform to a Sleigh language. This method makes no
|
||||
* attempt at cleaning up partial pieces. Instead it returns those pieces in the launch result.
|
||||
* If the result includes a recorder, the launch was successful. If not, the script can decide
|
||||
* what to do with the other pieces. That choice depends on what is expected of the user. Can
|
||||
* the user reasonable be expected to intervene and complete the launch manually? How many
|
||||
* targets does the script intend to launch? How big is the mess if left partially completed?
|
||||
*
|
||||
* @param offer the offer (this includes the program given when asking for offers)
|
||||
* @param commandLine the command-line override. If this doesn't refer to the same program as
|
||||
* the offer, there may be unexpected results
|
||||
* @param monitor the monitor for the launch stages
|
||||
* @return the result, possibly partial
|
||||
*/
|
||||
default LaunchResult launch(DebuggerProgramLaunchOffer offer, String commandLine,
|
||||
TaskMonitor monitor) {
|
||||
try {
|
||||
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() {
|
||||
@Override
|
||||
public Map<String, ?> configureLauncher(TargetLauncher launcher,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
Map<String, Object> adjusted = new HashMap<>(arguments);
|
||||
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, commandLine);
|
||||
return adjusted;
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
// TODO: This is not ideal, since it's likely partially completed
|
||||
return LaunchResult.totalFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer with the default/saved arguments
|
||||
*
|
||||
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(DebuggerProgramLaunchOffer offer, TaskMonitor monitor) {
|
||||
try {
|
||||
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
// TODO: This is not ideal, since it's likely partially completed
|
||||
return LaunchResult.totalFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given program, overriding its command line
|
||||
*
|
||||
* <p>
|
||||
* This takes the best offer for the given program. The command line should invoke the given
|
||||
* program. If it does not, there may be unexpected results.
|
||||
*
|
||||
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(Program program, String commandLine, TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireLaunchOffer(program), commandLine, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given program with the default/saved arguments
|
||||
*
|
||||
* <p>
|
||||
* This takes the best offer for the given program.
|
||||
*
|
||||
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(Program program, TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireLaunchOffer(program), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the current program, overriding its command line
|
||||
*
|
||||
* @see #launch(Program, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(String commandLine, TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireCurrentProgram(), commandLine, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the current program with the default/saved arguments
|
||||
*
|
||||
* @see #launch(Program, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireCurrentProgram(), monitor);
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.services.TraceRmiLauncherService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public interface FlatDebuggerRmiAPI extends FlatDebuggerAPI {
|
||||
|
||||
/**
|
||||
* Get the trace-rmi launcher service
|
||||
*
|
||||
* @return the service
|
||||
*/
|
||||
default TraceRmiLauncherService getTraceRmiLauncherService() {
|
||||
return requireService(TraceRmiLauncherService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the given program
|
||||
*
|
||||
* @param program the program, or null for no image
|
||||
* @return the offers
|
||||
*/
|
||||
default Collection<TraceRmiLaunchOffer> getLaunchOffers(Program program) {
|
||||
return getTraceRmiLauncherService().getOffers(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the current program
|
||||
*
|
||||
* @return the offers
|
||||
*/
|
||||
default Collection<TraceRmiLaunchOffer> getLaunchOffers() {
|
||||
return getLaunchOffers(getCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved offers for launching the given program, ordered by most-recently-saved
|
||||
*
|
||||
* @param program the program, or null for no image
|
||||
* @return the offers
|
||||
*/
|
||||
default List<TraceRmiLaunchOffer> getSavedLaunchOffers(Program program) {
|
||||
return getTraceRmiLauncherService().getSavedOffers(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved offers for launching the current program, ordered by most-recently-saved
|
||||
*
|
||||
* @return the offers
|
||||
*/
|
||||
default List<TraceRmiLaunchOffer> getSavedLaunchOffers() {
|
||||
return getSavedLaunchOffers(getCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most-recently-saved launch offer for the given program
|
||||
*
|
||||
* @param program the program, or null for no image
|
||||
* @return the offer
|
||||
* @throws NoSuchElementException if no offer's configuration has been saved
|
||||
*/
|
||||
default TraceRmiLaunchOffer requireLastLaunchOffer(Program program) {
|
||||
List<TraceRmiLaunchOffer> offers = getSavedLaunchOffers(program);
|
||||
if (offers.isEmpty()) {
|
||||
throw new NoSuchElementException("No saved offers to launch " + program);
|
||||
}
|
||||
return offers.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most-recently-saved launch offer for the current program
|
||||
*
|
||||
* @return the offer
|
||||
* @throws NoSuchElementException if no offer's configuration has been saved
|
||||
*/
|
||||
default TraceRmiLaunchOffer requireLastLaunchOffer() {
|
||||
return requireLastLaunchOffer(getCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer with the default, saved, and/or overridden arguments
|
||||
*
|
||||
* <p>
|
||||
* If the offer has saved arguments, those will be loaded. Otherwise, the default arguments will
|
||||
* be used. If given, specific arguments can be overridden by the caller. The caller may need to
|
||||
* examine the offer's parameters before overriding any arguments. Conventionally, the argument
|
||||
* displayed as "Image" gives the path to the executable, and "Args" gives the command-line
|
||||
* arguments to pass to the target.
|
||||
*
|
||||
* @param offer the offer to launch
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @param overrideArgs overridden arguments, which may be empty
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(TraceRmiLaunchOffer offer, Map<String, ?> overrideArgs,
|
||||
TaskMonitor monitor) {
|
||||
return offer.launchProgram(monitor, new LaunchConfigurator() {
|
||||
@Override
|
||||
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
if (arguments.isEmpty()) {
|
||||
return arguments;
|
||||
}
|
||||
Map<String, Object> args = new HashMap<>(arguments);
|
||||
args.putAll(overrideArgs);
|
||||
return args;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer with the default or saved arguments
|
||||
*
|
||||
* @param offer the offer to launch
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(TraceRmiLaunchOffer offer, TaskMonitor monitor) {
|
||||
return launch(offer, Map.of(), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given program with the most-recently-saved offer
|
||||
*
|
||||
* @param program the program to launch
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(Program program, TaskMonitor monitor) {
|
||||
return launch(requireLastLaunchOffer(program), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the current program with the most-recently-saved offer
|
||||
*
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(TaskMonitor monitor) {
|
||||
return launch(requireLastLaunchOffer(), monitor);
|
||||
}
|
||||
}
|
@ -146,6 +146,15 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
this.terminalService = Objects.requireNonNull(tool.getService(TerminalService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AbstractTraceRmiLaunchOffer other = (AbstractTraceRmiLaunchOffer) obj;
|
||||
return this.getConfigName().equals(other.getConfigName());
|
||||
}
|
||||
|
||||
protected int getTimeoutMillis() {
|
||||
return DEFAULT_TIMEOUT_MILLIS;
|
||||
}
|
||||
|
@ -192,6 +192,15 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TraceRmiLaunchOffer> getSavedOffers(Program program) {
|
||||
Map<String, Long> savedConfigs = loadSavedConfigs(program);
|
||||
return getOffers(program).stream()
|
||||
.filter(o -> savedConfigs.containsKey(o.getConfigName()))
|
||||
.sorted(Comparator.comparing(o -> -savedConfigs.get(o.getConfigName())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected void executeTask(Task task) {
|
||||
ProgressService progressService = tool.getService(ProgressService.class);
|
||||
if (progressService != null) {
|
||||
|
@ -25,7 +25,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import ghidra.app.context.ProgramLocationActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.RemoteMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.service.target.AbstractTarget;
|
||||
import ghidra.app.services.DebuggerConsoleService;
|
||||
@ -38,6 +37,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.util.PathMatcher;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.dbg.util.PathPredicates.Align;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
@ -51,6 +51,7 @@ import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
|
||||
import ghidra.trace.model.stack.*;
|
||||
import ghidra.trace.model.target.*;
|
||||
@ -611,6 +612,13 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||
}
|
||||
}
|
||||
|
||||
record ExecuteMatcher(int score, List<ParamSpec> spec) implements MethodMatcher {
|
||||
static final ExecuteMatcher HAS_CMD_TOSTRING = new ExecuteMatcher(2, List.of(
|
||||
new TypeParamSpec("command", String.class),
|
||||
new TypeParamSpec("toString", Boolean.class)));
|
||||
static final List<ExecuteMatcher> ALL = matchers(HAS_CMD_TOSTRING);
|
||||
}
|
||||
|
||||
record ReadMemMatcher(int score, List<ParamSpec> spec) implements MethodMatcher {
|
||||
static final ReadMemMatcher HAS_PROC_RANGE = new ReadMemMatcher(2, List.of(
|
||||
new TypeParamSpec("process", TargetProcess.class),
|
||||
@ -841,6 +849,18 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> executeAsync(String command, boolean toString) {
|
||||
MatchedMethod execute = matches.getBest("execute", ActionName.EXECUTE, ExecuteMatcher.ALL);
|
||||
if (execute == null) {
|
||||
return CompletableFuture.failedFuture(new NoSuchElementException());
|
||||
}
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put(execute.params.get("command").name(), command);
|
||||
args.put(execute.params.get("toString").name(), toString);
|
||||
return execute.method.invokeAsync(args).toCompletableFuture().thenApply(v -> (String) v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> activateAsync(DebuggerCoordinates prev,
|
||||
DebuggerCoordinates coords) {
|
||||
@ -906,15 +926,11 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||
}
|
||||
|
||||
protected TraceObject getProcessForSpace(AddressSpace space) {
|
||||
for (TraceObjectValue objVal : trace.getObjectManager()
|
||||
.getValuesIntersecting(
|
||||
for (TraceMemoryRegion region : trace.getMemoryManager()
|
||||
.getRegionsIntersecting(
|
||||
Lifespan.at(getSnap()),
|
||||
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()),
|
||||
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME)) {
|
||||
TraceObject obj = objVal.getParent();
|
||||
if (!obj.getInterfaces().contains(TraceObjectMemoryRegion.class)) {
|
||||
continue;
|
||||
}
|
||||
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()))) {
|
||||
TraceObject obj = ((TraceObjectMemoryRegion) region).getObject();
|
||||
return obj.queryCanonicalAncestorsTargetInterface(TargetProcess.class)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
@ -20,12 +20,14 @@ import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.app.services.DebuggerTargetService;
|
||||
import ghidra.async.AsyncPairingQueue;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
@ -65,6 +67,19 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
||||
retType);
|
||||
}
|
||||
|
||||
public TestRemoteMethod(String name, ActionName action, String display, String description,
|
||||
Map<String, RemoteParameter> parameters, TargetObjectSchema retType) {
|
||||
this(name, action, display, description, parameters, retType.getName(),
|
||||
new AsyncPairingQueue<>(), new AsyncPairingQueue<>());
|
||||
}
|
||||
|
||||
public TestRemoteMethod(String name, ActionName action, String display, String description,
|
||||
TargetObjectSchema retType, RemoteParameter... parameters) {
|
||||
this(name, action, display, description, Stream.of(parameters)
|
||||
.collect(Collectors.toMap(RemoteParameter::name, p -> p)),
|
||||
retType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteAsyncResult invokeAsync(Map<String, Object> arguments) {
|
||||
argQueue.give().complete(arguments);
|
||||
@ -80,10 +95,24 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
||||
public void result(Object ret) {
|
||||
retQueue.give().complete(ret);
|
||||
}
|
||||
|
||||
public CompletableFuture<Map<String, Object>> expect(
|
||||
Function<Map<String, Object>, Object> impl) {
|
||||
record ArgsRet(Map<String, Object> args, Object ret) {
|
||||
}
|
||||
var result = argQueue().take().thenApply(a -> new ArgsRet(a, impl.apply(a)));
|
||||
result.thenApply(ar -> ar.ret).handle(AsyncUtils.copyTo(retQueue().give()));
|
||||
return result.thenApply(ar -> ar.args);
|
||||
}
|
||||
}
|
||||
|
||||
public record TestRemoteParameter(String name, SchemaName type, boolean required,
|
||||
Object defaultValue, String display, String description) implements RemoteParameter {
|
||||
public TestRemoteParameter(String name, TargetObjectSchema type, boolean required,
|
||||
Object defaultValue, String display, String description) {
|
||||
this(name, type.getName(), required, defaultValue, display, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue;
|
||||
|
@ -27,12 +27,12 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.LaunchResult;
|
||||
import ghidra.debug.flatapi.FlatDebuggerAPI;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchResult;
|
||||
import ghidra.debug.flatapi.FlatDebuggerRmiAPI;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public class DemoDebuggerScript extends GhidraScript implements FlatDebuggerAPI {
|
||||
public class DemoDebuggerScript extends GhidraScript implements FlatDebuggerRmiAPI {
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
@ -55,25 +55,19 @@ public class DemoDebuggerScript extends GhidraScript implements FlatDebuggerAPI
|
||||
if (result.exception() != null) {
|
||||
printerr("Failed to launch " + currentProgram + ": " + result.exception());
|
||||
|
||||
if (result.model() != null) {
|
||||
result.model().close();
|
||||
}
|
||||
|
||||
if (result.recorder() != null) {
|
||||
closeTrace(result.recorder().getTrace());
|
||||
}
|
||||
result.close();
|
||||
return;
|
||||
}
|
||||
Trace trace = result.recorder().getTrace();
|
||||
Trace trace = result.trace();
|
||||
println("Successfully launched in trace " + trace);
|
||||
|
||||
/**
|
||||
* Breakpoints are highly dependent on the module map. To work correctly: 1) The target
|
||||
* debugger must provide the module map. 2) Ghidra must have recorded that module map into
|
||||
* the trace. 3) Ghidra must recognize the module names and map them to programs open in the
|
||||
* tool. These events all occur asynchronously, usually immediately after launch. Most
|
||||
* launchers will wait for the target program module to be mapped to its Ghidra program
|
||||
* database, but the breakpoint service may still be processing the new mapping.
|
||||
* Breakpoints are highly dependent on the module map. To work correctly, the target
|
||||
* debugger must record the module map into the trace, and Ghidra must recognize the module
|
||||
* names and map them to programs open in the tool. These events all occur asynchronously,
|
||||
* usually immediately after launch. Most launchers will wait for the target program module
|
||||
* to be mapped to its Ghidra program database, but the breakpoint service may still be
|
||||
* processing the new mapping.
|
||||
*/
|
||||
flushAsyncPipelines(trace);
|
||||
|
||||
@ -104,8 +98,8 @@ public class DemoDebuggerScript extends GhidraScript implements FlatDebuggerAPI
|
||||
while (isTargetAlive()) {
|
||||
waitForBreak(10, TimeUnit.SECONDS);
|
||||
/**
|
||||
* The recorder is going to schedule some reads upon break, so let's allow them to
|
||||
* settle.
|
||||
* The target is going to perform some reads upon break, so let's allow them to
|
||||
* complete.
|
||||
*/
|
||||
flushAsyncPipelines(trace);
|
||||
|
||||
|
@ -20,9 +20,9 @@ import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.target.TargetEventScope.TargetEventType;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetThread;
|
||||
import ghidra.debug.flatapi.FlatDebuggerAPI;
|
||||
import ghidra.debug.flatapi.FlatDebuggerRecorderAPI;;
|
||||
|
||||
public class MonitorModelEventsScript extends GhidraScript implements FlatDebuggerAPI {
|
||||
public class MonitorModelEventsScript extends GhidraScript implements FlatDebuggerRecorderAPI {
|
||||
static DebuggerModelListener listener = new DebuggerModelListener() {
|
||||
@Override
|
||||
public void attributesChanged(TargetObject object, Collection<String> removed,
|
||||
|
@ -30,7 +30,6 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerControlService.ControlModeChangeListener;
|
||||
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
|
||||
@ -39,6 +38,7 @@ import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.emulation.DebuggerPcodeMachine;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
|
@ -29,9 +29,9 @@ import ghidra.app.context.ProgramLocationActionContext;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
|
@ -25,6 +25,7 @@ import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
|
@ -35,10 +35,10 @@ import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectRowsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider;
|
||||
import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.modules.MapProposal;
|
||||
import ghidra.debug.api.modules.RegionMapProposal;
|
||||
import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry;
|
||||
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActiva
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
|
@ -51,6 +51,7 @@ import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.*;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.RootNode;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
|
@ -28,6 +28,7 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
|
@ -41,11 +41,11 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.action.AutoMapSpec;
|
||||
import ghidra.app.plugin.core.debug.gui.action.AutoMapSpec.AutoMapSpecConfigFieldCodec;
|
||||
import ghidra.app.plugin.core.debug.gui.action.ByModuleAutoMapSpec;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.plugin.core.debug.service.model.TraceRecorderTarget;
|
||||
import ghidra.app.plugin.core.debug.service.modules.MapModulesBackgroundCommand;
|
||||
import ghidra.app.plugin.core.debug.service.modules.MapSectionsBackgroundCommand;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.modules.*;
|
||||
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
|
||||
|
@ -23,7 +23,6 @@ import java.util.function.Predicate;
|
||||
|
||||
import docking.ActionContext;
|
||||
import ghidra.app.context.ProgramLocationActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.service.target.AbstractTarget;
|
||||
import ghidra.app.services.DebuggerConsoleService;
|
||||
@ -39,7 +38,7 @@ import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.PathMatcher;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.debug.api.model.*;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@ -113,6 +112,13 @@ public class TraceRecorderTarget extends AbstractTarget {
|
||||
}
|
||||
return iface.cast(recorder.getTargetObject(suitable));
|
||||
}
|
||||
else if (context instanceof DebuggerSingleObjectPathActionContext ctx) {
|
||||
TargetObject targetObject = recorder.getTargetObject(ctx.getPath());
|
||||
if (targetObject == null) {
|
||||
return null;
|
||||
}
|
||||
return targetObject.getCachedSuitable(iface);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -469,6 +475,18 @@ public class TraceRecorderTarget extends AbstractTarget {
|
||||
return CompletableFuture.allOf(requests);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> executeAsync(String command, boolean toString) {
|
||||
TargetInterpreter interpreter = findObjectInRecorder(null, TargetInterpreter.class);
|
||||
if (interpreter == null) {
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
if (toString) {
|
||||
return interpreter.executeCapture(command);
|
||||
}
|
||||
return interpreter.execute(command).thenApply(r -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> readMemoryAsync(AddressSetView set, TaskMonitor monitor) {
|
||||
return recorder.readMemoryBlocks(set, monitor)
|
||||
|
@ -307,6 +307,11 @@ public abstract class AbstractTarget implements Target {
|
||||
getSyncMonitored(monitor, name, supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(String command, boolean toString) {
|
||||
return getSync("execute", () -> executeAsync(command, toString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(DebuggerCoordinates prev, DebuggerCoordinates coords) {
|
||||
runSync("activate", () -> activateAsync(prev, coords));
|
||||
|
@ -24,7 +24,6 @@ import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
@ -115,6 +114,16 @@ class MockTarget implements Target {
|
||||
public void invalidateMemoryCaches() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> executeAsync(String command, boolean toString) {
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(String command, boolean toString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> readMemoryAsync(AddressSetView set, TaskMonitor monitor) {
|
||||
return AsyncUtils.nil();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -127,6 +127,8 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||
|
||||
protected TestTraceRmiConnection rmiCx;
|
||||
|
||||
protected TestRemoteMethod rmiMethodExecute;
|
||||
|
||||
protected TestRemoteMethod rmiMethodResume;
|
||||
protected TestRemoteMethod rmiMethodInterrupt;
|
||||
protected TestRemoteMethod rmiMethodKill;
|
||||
@ -145,41 +147,53 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||
protected TestRemoteMethod rmiMethodReadRegs;
|
||||
protected TestRemoteMethod rmiMethodWriteReg;
|
||||
|
||||
protected TestRemoteMethod rmiMethodReadMem;
|
||||
protected TestRemoteMethod rmiMethodWriteMem;
|
||||
|
||||
protected void createRmiConnection() {
|
||||
rmiCx = new TestTraceRmiConnection();
|
||||
}
|
||||
|
||||
protected void addExecuteMethod() {
|
||||
rmiMethodExecute = new TestRemoteMethod("execute", ActionName.EXECUTE, "Execute",
|
||||
"Execut a CLI command", EnumerableTargetObjectSchema.STRING,
|
||||
new TestRemoteParameter("cmd", EnumerableTargetObjectSchema.STRING, true, null,
|
||||
"Command", "The command to execute"),
|
||||
new TestRemoteParameter("to_string", EnumerableTargetObjectSchema.BOOL, true, false,
|
||||
"To String", "Capture output to string"));
|
||||
|
||||
rmiCx.getMethods().add(rmiMethodExecute);
|
||||
}
|
||||
|
||||
protected void addControlMethods() {
|
||||
rmiMethodResume = new TestRemoteMethod("resume", ActionName.RESUME, "Resume",
|
||||
"Resume the target", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Resume the target", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process to resume"));
|
||||
|
||||
rmiMethodInterrupt = new TestRemoteMethod("interrupt", ActionName.INTERRUPT, "Interrupt",
|
||||
"Interrupt the target", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Interrupt the target", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process to interrupt"));
|
||||
|
||||
rmiMethodKill = new TestRemoteMethod("kill", ActionName.KILL, "Kill",
|
||||
"Kill the target", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Kill the target", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process to kill"));
|
||||
|
||||
rmiMethodStepInto = new TestRemoteMethod("step_into", ActionName.STEP_INTO, "Step Into",
|
||||
"Step the thread, descending into subroutines",
|
||||
EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Step the thread, descending into subroutines", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
|
||||
"The thread to step"));
|
||||
|
||||
rmiMethodStepOver = new TestRemoteMethod("step_over", ActionName.STEP_OVER, "Step Over",
|
||||
"Step the thread, without descending into subroutines",
|
||||
EnumerableTargetObjectSchema.VOID.getName(),
|
||||
EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
|
||||
"The thread to step"));
|
||||
|
||||
rmiMethodStepOut = new TestRemoteMethod("step_out", ActionName.STEP_OUT, "Step Out",
|
||||
"Allow the thread to finish the current subroutine",
|
||||
EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Allow the thread to finish the current subroutine", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
|
||||
"The thread to step"));
|
||||
|
||||
@ -194,56 +208,51 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||
|
||||
protected void addBreakpointMethods() {
|
||||
rmiMethodSetHwBreak = new TestRemoteMethod("set_hw_break", ActionName.BREAK_HW_EXECUTE,
|
||||
"Hardware Breakpoint",
|
||||
"Place a hardware execution breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Hardware Breakpoint", "Place a hardware execution breakpoint",
|
||||
EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS.getName(), true,
|
||||
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS, true,
|
||||
null, "Address", "The desired address"));
|
||||
|
||||
rmiMethodSetSwBreak = new TestRemoteMethod("set_sw_break", ActionName.BREAK_SW_EXECUTE,
|
||||
"Software Breakpoint",
|
||||
"Place a software execution breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Software Breakpoint", "Place a software execution breakpoint",
|
||||
EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS.getName(), true,
|
||||
new TestRemoteParameter("address", EnumerableTargetObjectSchema.ADDRESS, true,
|
||||
null, "Address", "The desired address"));
|
||||
|
||||
rmiMethodSetReadBreak = new TestRemoteMethod("set_read_break", ActionName.BREAK_READ,
|
||||
"Read Breakpoint",
|
||||
"Place a read breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Read Breakpoint", "Place a read breakpoint", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE.getName(), true,
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true,
|
||||
null, "Range", "The desired address range"));
|
||||
|
||||
rmiMethodSetWriteBreak = new TestRemoteMethod("set_write_break", ActionName.BREAK_WRITE,
|
||||
"Write Breakpoint",
|
||||
"Place a write breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Write Breakpoint", "Place a write breakpoint", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE.getName(), true,
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true,
|
||||
null, "Range", "The desired address range"));
|
||||
|
||||
rmiMethodSetAccessBreak = new TestRemoteMethod("set_acc_break", ActionName.BREAK_ACCESS,
|
||||
"Access Breakpoint",
|
||||
"Place an access breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Access Breakpoint", "Place an access breakpoint", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE.getName(), true,
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true,
|
||||
null, "Range", "The desired address range"));
|
||||
|
||||
rmiMethodToggleBreak = new TestRemoteMethod("toggle_break", ActionName.TOGGLE,
|
||||
"Toggle Breakpoint",
|
||||
"Toggle a breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Toggle Breakpoint", "Toggle a breakpoint", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("breakpoint", new SchemaName("BreakpointSpec"), true, null,
|
||||
"Breakpoint", "The breakpoint to toggle"),
|
||||
new TestRemoteParameter("enabled", EnumerableTargetObjectSchema.BOOL.getName(), true,
|
||||
new TestRemoteParameter("enabled", EnumerableTargetObjectSchema.BOOL, true,
|
||||
null, "Enable", "True to enable. False to disable"));
|
||||
|
||||
rmiMethodDeleteBreak = new TestRemoteMethod("delete_break", ActionName.DELETE,
|
||||
"Delete Breakpoint",
|
||||
"Delete a breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Delete Breakpoint", "Delete a breakpoint", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("breakpoint", new SchemaName("BreakpointSpec"), true, null,
|
||||
"Breakpoint", "The breakpoint to delete"));
|
||||
|
||||
@ -259,17 +268,17 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||
|
||||
protected void addRegisterMethods() {
|
||||
rmiMethodReadRegs = new TestRemoteMethod("read_regs", ActionName.REFRESH, "Read Registers",
|
||||
"Read registers", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Read registers", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("container", new SchemaName("RegisterContainer"), true, null,
|
||||
"Registers", "The registers node to read"));
|
||||
|
||||
rmiMethodWriteReg = new TestRemoteMethod("write_reg", ActionName.WRITE_REG,
|
||||
"Write Register", "Write a register", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Write Register", "Write a register", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("frame", new SchemaName("Frame"), false, 0, "Frame",
|
||||
"The frame to write to"),
|
||||
new TestRemoteParameter("name", EnumerableTargetObjectSchema.STRING.getName(), true,
|
||||
new TestRemoteParameter("name", EnumerableTargetObjectSchema.STRING, true,
|
||||
null, "Register", "The name of the register to write"),
|
||||
new TestRemoteParameter("value", EnumerableTargetObjectSchema.BYTE_ARR.getName(), true,
|
||||
new TestRemoteParameter("value", EnumerableTargetObjectSchema.BYTE_ARR, true,
|
||||
null, "Value", "The desired value"));
|
||||
|
||||
TestRemoteMethodRegistry reg = rmiCx.getMethods();
|
||||
@ -277,6 +286,28 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||
reg.add(rmiMethodWriteReg);
|
||||
}
|
||||
|
||||
protected void addMemoryMethods() {
|
||||
rmiMethodReadMem = new TestRemoteMethod("read_mem", ActionName.READ_MEM, "Read Memory",
|
||||
"Read memory", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null,
|
||||
"Process", "The process whose memory to read"),
|
||||
new TestRemoteParameter("range", EnumerableTargetObjectSchema.RANGE, true, null,
|
||||
"Range", "The address range to read"));
|
||||
|
||||
rmiMethodWriteMem = new TestRemoteMethod("write_mem", ActionName.WRITE_MEM, "Write Memory",
|
||||
"Write memory", EnumerableTargetObjectSchema.VOID,
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null,
|
||||
"Process", "The process whose memory to read"),
|
||||
new TestRemoteParameter("start", EnumerableTargetObjectSchema.ADDRESS, true, null,
|
||||
"Start", "The address to start writing"),
|
||||
new TestRemoteParameter("data", EnumerableTargetObjectSchema.BYTE_ARR, true, null,
|
||||
"Data", "The data to write"));
|
||||
|
||||
TestRemoteMethodRegistry reg = rmiCx.getMethods();
|
||||
reg.add(rmiMethodReadMem);
|
||||
reg.add(rmiMethodWriteMem);
|
||||
}
|
||||
|
||||
protected TraceObject addMemoryRegion(TraceObjectManager objs, Lifespan lifespan,
|
||||
AddressRange range, String name, String flags) {
|
||||
String pathStr =
|
||||
|
@ -0,0 +1,94 @@
|
||||
/* ###
|
||||
* 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.app.plugin.core.debug.gui.tracermi.launcher;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.debug.api.tracermi.TerminalSession;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TestTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
|
||||
|
||||
public static class TestTraceRmiLaunchOffer extends AbstractTraceRmiLaunchOffer {
|
||||
private static final ParameterDescription<String> PARAM_DESC_IMAGE =
|
||||
ParameterDescription.create(String.class, "image", true, "",
|
||||
PARAM_DISPLAY_IMAGE, "Image to execute");
|
||||
|
||||
public TestTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program) {
|
||||
super(plugin, program);
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "TEST";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Test launch offer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ParameterDescription<?>> getParameters() {
|
||||
return Map.ofEntries(Map.entry(PARAM_DESC_IMAGE.name, PARAM_DESC_IMAGE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
|
||||
Map<String, ?> args, SocketAddress address) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
|
||||
assertEquals(PromptMode.NEVER, configurator.getPromptMode());
|
||||
Map<String, ?> args =
|
||||
configurator.configureLauncher(this, loadLastLauncherArgs(false), RelPrompt.NONE);
|
||||
return new LaunchResult(program, null, null, null, null,
|
||||
new RuntimeException("Test launcher cannot launch " + args.get("image")));
|
||||
}
|
||||
|
||||
public void saveLauncherArgs(Map<String, ?> args) {
|
||||
super.saveLauncherArgs(args, getParameters());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiLaunchOffer> getOffers(TraceRmiLauncherServicePlugin plugin,
|
||||
Program program) {
|
||||
return List.of(new TestTraceRmiLaunchOffer(plugin, program));
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import db.Transaction;
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public abstract class AbstractFlatDebuggerAPITest<API extends FlatDebuggerAPI>
|
||||
extends AbstractGhidraHeadedDebuggerIntegrationTest {
|
||||
|
||||
protected DebuggerLogicalBreakpointService breakpointService;
|
||||
protected DebuggerStaticMappingService mappingService;
|
||||
protected DebuggerEmulationService emulationService;
|
||||
protected DebuggerListingService listingService;
|
||||
protected DebuggerControlService editingService;
|
||||
protected API api;
|
||||
|
||||
protected abstract API newFlatAPI();
|
||||
|
||||
@Before
|
||||
public void setUpFlatAPITest() throws Throwable {
|
||||
breakpointService = addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class);
|
||||
mappingService = tool.getService(DebuggerStaticMappingService.class);
|
||||
emulationService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
|
||||
listingService = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
editingService = addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
api = newFlatAPI();
|
||||
|
||||
// TODO: This seems to hold up the task manager.
|
||||
waitForComponentProvider(DebuggerListingProvider.class).setAutoDisassemble(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException {
|
||||
super.createProgram(lang, cSpec);
|
||||
api.getState().setCurrentProgram(program);
|
||||
}
|
||||
|
||||
protected TraceThread createTraceWithThreadAndStack(boolean open) throws Throwable {
|
||||
if (open) {
|
||||
createAndOpenTrace();
|
||||
}
|
||||
else {
|
||||
createTrace();
|
||||
}
|
||||
TraceThread thread;
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
TraceStack stack = tb.trace.getStackManager().getStack(thread, 0, true);
|
||||
stack.setDepth(3, true);
|
||||
}
|
||||
waitForSwing();
|
||||
return thread;
|
||||
}
|
||||
|
||||
protected void createTraceWithBinText() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||
mm.createRegion("Memory[bin.text]", 0, tb.range(0x00400000, 0x0040ffff),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
|
||||
mm.putBytes(0, tb.addr(0x00400000), tb.buf(1, 2, 3, 4, 5, 6, 7, 8));
|
||||
}
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
protected void createMappedTraceAndProgram() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
createProgramFromTrace();
|
||||
|
||||
intoProject(program);
|
||||
intoProject(tb.trace);
|
||||
|
||||
programManager.openProgram(program);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
try (Transaction tx = program.openTransaction("add block")) {
|
||||
program.getMemory()
|
||||
.createInitializedBlock(".text", addr(program, 0x00400000), 4096, (byte) 0,
|
||||
monitor, false);
|
||||
}
|
||||
|
||||
CompletableFuture<Void> changesSettled;
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin.text]", 0, tb.range(0x00400000, 0x00400fff),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
changesSettled = mappingService.changesSettled();
|
||||
mappingService.addIdentityMapping(tb.trace, program, Lifespan.nowOn(0), true);
|
||||
}
|
||||
waitForSwing();
|
||||
waitOn(changesSettled);
|
||||
}
|
||||
|
||||
protected Address createEmulatableProgram() throws Throwable {
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
|
||||
Address entry = addr(program, 0x00400000);
|
||||
try (Transaction start = program.openTransaction("init")) {
|
||||
program.getMemory()
|
||||
.createInitializedBlock(".text", entry, 4096, (byte) 0,
|
||||
monitor, false);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
asm.assemble(entry, "imm r0,#123");
|
||||
}
|
||||
|
||||
// Emulate launch will create a static mapping
|
||||
intoProject(program);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadMemoryBuffer() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
byte[] data = new byte[1024];
|
||||
assertEquals(1024, api.readMemory(tb.addr(0x00400000), data, monitor));
|
||||
assertArrayEquals(new byte[1024], data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadMemoryLength() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
byte[] data = api.readMemory(tb.addr(0x00400000), 1024, monitor);
|
||||
assertArrayEquals(new byte[1024], data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadRegister() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
traceManager.activateThread(thread);
|
||||
|
||||
Register r0 = tb.language.getRegister("r0");
|
||||
assertEquals(new RegisterValue(r0), api.readRegister("r0"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadRegisterInvalidNameErr() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
traceManager.activateThread(thread);
|
||||
|
||||
api.readRegister("THERE_IS_NO_SUCH_REGISTER");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadRegisters() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
Register r0 = tb.language.getRegister("r0");
|
||||
Register r1 = tb.language.getRegister("r1");
|
||||
assertEquals(List.of(
|
||||
new RegisterValue(r0),
|
||||
new RegisterValue(r1)),
|
||||
api.readRegistersNamed(List.of("r0", "r1")));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadRegistersInvalidNameErr() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
traceManager.activateThread(thread);
|
||||
|
||||
api.readRegistersNamed(Set.of("THERE_IS_NO_SUCH_REGISTER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteMemoryGivenContext() throws Throwable {
|
||||
createTraceWithBinText();
|
||||
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
|
||||
|
||||
assertTrue(api.writeMemory(tb.trace, 0, tb.addr(0x00400123), tb.arr(3, 2, 1)));
|
||||
ByteBuffer buf = ByteBuffer.allocate(3);
|
||||
assertEquals(3, tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400123), buf));
|
||||
assertArrayEquals(tb.arr(3, 2, 1), buf.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteMemoryCurrentContext() throws Throwable {
|
||||
createTraceWithBinText();
|
||||
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
|
||||
|
||||
assertTrue(api.writeMemory(tb.addr(0x00400123), tb.arr(3, 2, 1)));
|
||||
ByteBuffer buf = ByteBuffer.allocate(3);
|
||||
assertEquals(3, tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400123), buf));
|
||||
assertArrayEquals(tb.arr(3, 2, 1), buf.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteRegisterGivenContext() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(api.writeRegister(thread, 0, 0, "r0", BigInteger.valueOf(0x0102030405060708L)));
|
||||
DBTraceMemorySpace regs =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
assertNotNull(regs);
|
||||
Register r0 = tb.language.getRegister("r0");
|
||||
assertEquals(new RegisterValue(r0, BigInteger.valueOf(0x0102030405060708L)),
|
||||
regs.getValue(0, r0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteRegisterCurrentContext() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
editingService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(api.writeRegister("r0", BigInteger.valueOf(0x0102030405060708L)));
|
||||
DBTraceMemorySpace regs =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
assertNotNull(regs);
|
||||
Register r0 = tb.language.getRegister("r0");
|
||||
assertEquals(new RegisterValue(r0, BigInteger.valueOf(0x0102030405060708L)),
|
||||
regs.getValue(0, r0));
|
||||
}
|
||||
|
||||
protected void createProgramWithText() throws Throwable {
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
try (Transaction tx = program.openTransaction("Add block")) {
|
||||
program.getMemory()
|
||||
.createInitializedBlock(
|
||||
".text", addr(program, 0x00400000), 1024, (byte) 0, monitor, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createProgramWithBreakpoint() throws Throwable {
|
||||
createProgramWithText();
|
||||
|
||||
CompletableFuture<Void> changesSettled = breakpointService.changesSettled();
|
||||
waitOn(breakpointService.placeBreakpointAt(program, addr(program, 0x00400000), 1,
|
||||
Set.of(TraceBreakpointKind.SW_EXECUTE), "name"));
|
||||
waitForSwing();
|
||||
waitOn(changesSettled);
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.script.GhidraState;
|
||||
|
||||
public abstract class AbstractLiveFlatDebuggerAPITest<API extends FlatDebuggerAPI>
|
||||
extends AbstractFlatDebuggerAPITest<API> {
|
||||
|
||||
protected class TestFlatAPI implements FlatDebuggerAPI {
|
||||
protected final GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), program, null, null, null);
|
||||
|
||||
@Override
|
||||
public GhidraState getState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void runTestResume(BooleanSupplier resume) throws Throwable;
|
||||
|
||||
@Test
|
||||
public void testResumeGivenThread() throws Throwable {
|
||||
runTestResume(() -> api.resume(api.getCurrentThread()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResumeGivenTrace() throws Throwable {
|
||||
runTestResume(() -> api.resume(api.getCurrentTrace()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResume() throws Throwable {
|
||||
runTestResume(api::resume);
|
||||
}
|
||||
|
||||
protected abstract void runTestInterrupt(BooleanSupplier interrupt) throws Throwable;
|
||||
|
||||
@Test
|
||||
public void testInterruptGivenThread() throws Throwable {
|
||||
runTestInterrupt(() -> api.interrupt(api.getCurrentThread()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterruptGivenTrace() throws Throwable {
|
||||
runTestInterrupt(() -> api.interrupt(api.getCurrentTrace()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterrupt() throws Throwable {
|
||||
runTestInterrupt(api::interrupt);
|
||||
}
|
||||
|
||||
protected abstract void runTestKill(BooleanSupplier kill) throws Throwable;
|
||||
|
||||
@Test
|
||||
public void testKillGivenThread() throws Throwable {
|
||||
runTestKill(() -> api.kill(api.getCurrentThread()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKillGivenTrace() throws Throwable {
|
||||
runTestKill(() -> api.kill(api.getCurrentTrace()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKill() throws Throwable {
|
||||
runTestKill(api::kill);
|
||||
}
|
||||
|
||||
protected abstract void runTestExecuteCapture(Function<String, String> executeCapture)
|
||||
throws Throwable;
|
||||
|
||||
@Test
|
||||
public void testExecuteCaptureGivenTrace() throws Throwable {
|
||||
runTestExecuteCapture(cmd -> api.executeCapture(api.getCurrentTrace(), cmd));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteCapture() throws Throwable {
|
||||
runTestExecuteCapture(api::executeCapture);
|
||||
}
|
||||
}
|
@ -0,0 +1,539 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.Unique;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
|
||||
public class DeadFlatDebuggerAPITest extends AbstractFlatDebuggerAPITest<FlatDebuggerAPI> {
|
||||
|
||||
protected class TestFlatAPI implements FlatDebuggerAPI {
|
||||
protected final GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), program, null, null, null);
|
||||
|
||||
@Override
|
||||
public GhidraState getState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FlatDebuggerAPI newFlatAPI() {
|
||||
return new TestFlatAPI();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequireService() throws Throwable {
|
||||
assertEquals(traceManager, api.requireService(DebuggerTraceManagerService.class));
|
||||
}
|
||||
|
||||
interface NoSuchService {
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testRequireServiceAbsentErr() {
|
||||
api.requireService(NoSuchService.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentDebuggerCoordinates() throws Throwable {
|
||||
assertSame(DebuggerCoordinates.NOWHERE, api.getCurrentDebuggerCoordinates());
|
||||
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
assertEquals(DebuggerCoordinates.NOWHERE.trace(tb.trace),
|
||||
api.getCurrentDebuggerCoordinates());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentTrace() throws Throwable {
|
||||
assertNull(api.getCurrentTrace());
|
||||
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
assertEquals(tb.trace, api.getCurrentTrace());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testRequireCurrentTraceAbsentErr() {
|
||||
api.requireCurrentTrace();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentThread() throws Throwable {
|
||||
assertNull(api.getCurrentThread());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceThread thread;
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
}
|
||||
waitForSwing();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
assertEquals(thread, api.getCurrentThread());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentView() throws Throwable {
|
||||
assertNull(api.getCurrentView());
|
||||
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
assertEquals(tb.trace.getProgramView(), api.getCurrentView());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testRequireCurrentViewAbsentErr() {
|
||||
api.requireCurrentView();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentFrame() throws Throwable {
|
||||
assertEquals(0, api.getCurrentFrame());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceThread thread;
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
TraceStack stack = tb.trace.getStackManager().getStack(thread, 0, true);
|
||||
stack.setDepth(3, true);
|
||||
}
|
||||
waitForSwing();
|
||||
traceManager.activateThread(thread);
|
||||
traceManager.activateFrame(1);
|
||||
|
||||
assertEquals(1, api.getCurrentFrame());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentSnap() throws Throwable {
|
||||
assertEquals(0L, api.getCurrentSnap());
|
||||
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
traceManager.activateSnap(1);
|
||||
|
||||
assertEquals(1L, api.getCurrentSnap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentEmulationSchedule() throws Throwable {
|
||||
assertEquals(TraceSchedule.parse("0"), api.getCurrentEmulationSchedule());
|
||||
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
traceManager.activateSnap(1);
|
||||
|
||||
assertEquals(TraceSchedule.parse("1"), api.getCurrentEmulationSchedule());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTrace() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
api.activateTrace(tb.trace);
|
||||
|
||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTraceNull() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||
|
||||
api.activateTrace(null);
|
||||
assertEquals(null, traceManager.getCurrentTrace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTraceNotOpen() throws Throwable {
|
||||
createTrace();
|
||||
assertFalse(traceManager.getOpenTraces().contains(tb.trace));
|
||||
|
||||
api.activateTrace(tb.trace);
|
||||
|
||||
assertTrue(traceManager.getOpenTraces().contains(tb.trace));
|
||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentProgram() throws Throwable {
|
||||
assertEquals(null, api.getCurrentProgram());
|
||||
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
|
||||
assertEquals(program, api.getCurrentProgram());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testRequireCurrentProgramAbsentErr() throws Throwable {
|
||||
api.requireCurrentProgram();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThread() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
api.activateThread(thread);
|
||||
|
||||
assertEquals(thread, traceManager.getCurrentThread());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThreadNull() throws Throwable {
|
||||
api.activateThread(null);
|
||||
assertEquals(null, traceManager.getCurrentThread());
|
||||
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
assertEquals(thread, traceManager.getCurrentThread());
|
||||
|
||||
api.activateThread(null);
|
||||
assertNull(traceManager.getCurrentThread());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThreadNotOpen() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(false);
|
||||
assertFalse(traceManager.getOpenTraces().contains(tb.trace));
|
||||
|
||||
api.activateThread(thread);
|
||||
|
||||
assertTrue(traceManager.getOpenTraces().contains(tb.trace));
|
||||
assertEquals(thread, traceManager.getCurrentThread());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateFrame() throws Throwable {
|
||||
TraceThread thread = createTraceWithThreadAndStack(true);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
api.activateFrame(1);
|
||||
|
||||
assertEquals(1, traceManager.getCurrentFrame());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateSnap() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
api.activateSnap(1);
|
||||
|
||||
assertEquals(1L, traceManager.getCurrentSnap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentDebuggerAddress() throws Throwable {
|
||||
assertEquals(null, api.getCurrentDebuggerAddress());
|
||||
|
||||
createTraceWithBinText();
|
||||
|
||||
assertEquals(tb.addr(0x00400000), api.getCurrentDebuggerAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoToDynamic() throws Throwable {
|
||||
createTraceWithBinText();
|
||||
|
||||
assertTrue(api.goToDynamic("00400123"));
|
||||
assertEquals(tb.addr(0x00400123), listingService.getCurrentLocation().getAddress());
|
||||
|
||||
assertTrue(api.goToDynamic(tb.addr(0x00400321)));
|
||||
assertEquals(tb.addr(0x00400321), listingService.getCurrentLocation().getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTranslateStaticToDynamic() throws Throwable {
|
||||
createMappedTraceAndProgram();
|
||||
|
||||
assertEquals(api.dynamicLocation("00400123"),
|
||||
api.translateStaticToDynamic(api.staticLocation("00400123")));
|
||||
assertNull(api.translateStaticToDynamic(api.staticLocation("00600123")));
|
||||
|
||||
assertEquals(tb.addr(0x00400123), api.translateStaticToDynamic(addr(program, 0x00400123)));
|
||||
assertNull(api.translateStaticToDynamic(addr(program, 0x00600123)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTranslateDynamicToStatic() throws Throwable {
|
||||
createMappedTraceAndProgram();
|
||||
|
||||
assertEquals(api.staticLocation("00400123"),
|
||||
api.translateDynamicToStatic(api.dynamicLocation("00400123")));
|
||||
assertNull(api.translateDynamicToStatic(api.dynamicLocation("00600123")));
|
||||
|
||||
assertEquals(addr(program, 0x00400123), api.translateDynamicToStatic(tb.addr(0x00400123)));
|
||||
assertNull(api.translateDynamicToStatic(tb.addr(0x00600123)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulateLaunch() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
Trace trace = api.emulateLaunch(entry);
|
||||
assertEquals(trace, traceManager.getCurrentTrace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulate() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
api.emulateLaunch(entry);
|
||||
TraceSchedule schedule =
|
||||
traceManager.getCurrent().getTime().steppedForward(traceManager.getCurrentThread(), 1);
|
||||
api.emulate(schedule, monitor);
|
||||
|
||||
assertEquals(schedule, traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepEmuInstruction() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
api.emulateLaunch(entry);
|
||||
TraceSchedule schedule =
|
||||
traceManager.getCurrent().getTime().steppedForward(traceManager.getCurrentThread(), 1);
|
||||
|
||||
api.stepEmuInstruction(1, monitor);
|
||||
assertEquals(schedule, traceManager.getCurrent().getTime());
|
||||
|
||||
api.stepEmuInstruction(-1, monitor);
|
||||
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepEmuPcodeOp() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
api.emulateLaunch(entry);
|
||||
TraceSchedule schedule = traceManager.getCurrent()
|
||||
.getTime()
|
||||
.steppedPcodeForward(traceManager.getCurrentThread(), 1);
|
||||
|
||||
api.stepEmuPcodeOp(1, monitor);
|
||||
assertEquals(schedule, traceManager.getCurrent().getTime());
|
||||
|
||||
api.stepEmuPcodeOp(-1, monitor);
|
||||
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipEmuInstruction() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
api.emulateLaunch(entry);
|
||||
TraceSchedule schedule =
|
||||
traceManager.getCurrent().getTime().skippedForward(traceManager.getCurrentThread(), 1);
|
||||
|
||||
api.skipEmuInstruction(1, monitor);
|
||||
assertEquals(schedule, traceManager.getCurrent().getTime());
|
||||
|
||||
api.skipEmuInstruction(-1, monitor);
|
||||
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipEmuPcodeOp() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
api.emulateLaunch(entry);
|
||||
TraceSchedule schedule = traceManager.getCurrent()
|
||||
.getTime()
|
||||
.skippedPcodeForward(traceManager.getCurrentThread(), 1);
|
||||
|
||||
api.skipEmuPcodeOp(1, monitor);
|
||||
assertEquals(schedule, traceManager.getCurrent().getTime());
|
||||
|
||||
api.skipEmuPcodeOp(-1, monitor);
|
||||
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchEmu() throws Throwable {
|
||||
Address entry = createEmulatableProgram();
|
||||
|
||||
api.emulateLaunch(entry);
|
||||
TraceSchedule schedule = traceManager.getCurrent()
|
||||
.getTime()
|
||||
.patched(traceManager.getCurrentThread(),
|
||||
traceManager.getCurrentPlatform().getLanguage(), "r0=0x321");
|
||||
|
||||
api.patchEmu("r0=0x321", monitor);
|
||||
assertEquals(schedule, traceManager.getCurrent().getTime());
|
||||
|
||||
api.stepEmuInstruction(-1, monitor);
|
||||
assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchMemory() throws Throwable {
|
||||
createTraceWithBinText();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(tb.addr(0x00400003), api.searchMemory(tb.trace, 2, tb.range(0L, -1L),
|
||||
tb.arr(4, 5, 6, 7), null, true, monitor));
|
||||
assertEquals(tb.addr(0x00400003), api.searchMemory(tb.trace, 2, tb.range(0L, -1L),
|
||||
tb.arr(4, 5, 6, 7), tb.arr(-1, -1, -1, -1), true, monitor));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllBreakpoints() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
|
||||
assertEquals(1, api.getAllBreakpoints().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBreakpointsAt() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
|
||||
assertEquals(1, api.getBreakpointsAt(api.staticLocation("00400000")).size());
|
||||
assertEquals(0, api.getBreakpointsAt(api.staticLocation("00400001")).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBreakpointsNamed() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
|
||||
assertEquals(1, api.getBreakpointsNamed("name").size());
|
||||
assertEquals(0, api.getBreakpointsNamed("miss").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointsToggle() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||
|
||||
assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
|
||||
assertEquals(Set.of(lb), api.breakpointsToggle(api.staticLocation("00400000")));
|
||||
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointSetSoftwareExecute() throws Throwable {
|
||||
createProgramWithText();
|
||||
|
||||
LogicalBreakpoint lb = Unique.assertOne(
|
||||
api.breakpointSetSoftwareExecute(api.staticLocation("00400000"), "name"));
|
||||
assertEquals(addr(program, 0x00400000), lb.getAddress());
|
||||
assertEquals(TraceBreakpointKindSet.SW_EXECUTE, lb.getKinds());
|
||||
assertEquals(1, lb.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointSetHardwareExecute() throws Throwable {
|
||||
createProgramWithText();
|
||||
|
||||
LogicalBreakpoint lb = Unique.assertOne(
|
||||
api.breakpointSetHardwareExecute(api.staticLocation("00400000"), "name"));
|
||||
assertEquals(addr(program, 0x00400000), lb.getAddress());
|
||||
assertEquals(TraceBreakpointKindSet.HW_EXECUTE, lb.getKinds());
|
||||
assertEquals(1, lb.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointSetRead() throws Throwable {
|
||||
createProgramWithText();
|
||||
|
||||
LogicalBreakpoint lb = Unique.assertOne(
|
||||
api.breakpointSetRead(api.staticLocation("00400000"), 4, "name"));
|
||||
assertEquals(addr(program, 0x00400000), lb.getAddress());
|
||||
assertEquals(TraceBreakpointKindSet.READ, lb.getKinds());
|
||||
assertEquals(4, lb.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointSetWrite() throws Throwable {
|
||||
createProgramWithText();
|
||||
|
||||
LogicalBreakpoint lb = Unique.assertOne(
|
||||
api.breakpointSetWrite(api.staticLocation("00400000"), 4, "name"));
|
||||
assertEquals(addr(program, 0x00400000), lb.getAddress());
|
||||
assertEquals(TraceBreakpointKindSet.WRITE, lb.getKinds());
|
||||
assertEquals(4, lb.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointSetAccess() throws Throwable {
|
||||
createProgramWithText();
|
||||
|
||||
LogicalBreakpoint lb = Unique.assertOne(
|
||||
api.breakpointSetAccess(api.staticLocation("00400000"), 4, "name"));
|
||||
assertEquals(addr(program, 0x00400000), lb.getAddress());
|
||||
assertEquals(TraceBreakpointKindSet.ACCESS, lb.getKinds());
|
||||
assertEquals(4, lb.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointsEnable() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||
CompletableFuture<Void> changesSettled = breakpointService.changesSettled();
|
||||
waitOn(lb.disable());
|
||||
waitForSwing();
|
||||
waitOn(changesSettled);
|
||||
|
||||
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
|
||||
assertEquals(Set.of(lb), api.breakpointsEnable(api.staticLocation("00400000")));
|
||||
assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointsDisable() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||
|
||||
assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
|
||||
assertEquals(Set.of(lb), api.breakpointsDisable(api.staticLocation("00400000")));
|
||||
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakpointsClear() throws Throwable {
|
||||
createProgramWithBreakpoint();
|
||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||
|
||||
assertTrue(api.breakpointsClear(api.staticLocation("00400000")));
|
||||
assertTrue(lb.isEmpty());
|
||||
assertEquals(0, breakpointService.getAllBreakpoints().size());
|
||||
}
|
||||
}
|
@ -0,0 +1,502 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.service.model.TestDebuggerProgramLaunchOpinion.TestDebuggerProgramLaunchOffer;
|
||||
import ghidra.app.plugin.core.debug.service.model.launch.AbstractDebuggerProgramLaunchOffer;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.model.*;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.LaunchResult;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public class FlatDebuggerRecorderAPITest
|
||||
extends AbstractLiveFlatDebuggerAPITest<FlatDebuggerRecorderAPI> {
|
||||
|
||||
protected static class TestFactory implements DebuggerModelFactory {
|
||||
private final DebuggerObjectModel model;
|
||||
|
||||
public TestFactory(DebuggerObjectModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
return CompletableFuture.completedFuture(model);
|
||||
}
|
||||
}
|
||||
|
||||
protected class TestOffer extends AbstractDebuggerProgramLaunchOffer {
|
||||
public TestOffer(Program program, DebuggerModelFactory factory) {
|
||||
super(program, env.getTool(), factory);
|
||||
}
|
||||
|
||||
public TestOffer(Program program, DebuggerObjectModel model) {
|
||||
this(program, new TestFactory(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "TEST";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
return "in Test Debugger";
|
||||
}
|
||||
}
|
||||
|
||||
protected static class TestModelBuilder extends TestDebuggerModelBuilder {
|
||||
private final TestDebuggerObjectModel model;
|
||||
|
||||
public TestModelBuilder(TestDebuggerObjectModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestDebuggerObjectModel newModel(String typeHint) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
protected class TestFlatRecorderAPI extends TestFlatAPI implements FlatDebuggerRecorderAPI {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FlatDebuggerRecorderAPI newFlatAPI() {
|
||||
return new TestFlatRecorderAPI();
|
||||
}
|
||||
|
||||
protected TraceRecorder recordTarget(TargetObject target)
|
||||
throws LanguageNotFoundException, CompilerSpecNotFoundException, IOException {
|
||||
return modelService.recordTargetAndActivateTrace(target,
|
||||
new TestDebuggerTargetTraceMapper(target));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLiveMemory() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
mb.testProcess1.memory.writeMemory(mb.addr(0x00400000), mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
useTrace(recorder.getTrace());
|
||||
waitForSwing();
|
||||
|
||||
byte[] data = api.readMemory(tb.addr(0x00400000), 8, monitor);
|
||||
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLiveRegister() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
mb.createTestThreadRegisterBanks();
|
||||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), r -> true);
|
||||
mb.testBank1.writeRegister("r0", mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
useTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
|
||||
RegisterValue rv = api.readRegister("r0");
|
||||
assertEquals(BigInteger.valueOf(0x0102030405060708L), rv.getUnsignedValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLiveRegisters() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
mb.createTestThreadRegisterBanks();
|
||||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), r -> true);
|
||||
mb.testBank1.writeRegister("r0", mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
|
||||
mb.testBank1.writeRegister("r1", mb.arr(8, 7, 6, 5, 4, 3, 2, 1));
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
useTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
|
||||
Register r0 = tb.language.getRegister("r0");
|
||||
Register r1 = tb.language.getRegister("r1");
|
||||
assertEquals(List.of(
|
||||
new RegisterValue(r0, BigInteger.valueOf(0x0102030405060708L)),
|
||||
new RegisterValue(r1, BigInteger.valueOf(0x0807060504030201L))),
|
||||
api.readRegistersNamed(List.of("r0", "r1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLaunchOffers() throws Throwable {
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
Unique.assertOne(api.getLaunchOffers().stream().mapMulti((o, m) -> {
|
||||
if (o instanceof TestDebuggerProgramLaunchOffer to) {
|
||||
m.accept(to);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchCustomCommandLine() throws Throwable {
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
Map<String, ?> observedParams;
|
||||
|
||||
@Override
|
||||
protected TestTargetSession newTestTargetSession(String rootHint) {
|
||||
return new TestTargetSession(this, "Session", ROOT_SCHEMA) {
|
||||
@Override
|
||||
public CompletableFuture<Void> launch(Map<String, ?> params) {
|
||||
observedParams = params;
|
||||
throw new CancellationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
DebuggerProgramLaunchOffer offer = new TestOffer(program, model);
|
||||
|
||||
LaunchResult result = api.launch(offer, "custom command line", monitor);
|
||||
|
||||
assertEquals("custom command line",
|
||||
model.observedParams.get(TargetCmdLineLauncher.CMDLINE_ARGS_NAME));
|
||||
assertNotNull(result.model());
|
||||
assertNull(result.target());
|
||||
assertEquals(CancellationException.class, result.exception().getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTarget() throws Exception {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
|
||||
assertEquals(mb.testProcess1, api.getTarget(recorder.getTrace()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTargetThread() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
|
||||
Trace trace = recorder.getTrace();
|
||||
TraceThread thread =
|
||||
trace.getThreadManager()
|
||||
.getLiveThreadByPath(recorder.getSnap(), "Processes[1].Threads[1]");
|
||||
assertNotNull(thread);
|
||||
assertEquals(mb.testThread1, api.getTargetThread(thread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTargetFocus() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
|
||||
waitOn(mb.testModel.requestFocus(mb.testThread2));
|
||||
waitRecorder(recorder);
|
||||
|
||||
assertEquals(mb.testThread2, api.getTargetFocus(recorder.getTrace()));
|
||||
}
|
||||
|
||||
protected void runTestStep(BooleanSupplier step, TargetStepKind kind) throws Throwable {
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
TestTargetThread observedThread;
|
||||
TargetStepKind observedKind;
|
||||
|
||||
@Override
|
||||
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
|
||||
int tid) {
|
||||
return new TestTargetThread(container, tid) {
|
||||
@Override
|
||||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||
observedThread = this;
|
||||
observedKind = kind;
|
||||
return super.step(kind);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||
waitRecorder(recorder);
|
||||
|
||||
assertTrue(step.getAsBoolean());
|
||||
waitRecorder(recorder);
|
||||
assertEquals(mb.testThread2, model.observedThread);
|
||||
assertEquals(kind, model.observedKind);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepGivenThread() throws Throwable {
|
||||
runTestStep(() -> api.step(api.getCurrentThread(), TargetStepKind.INTO),
|
||||
TargetStepKind.INTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepInto() throws Throwable {
|
||||
runTestStep(api::stepInto, TargetStepKind.INTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOver() throws Throwable {
|
||||
runTestStep(api::stepOver, TargetStepKind.OVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOut() throws Throwable {
|
||||
runTestStep(api::stepOut, TargetStepKind.FINISH);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestResume(BooleanSupplier resume) throws Throwable {
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
TestTargetThread observedThread;
|
||||
|
||||
@Override
|
||||
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
|
||||
int tid) {
|
||||
return new TestTargetThread(container, tid) {
|
||||
@Override
|
||||
public CompletableFuture<Void> resume() {
|
||||
observedThread = this;
|
||||
return super.resume();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||
waitRecorder(recorder);
|
||||
|
||||
assertTrue(resume.getAsBoolean());
|
||||
waitRecorder(recorder);
|
||||
assertEquals(mb.testThread2, model.observedThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestInterrupt(BooleanSupplier interrupt) throws Throwable {
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
TestTargetThread observedThread;
|
||||
|
||||
@Override
|
||||
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
|
||||
int tid) {
|
||||
return new TestTargetThread(container, tid) {
|
||||
@Override
|
||||
public CompletableFuture<Void> interrupt() {
|
||||
observedThread = this;
|
||||
return super.interrupt();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||
waitRecorder(recorder);
|
||||
|
||||
assertTrue(interrupt.getAsBoolean());
|
||||
waitRecorder(recorder);
|
||||
assertEquals(mb.testThread2, model.observedThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestKill(BooleanSupplier kill) throws Throwable {
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
TestTargetThread observedThread;
|
||||
|
||||
@Override
|
||||
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
|
||||
int tid) {
|
||||
return new TestTargetThread(container, tid) {
|
||||
@Override
|
||||
public CompletableFuture<Void> kill() {
|
||||
observedThread = this;
|
||||
return super.kill();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||
waitRecorder(recorder);
|
||||
|
||||
assertTrue(kill.getAsBoolean());
|
||||
waitRecorder(recorder);
|
||||
assertEquals(mb.testThread2, model.observedThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestExecuteCapture(Function<String, String> executeCapture) throws Throwable {
|
||||
// NOTE: Can't use TestTargetInterpreter.queueExecute stuff, since flat API waits
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
@Override
|
||||
protected TestTargetInterpreter newTestTargetInterpreter(TestTargetSession session) {
|
||||
return new TestTargetInterpreter(session) {
|
||||
@Override
|
||||
public CompletableFuture<String> executeCapture(String cmd) {
|
||||
return CompletableFuture.completedFuture("Response to " + cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
waitRecorder(recorder);
|
||||
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||
waitRecorder(recorder);
|
||||
|
||||
assertEquals("Response to cmd", executeCapture.apply("cmd"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetModelValue() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
recordTarget(mb.testProcess1);
|
||||
|
||||
assertEquals(mb.testThread2, api.getModelValue("Processes[1].Threads[2]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshObjectChildren() throws Throwable {
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
Set<TestTargetProcess> observed = new HashSet<>();
|
||||
|
||||
@Override
|
||||
protected TestTargetProcess newTestTargetProcess(TestTargetProcessContainer container,
|
||||
int pid, AddressSpace space) {
|
||||
return new TestTargetProcess(container, pid, space) {
|
||||
@Override
|
||||
public CompletableFuture<Void> resync(RefreshBehavior refreshAttributes,
|
||||
RefreshBehavior refreshElements) {
|
||||
observed.add(this);
|
||||
return super.resync(refreshAttributes, refreshElements);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
api.refreshObjectChildren(mb.testProcess1);
|
||||
assertEquals(Set.of(mb.testProcess1), model.observed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshSubtree() throws Throwable {
|
||||
var model = new TestDebuggerObjectModel() {
|
||||
Set<TestTargetObject> observed = new HashSet<>();
|
||||
|
||||
@Override
|
||||
protected TestTargetProcess newTestTargetProcess(TestTargetProcessContainer container,
|
||||
int pid, AddressSpace space) {
|
||||
return new TestTargetProcess(container, pid, space) {
|
||||
@Override
|
||||
public CompletableFuture<Void> resync(RefreshBehavior refreshAttributes,
|
||||
RefreshBehavior refreshElements) {
|
||||
observed.add(this);
|
||||
return super.resync(refreshAttributes, refreshElements);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container,
|
||||
int tid) {
|
||||
return new TestTargetThread(container, tid) {
|
||||
@Override
|
||||
public CompletableFuture<Void> resync(RefreshBehavior refreshAttributes,
|
||||
RefreshBehavior refreshElements) {
|
||||
observed.add(this);
|
||||
return super.resync(refreshAttributes, refreshElements);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
mb = new TestModelBuilder(model);
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
api.refreshSubtree(mb.testModel.session);
|
||||
assertEquals(Set.of(mb.testProcess1, mb.testProcess3, mb.testThread1, mb.testThread2,
|
||||
mb.testThread3, mb.testThread4), model.observed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushAsyncPipelines() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = recordTarget(mb.testProcess1);
|
||||
|
||||
// Ensure it works whether or not there are pending events
|
||||
for (int i = 0; i < 10; i++) {
|
||||
api.flushAsyncPipelines(recorder.getTrace());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TestTraceRmiLaunchOpinion.TestTraceRmiLaunchOffer;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchResult;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.database.target.DBTraceObjectManager;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public class FlatDebuggerRmiAPITest extends AbstractLiveFlatDebuggerAPITest<FlatDebuggerRmiAPI> {
|
||||
|
||||
protected TraceRmiLauncherServicePlugin rmiLaunchPlugin;
|
||||
|
||||
protected class TestFlatRmiAPI extends TestFlatAPI implements FlatDebuggerRmiAPI {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUpRmiTest() throws Throwable {
|
||||
rmiLaunchPlugin = addPlugin(tool, TraceRmiLauncherServicePlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FlatDebuggerRmiAPI newFlatAPI() {
|
||||
return new TestFlatRmiAPI();
|
||||
}
|
||||
|
||||
protected TraceRmiTarget createTarget() throws Throwable {
|
||||
createRmiConnection();
|
||||
addExecuteMethod();
|
||||
addControlMethods();
|
||||
addMemoryMethods();
|
||||
addRegisterMethods();
|
||||
createTrace();
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
DBTraceObjectManager objs = tb.trace.getObjectManager();
|
||||
objs.createRootObject(SCHEMA_SESSION);
|
||||
tb.createObjectsProcessAndThreads();
|
||||
tb.createObjectsFramesAndRegs(
|
||||
tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class),
|
||||
Lifespan.nowOn(0), tb.host, 2);
|
||||
addMemoryRegion(objs, Lifespan.nowOn(0), tb.range(0x00400000, 0x00400fff), ".text",
|
||||
"rx");
|
||||
}
|
||||
TraceRmiTarget target = rmiCx.publishTarget(tool, tb.trace);
|
||||
traceManager.openTrace(tb.trace);
|
||||
// Do not activate, as this pollutes the method invocation queues
|
||||
waitForSwing();
|
||||
return target;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLiveMemory() throws Throwable {
|
||||
TraceRmiTarget target = createTarget();
|
||||
var args = rmiMethodReadMem.expect(a -> {
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
tb.trace.getMemoryManager()
|
||||
.putBytes(target.getSnap(), tb.addr(0x00400000),
|
||||
tb.buf(1, 2, 3, 4, 5, 6, 7, 8));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
byte[] data = api.readMemory(tb.trace, target.getSnap(), tb.addr(0x00400000), 8, monitor);
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("process", tb.obj("Processes[1]")),
|
||||
// Framework quantizes to page
|
||||
Map.entry("range", tb.range(0x00400000, 0x00400fff))),
|
||||
waitOn(args));
|
||||
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLiveRegister() throws Throwable {
|
||||
TraceRmiTarget target = createTarget();
|
||||
TraceThread thread =
|
||||
tb.trace.getThreadManager().getLiveThreadByPath(0, "Processes[1].Threads[1]");
|
||||
Register r0 = tb.reg("r0");
|
||||
var args = rmiMethodReadRegs.expect(a -> {
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
DBTraceMemorySpace regs =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
|
||||
regs.setValue(target.getSnap(), new RegisterValue(r0, new BigInteger("1234")));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
RegisterValue value = api.readRegister(tb.host, thread, 0, target.getSnap(), r0);
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("container", tb.obj("Processes[1].Threads[1].Stack[0].Registers"))),
|
||||
waitOn(args));
|
||||
assertEquals(new RegisterValue(r0, new BigInteger("1234")), value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLiveRegisters() throws Throwable {
|
||||
TraceRmiTarget target = createTarget();
|
||||
TraceThread thread =
|
||||
tb.trace.getThreadManager().getLiveThreadByPath(0, "Processes[1].Threads[1]");
|
||||
Register r0 = tb.reg("r0");
|
||||
Register r1 = tb.reg("r1");
|
||||
var args = rmiMethodReadRegs.expect(a -> {
|
||||
try (Transaction tx = tb.startTransaction()) {
|
||||
DBTraceMemorySpace regs =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
|
||||
regs.setValue(target.getSnap(), new RegisterValue(r0, new BigInteger("1234")));
|
||||
regs.setValue(target.getSnap(), new RegisterValue(r1, new BigInteger("5678")));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
List<RegisterValue> values =
|
||||
api.readRegisters(tb.host, thread, 0, target.getSnap(), List.of(r0, r1));
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("container", tb.obj("Processes[1].Threads[1].Stack[0].Registers"))),
|
||||
waitOn(args));
|
||||
assertEquals(List.of(
|
||||
new RegisterValue(r0, new BigInteger("1234")),
|
||||
new RegisterValue(r1, new BigInteger("5678"))),
|
||||
values);
|
||||
}
|
||||
|
||||
protected <T, U extends T> List<U> filter(Collection<T> col, Class<U> cls) {
|
||||
return col.stream().<U> mapMulti((e, m) -> {
|
||||
if (cls.isInstance(e)) {
|
||||
m.accept(cls.cast(e));
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLaunchOffers() throws Throwable {
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
TestTraceRmiLaunchOffer offer =
|
||||
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
|
||||
assertEquals(program, offer.getProgram());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSavedLaunchOffers() throws Throwable {
|
||||
createProgram();
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(List.of(), api.getSavedLaunchOffers());
|
||||
|
||||
TestTraceRmiLaunchOffer offer =
|
||||
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
|
||||
offer.saveLauncherArgs(Map.of("image", "/test/image"));
|
||||
|
||||
assertEquals(List.of(offer), api.getSavedLaunchOffers());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchCustomCommandLine() throws Throwable {
|
||||
TestTraceRmiLaunchOffer offer =
|
||||
Unique.assertOne(filter(api.getLaunchOffers(), TestTraceRmiLaunchOffer.class));
|
||||
offer.saveLauncherArgs(Map.of("image", "/test/image"));
|
||||
|
||||
LaunchResult result = api.launch(monitor);
|
||||
assertEquals("Test launcher cannot launch /test/image", result.exception().getMessage());
|
||||
}
|
||||
|
||||
protected void runTestStep(Predicate<TraceThread> step, Supplier<TestRemoteMethod> method)
|
||||
throws Throwable {
|
||||
createTarget();
|
||||
TraceObjectThread thread =
|
||||
tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
var args = method.get().expect(a -> null);
|
||||
assertTrue(step.test(thread));
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("thread", thread.getObject())),
|
||||
waitOn(args));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepGivenThread() throws Throwable {
|
||||
runTestStep(api::stepInto, () -> rmiMethodStepInto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepInto() throws Throwable {
|
||||
runTestStep(t -> api.stepInto(), () -> rmiMethodStepInto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOver() throws Throwable {
|
||||
runTestStep(t -> api.stepOver(), () -> rmiMethodStepOver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOut() throws Throwable {
|
||||
runTestStep(t -> api.stepOut(), () -> rmiMethodStepOut);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestResume(BooleanSupplier resume) throws Throwable {
|
||||
createTarget();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
var args = rmiMethodResume.expect(a -> null);
|
||||
assertTrue(resume.getAsBoolean());
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("process", tb.obj("Processes[1]"))),
|
||||
waitOn(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestInterrupt(BooleanSupplier interrupt) throws Throwable {
|
||||
createTarget();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
var args = rmiMethodInterrupt.expect(a -> null);
|
||||
assertTrue(interrupt.getAsBoolean());
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("process", tb.obj("Processes[1]"))),
|
||||
waitOn(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestKill(BooleanSupplier kill) throws Throwable {
|
||||
createTarget();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
var args = rmiMethodKill.expect(a -> null);
|
||||
assertTrue(kill.getAsBoolean());
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("process", tb.obj("Processes[1]"))),
|
||||
waitOn(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTestExecuteCapture(Function<String, String> executeCapture) throws Throwable {
|
||||
createTarget();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
var args = rmiMethodExecute.expect(a -> "result");
|
||||
assertEquals("result", api.executeCapture("some command"));
|
||||
assertEquals(Map.ofEntries(
|
||||
Map.entry("cmd", "some command"),
|
||||
Map.entry("to_string", true)),
|
||||
waitOn(args));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user