GP-2189: Add FlatDebuggerAPI interface

This commit is contained in:
Dan 2022-08-15 15:18:15 -04:00
parent 58066601fc
commit c7b464a0be
46 changed files with 4619 additions and 129 deletions

View file

@ -0,0 +1,130 @@
/* ###
* 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.
*/
//An example debugger script
//It launches the current program, places saved breakpoints, and runs it until termination.
//This script must be run from the Debugger tool, or another tool with the required plugins.
//This script has only been tested with /usr/bin/echo.
//@category Debugger
//@keybinding
//@menupath
//@toolbar
import java.util.Set;
import java.util.concurrent.TimeUnit;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer.LaunchResult;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.LogicalBreakpoint;
import ghidra.debug.flatapi.FlatDebuggerAPI;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
public class DemoDebuggerScript extends GhidraScript implements FlatDebuggerAPI {
@Override
protected void run() throws Exception {
/**
* Here we'll just launch the current program. Note that this is not guaranteed to succeed
* at all. Launching is subject to an opinion-based service. If no offers are made, this
* will fail. If the target system is missing required components, this will fail. If the
* target behaves in an unexpected way, this may fail. One example is targets without an
* initial break. If Ghidra does not recognize the target platform, this will fail. Etc.,
* etc., this may fail.
*
* In the event of failure, nothing is cleaned up automatically, since in some cases, the
* user may be expected to intervene. In our case; however, there's no way to continue this
* script on a repaired target, so we'll close the connection on failure. An alternative
* design for this script would expect the user to have already launched a target, and it
* would just operate on the "current target."
*/
println("Launching " + currentProgram);
LaunchResult result = launch(monitor);
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());
}
return;
}
Trace trace = result.recorder().getTrace();
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.
*/
flushAsyncPipelines(trace);
/**
* There is also breakpointsEnable(), but that operates on an address-by-address basis,
* which doesn't quite make sense in this case. We'll instead use getBreakpoints(Program)
* and enable them only in the new trace. The nested for is to deal with the fact that
* getBreakpoints(Program) returns a map from address to breakpoint set, i.e., a collection
* of collections.
*/
println("Enabling breakpoints");
for (Set<LogicalBreakpoint> bs : getBreakpoints(currentProgram).values()) {
for (LogicalBreakpoint lb : bs) {
println(" " + lb);
if (lb.getTraceAddress(trace) == null) {
printerr(" Not mapped!");
}
else {
waitOn(lb.enableForTrace(trace));
}
}
}
/**
* This runs the target, recording memory around the PC and SP at each break, until it
* terminates.
*/
while (isTargetAlive()) {
waitForBreak(10, TimeUnit.SECONDS);
/**
* The recorder is going to schedule some reads upon break, so let's allow them to
* settle.
*/
flushAsyncPipelines(trace);
println("Reading PC");
Address pc = getProgramCounter();
println("Reading 1024 bytes at PC=" + pc);
readMemory(pc, 1024, monitor);
println("Reading SP");
Address sp = getStackPointer();
println("Reading 8096 bytes at SP=" + sp);
readMemory(sp, 8096, monitor);
/**
* Allow the commands we just issued to settle.
*/
flushAsyncPipelines(trace);
println("Resuming");
resume();
}
println("Terminated");
}
}

View file

@ -261,6 +261,10 @@ public class DebuggerCoordinates {
return all(trace, recorder, thread, view, newTime, frame);
}
public DebuggerCoordinates withFrame(int newFrame) {
return all(trace, recorder, thread, view, time, newFrame);
}
public DebuggerCoordinates withView(TraceProgramView newView) {
return all(trace, recorder, thread, newView, time, frame);
}

View file

@ -196,7 +196,8 @@ public class DebuggerStaticSyncTrait {
}
protected void doSyncCursorIntoStatic(ProgramLocation location) {
if (location == null) {
DebuggerStaticMappingService mappingService = this.mappingService;
if (location == null || mappingService == null) {
return;
}
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
@ -207,8 +208,10 @@ public class DebuggerStaticSyncTrait {
}
protected void doSyncCursorFromStatic() {
ProgramLocation currentStaticLocation = this.currentStaticLocation;
TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap)
if (view == null || currentStaticLocation == null) {
DebuggerStaticMappingService mappingService = this.mappingService;
if (currentStaticLocation == null || view == null || mappingService == null) {
return;
}
ProgramLocation dynamicLoc =

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.breakpoint;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.swing.SwingUtilities;
@ -861,7 +862,6 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
protected void doToggleBreakpointsAt(String title, ActionContext context) {
// TODO: Seems like this should be in logical breakpoint service?
if (breakpointService == null) {
return;
}
@ -869,40 +869,22 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (loc == null) {
return;
}
Set<LogicalBreakpoint> bs = breakpointService.getBreakpointsAt(loc);
if (bs == null || bs.isEmpty()) {
breakpointService.toggleBreakpointsAt(loc, () -> {
Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context);
if (supported.isEmpty()) {
breakpointError(title, "It seems this target does not support breakpoints.");
return;
return CompletableFuture.completedFuture(Set.of());
}
Set<TraceBreakpointKind> kinds = computeDefaultKinds(context, supported);
long length = computeDefaultLength(context, kinds);
placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds, "");
return;
}
State state = breakpointService.computeState(bs, loc);
/**
* If we're in the static listing, this will return null, indicating we should use the
* program's perspective. The methods taking trace should accept a null trace and behave
* accordingly. If in the dynamic listing, we act in the context of the returned trace.
*/
Trace trace = getTraceFromContext(context);
boolean mapped = breakpointService.anyMapped(bs, trace);
State toggled = state.getToggled(mapped);
if (toggled.isEnabled()) {
breakpointService.enableAll(bs, trace).exceptionally(ex -> {
breakpointError(title, "Could not enable breakpoints", ex);
// Not great, but I'm not sticking around for the dialog
return CompletableFuture.completedFuture(Set.of());
}).exceptionally(ex -> {
breakpointError(title, "Could not toggle breakpoints", ex);
return null;
});
}
else {
breakpointService.disableAll(bs, trace).exceptionally(ex -> {
breakpointError(title, "Could not disable breakpoints", ex);
return null;
});
}
}
/**
* Instantiate a marker set for the given program or trace view

View file

@ -1492,7 +1492,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
public void performLaunch(ActionContext context) {
performAction(context, true, TargetLauncher.class, launcher -> {
Map<String, ?> args = launchOffer.getLauncherArgs(launcher.getParameters(), true);
Map<String, ?> args = launchOffer.getLauncherArgs(launcher, true);
if (args == null) {
// Cancelled
return AsyncUtils.NIL;

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.*;
import java.util.stream.Collectors;
import org.apache.commons.collections4.IteratorUtils;
@ -785,7 +784,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected void processChange(Consumer<ChangeCollector> processor, String description) {
executor.submit(() -> {
// Issue change callbacks without the lock! (try must surround sync)
// Invoke change callbacks without the lock! (try must surround sync)
try (ChangeCollector c = new ChangeCollector(changeListeners.fire)) {
synchronized (lock) {
processor.accept(c);
@ -1206,6 +1205,29 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
});
}
@Override
public CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation loc,
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer) {
Set<LogicalBreakpoint> bs = getBreakpointsAt(loc);
if (bs == null || bs.isEmpty()) {
return placer.get();
}
State state = computeState(bs, loc);
/**
* If we're in the static listing, this will return null, indicating we should use the
* program's perspective. The methods taking trace should accept a null trace and behave
* accordingly. If in the dynamic listing, we act in the context of the returned trace.
*/
Trace trace =
DebuggerLogicalBreakpointService.programOrTrace(loc, (p, a) -> null, (t, a) -> t);
boolean mapped = anyMapped(bs, trace);
State toggled = state.getToggled(mapped);
if (toggled.isEnabled()) {
return enableAll(bs, trace).thenApply(__ -> bs);
}
return disableAll(bs, trace).thenApply(__ -> bs);
}
@Override
public void processEvent(PluginEvent event) {
if (event instanceof ProgramOpenedPluginEvent) {

View file

@ -87,7 +87,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
private static final DebuggerProgramLaunchOffer DUMMY_LAUNCH_OFFER =
new DebuggerProgramLaunchOffer() {
@Override
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor,
boolean prompt, LaunchConfigurator configurator) {
throw new AssertionError("Who clicked me?");
}

View file

@ -562,9 +562,13 @@ public class DefaultTraceRecorder implements TraceRecorder {
return true;
}
// UNUSED?
@Override
public CompletableFuture<Void> flushTransactions() {
return CompletableFuture.runAsync(() -> {
}, privateQueue).thenCompose(__ -> {
return objectManager.flushEvents();
}).thenCompose(__ -> {
return parTx.flush();
});
}
}

View file

@ -285,4 +285,8 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
reorderer.dispose();
}
public CompletableFuture<Void> flushEvents() {
return reorderer.flushEvents();
}
}

View file

@ -220,6 +220,10 @@ public class TraceObjectListener implements DebuggerModelListener {
reorderer.dispose();
}
public CompletableFuture<Void> flushEvents() {
return reorderer.flushEvents();
}
/*
private CompletableFuture<List<TargetObject>> findDependenciesTop(TargetObject added) {
List<TargetObject> result = new ArrayList<>();

View file

@ -702,4 +702,10 @@ public class TraceObjectManager {
objectListener.dispose();
}
public CompletableFuture<Void> flushEvents() {
return eventListener.flushEvents().thenCompose(__ -> {
return objectListener.flushEvents();
});
}
}

View file

@ -23,7 +23,6 @@ import java.util.stream.Collectors;
import javax.swing.JOptionPane;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jdom.Element;
import org.jdom.JDOMException;
@ -35,6 +34,7 @@ import ghidra.dbg.*;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathUtils;
import ghidra.framework.model.DomainFile;
@ -276,11 +276,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param params the parameters of the model's launcher
* @return the arguments given by the user, or null if cancelled
*/
protected Map<String, ?> promptLauncherArgs(Map<String, ParameterDescription<?>> params) {
protected Map<String, ?> promptLauncherArgs(TargetLauncher launcher,
LaunchConfigurator configurator) {
TargetParameterMap params = launcher.getParameters();
DebuggerMethodInvocationDialog dialog =
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
// NB. Do not invoke read/writeConfigState
Map<String, ?> args = loadLastLauncherArgs(params, true);
Map<String, ?> args = configurator.configureLauncher(launcher,
loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE);
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
if (val != null) {
@ -311,13 +314,13 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param forPrompt true if the user will be confirming the arguments
* @return the loaded arguments, or defaults
*/
protected Map<String, ?> loadLastLauncherArgs(
Map<String, ParameterDescription<?>> params, boolean forPrompt) {
protected Map<String, ?> loadLastLauncherArgs(TargetLauncher launcher, boolean forPrompt) {
/**
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
* Re-examine this if/when that gets merged
*/
if (program != null) {
TargetParameterMap params = launcher.getParameters();
ProgramUserData userData = program.getProgramUserData();
String property =
userData.getStringProperty(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, null);
@ -354,7 +357,6 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
}
return new LinkedHashMap<>();
}
/**
@ -368,11 +370,17 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
* @param params the parameters of the model's launcher
* @return the chosen arguments, or null if the user cancels at the prompt
*/
public Map<String, ?> getLauncherArgs(Map<String, ParameterDescription<?>> params,
boolean prompt) {
public Map<String, ?> getLauncherArgs(TargetLauncher launcher,
boolean prompt, LaunchConfigurator configurator) {
return prompt
? promptLauncherArgs(params)
: loadLastLauncherArgs(params, false);
? configurator.configureLauncher(launcher,
promptLauncherArgs(launcher, configurator), RelPrompt.AFTER)
: configurator.configureLauncher(launcher, loadLastLauncherArgs(launcher, false),
RelPrompt.NONE);
}
public Map<String, ?> getLauncherArgs(TargetLauncher launcher, boolean prompt) {
return getLauncherArgs(launcher, prompt, LaunchConfigurator.NOP);
}
/**
@ -431,8 +439,9 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
}
protected CompletableFuture<DebuggerObjectModel> connect(DebuggerModelService service,
boolean prompt) {
boolean prompt, LaunchConfigurator configurator) {
DebuggerModelFactory factory = getModelFactory();
configurator.configureConnector(factory);
if (prompt) {
return service.showConnectDialog(factory);
}
@ -454,8 +463,8 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
// Eww.
protected CompletableFuture<Void> launch(TargetLauncher launcher,
boolean prompt) {
Map<String, ?> args = getLauncherArgs(launcher.getParameters(), prompt);
boolean prompt, LaunchConfigurator configurator) {
Map<String, ?> args = getLauncherArgs(launcher, prompt, configurator);
if (args == null) {
throw new CancellationException();
}
@ -551,17 +560,27 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
}
@Override
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt,
LaunchConfigurator configurator) {
DebuggerModelService service = tool.getService(DebuggerModelService.class);
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
monitor.initialize(6);
monitor.setMessage("Connecting");
var locals = new Object() {
DebuggerObjectModel model;
CompletableFuture<TargetObject> futureTarget;
TargetObject target;
TraceRecorder recorder;
Throwable exception;
LaunchResult getResult() {
return new LaunchResult(model, target, recorder, exception);
}
};
return connect(service, prompt).thenCompose(m -> {
return connect(service, prompt, configurator).thenCompose(m -> {
checkCancelled(monitor);
locals.model = m;
monitor.incrementProgress(1);
monitor.setMessage("Finding Launcher");
return AsyncTimer.DEFAULT_TIMER.mark()
@ -573,7 +592,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
monitor.setMessage("Launching");
locals.futureTarget = listenForTarget(l.getModel());
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(launch(l, prompt), getTimeoutMillis(),
.timeOut(launch(l, prompt, configurator), getTimeoutMillis(),
() -> onTimedOutLaunch(monitor));
}).thenCompose(__ -> {
checkCancelled(monitor);
@ -584,6 +603,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
() -> onTimedOutTarget(monitor));
}).thenCompose(t -> {
checkCancelled(monitor);
locals.target = t;
monitor.incrementProgress(1);
monitor.setMessage("Waiting for recorder");
return AsyncTimer.DEFAULT_TIMER.mark()
@ -591,6 +611,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
() -> onTimedOutRecorder(monitor, service, t));
}).thenCompose(r -> {
checkCancelled(monitor);
locals.recorder = r;
monitor.incrementProgress(1);
if (r == null) {
throw new CancellationException();
@ -600,10 +621,16 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
.timeOut(listenForMapping(mappingService, r), getTimeoutMillis(),
() -> onTimedOutMapping(monitor, mappingService, r));
}).exceptionally(ex -> {
if (AsyncUtils.unwrapThrowable(ex) instanceof CancellationException) {
locals.exception = AsyncUtils.unwrapThrowable(ex);
return null;
}).thenApply(__ -> {
if (locals.exception != null) {
monitor.setMessage("Launch error: " + locals.exception);
return locals.getResult();
}
return ExceptionUtils.rethrow(ex);
monitor.setMessage("Launch successful");
monitor.incrementProgress(1);
return locals.getResult();
});
}
}

View file

@ -15,11 +15,17 @@
*/
package ghidra.app.plugin.core.debug.service.model.launch;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetLauncher;
import ghidra.dbg.target.TargetObject;
import ghidra.util.task.TaskMonitor;
/**
@ -32,6 +38,89 @@ import ghidra.util.task.TaskMonitor;
*/
public interface DebuggerProgramLaunchOffer {
/**
* The result of launching a program
*
* <p>
* The launch may not always be completely successful. Instead of tearing things down, partial
* launches are left in place, in case the user wishes to repair/complete the steps manually. If
* the result includes a recorder, the launch was completed successfully. If not, then the
* caller can choose how to treat the connection and target. If the cause of failure was an
* exception, it is included. If the launch succeeded, but module mapping failed, the result
* will include a recorder and the exception.
*
* @param model the connection
* @param target the launched target
* @param recorder the recorder
* @param exception optional error, if failed
*/
public record LaunchResult(DebuggerObjectModel model, TargetObject target,
TraceRecorder recorder, Throwable exception) {
public static LaunchResult totalFailure(Throwable ex) {
return new LaunchResult(null, null, null, ex);
}
}
/**
* When programmatically customizing launch configuration, describes callback timing relative to
* prompting the user.
*/
public enum RelPrompt {
/**
* The user is not prompted for parameters. This will be the only callback.
*/
NONE,
/**
* The user will be prompted. This callback can pre-populate suggested parameters. Another
* callback will be issued if the user does not cancel.
*/
BEFORE,
/**
* The user has confirmed the parameters. This callback can validate or override the users
* parameters. Overriding the user is discouraged. This is the final callback.
*/
AFTER;
}
/**
* Callbacks for custom configuration when launching a program
*/
public interface LaunchConfigurator {
LaunchConfigurator NOP = new LaunchConfigurator() {};
/**
* Re-configure the factory, if desired
*
* @param factory the factory that will create the connection
*/
default void configureConnector(DebuggerModelFactory factory) {
}
/**
* Re-write the launcher arguments, if desired
*
* @param launcher the launcher that will create the target
* @param arguments the arguments suggested by the offer or saved settings
* @param relPrompt describes the timing of this callback relative to prompting the user
* @return the adjusted arguments
*/
default Map<String, ?> configureLauncher(TargetLauncher launcher,
Map<String, ?> arguments, RelPrompt relPrompt) {
return arguments;
}
}
/**
* Launch the program using the offered mechanism
*
* @param monitor a monitor for progress and cancellation
* @param prompt if the user should be prompted to confirm launch parameters
* @param configurator the configuration callbacks
* @return a future which completes when the program is launched
*/
CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt,
LaunchConfigurator configurator);
/**
* Launch the program using the offered mechanism
*
@ -39,7 +128,9 @@ public interface DebuggerProgramLaunchOffer {
* @param prompt if the user should be prompted to confirm launch parameters
* @return a future which completes when the program is launched
*/
CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt);
default CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt) {
return launchProgram(monitor, prompt, LaunchConfigurator.NOP);
}
/**
* A name so that this offer can be recognized later

View file

@ -599,7 +599,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
@Override
public CompletableFuture<Void> changesSettled() {
return changeDebouncer.settled();
return changeDebouncer.stable();
}
@Override

View file

@ -18,6 +18,7 @@ package ghidra.app.services;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
import ghidra.app.services.LogicalBreakpoint.State;
@ -351,4 +352,14 @@ public interface DebuggerLogicalBreakpointService {
* @return a future which completes when the command has been processed
*/
CompletableFuture<Void> deleteLocs(Collection<TraceBreakpoint> col);
/**
* Toggle the breakpoints at the given location
*
* @param location the location
* @param placer if there are no breakpoints, a routine for placing a breakpoint
* @return a future which completes when the command has been processed
*/
CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation location,
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer);
}

View file

@ -742,12 +742,13 @@ public interface LogicalBreakpoint {
* Enable (or create) this breakpoint in the given target.
*
* <p>
* Presuming the breakpoint is mappable to the given trace, if no breakpoint of the same kind
* exists at the mapped address, then this will create a new breakpoint. Note, depending on the
* debugging model, the enabled or created breakpoint may apply to more than the given trace.
* If the breakpoint already exists, it is enabled. If it's already enabled, this has no effect.
* If not, and the breakpoint is mappable to the given trace, the breakpoint is created. Note,
* depending on the debugging model, the enabled or created breakpoint may affect other targets.
* If the breakpoint is not mappable to the given trace, this has no effect.
*
* <p>
* This simply issues the command. The logical breakpoint is updated only when the resulting
* This simply issues the command(s). The logical breakpoint is updated only when the resulting
* events are processed.
*
* @param trace the trace for the given target
@ -761,7 +762,7 @@ public interface LogicalBreakpoint {
* <p>
* Note this will not create any new breakpoints. It will disable all breakpoints of the same
* kind at the mapped address. Note, depending on the debugging model, the disabled breakpoint
* may apply to more than the given trace.
* may affect other targets.
*
* <p>
* This simply issues the command. The logical breakpoint is updated only when the resulting
@ -779,7 +780,7 @@ public interface LogicalBreakpoint {
* This presumes the breakpoint's specifications are deletable. Note that if the logical
* breakpoint is still mappable into this trace, a marker may be displayed, even though no
* breakpoint is actually present. Note, depending on the debugging model, the deleted
* breakpoint may be removed from more than the given trace.
* breakpoint may be removed from other targets.
*
* This simply issues the command. The logical breakpoint is updated only when the resulting
* events are processed.
@ -794,7 +795,7 @@ public interface LogicalBreakpoint {
*
* <p>
* This affects the mapped program, if applicable, and all open and live traces. Note, depending
* on the debugging model, the enabled or created breakpoints may apply to more targets.
* on the debugging model, the enabled or created breakpoints may affect other targets.
*
* <p>
* This simply issues the command. The logical breakpoint is updated only when the resulting
@ -809,7 +810,7 @@ public interface LogicalBreakpoint {
*
* <p>
* This affects the mapped program, if applicable, and all open and live traces. Note, depending
* on the debugging model, the disabled breakpoints may apply to more targets.
* on the debugging model, the disabled breakpoints may affect other targets.
*
* <p>
* This simply issues the command. The logical breakpoint is updated only when the resulting
@ -825,7 +826,7 @@ public interface LogicalBreakpoint {
* <p>
* This presumes the breakpoint's specifications are deletable. This affects the mapped program,
* if applicable, and all open and live traces. Note, depending on the debugging model, the
* deleted breakpoints may be removed from more targets.
* deleted breakpoints may be removed from other targets.
*
* <p>
* This simply issues the command. The logical breakpoint is updated only when the resulting

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,6 @@ import generic.Unique;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
import ghidra.app.services.DebuggerModelService;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.model.TestDebuggerModelFactory;
import ghidra.framework.plugintool.PluginTool;
@ -34,10 +33,12 @@ import ghidra.util.task.TaskMonitor;
public class TestDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpinion {
static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
public static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
@Override
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
return AsyncUtils.NIL;
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt,
LaunchConfigurator configurator) {
return CompletableFuture
.completedFuture(LaunchResult.totalFailure(new AssertionError()));
}
@Override

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.service.model.record;
import static org.hamcrest.Matchers.isOneOf;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.math.BigInteger;
@ -304,7 +304,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
mb.testProcess1.memory.setMemory(tb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
flushAndWait();
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
isOneOf(null, TraceMemoryState.UNKNOWN));
is(oneOf(null, TraceMemoryState.UNKNOWN)));
byte[] data = new byte[10];
waitOn(recorder.readMemory(tb.addr(0x00400123), 10));
@ -331,7 +331,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
mb.testProcess1.memory.setMemory(tb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
flushAndWait();
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
isOneOf(null, TraceMemoryState.UNKNOWN));
is(oneOf(null, TraceMemoryState.UNKNOWN)));
byte[] data = new byte[10];
assertNull(waitOn(recorder.readMemoryBlocks(
@ -354,7 +354,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rwx");
flushAndWait();
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
isOneOf(null, TraceMemoryState.UNKNOWN));
is(oneOf(null, TraceMemoryState.UNKNOWN)));
byte[] data = new byte[10];
waitOn(recorder.writeMemory(tb.addr(0x00400123), tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9)));

File diff suppressed because it is too large Load diff

View file

@ -47,7 +47,7 @@ public class AsyncDebouncer<T> {
* Construct a new debouncer
*
* @param timer the timer to use for delay
* @param windowMillis the timing window of changes to elide
* @param windowMillis the timing window of changes to ignore
*/
public AsyncDebouncer(AsyncTimer timer, long windowMillis) {
this.timer = timer;
@ -115,7 +115,7 @@ public class AsyncDebouncer<T> {
* <p>
* The returned future completes <em>after</em> all registered listeners have been invoked.
*
* @return a future which completes with the value of the next settled event.
* @return a future which completes with the value of the next settled event
*/
public synchronized CompletableFuture<T> settled() {
if (settledPromise == null) {
@ -123,4 +123,22 @@ public class AsyncDebouncer<T> {
}
return settledPromise;
}
/**
* Wait for the debouncer to be stable
*
* <p>
* If the debouncer has not received a contact event within the event window, it's considered
* stable, and this returns a completed future with the value of the last received contact
* event. Otherwise, the returned future completes on the next settled event, as in
* {@link #settled()}.
*
* @return a future which completes, perhaps immediately, when the debouncer is stable
*/
public synchronized CompletableFuture<T> stable() {
if (alarm == null) {
return CompletableFuture.completedFuture(lastContact);
}
return settled();
}
}

View file

@ -25,12 +25,24 @@ import java.util.*;
import ghidra.dbg.target.TargetObject;
import ghidra.util.Msg;
/**
* A model listener that permits {@link AttributeCallback} annotations for convenient callbacks when
* the named attribute changes
*/
public abstract class AnnotatedDebuggerAttributeListener implements DebuggerModelListener {
private static final String ATTR_METHODS =
"@" + AttributeCallback.class.getSimpleName() + "-annotated methods";
private static final String PARAMS_ERR =
ATTR_METHODS + " must accept 2 parameters: (TargetObject, T)";
/**
* Annotation for a method receiving an attribute change callback
*
* <p>
* The annotated method must accept parameters {@code (TargetObject, T)}, where {@code T} is the
* type of the attribute. Currently, very little checks are applied during construction.
* Incorrect use will result in errors during callback invocation.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
protected @interface AttributeCallback {

View file

@ -374,4 +374,8 @@ public class DebuggerCallbackReorderer implements DebuggerModelListener {
rec.cancel();
}
}
public CompletableFuture<Void> flushEvents() {
return lastEvent.thenApply(v -> v);
}
}

View file

@ -40,12 +40,16 @@ public class TestDebuggerModelBuilder {
public TestTargetRegister testRegisterPC;
public TestTargetRegister testRegisterSP;
protected TestDebuggerObjectModel newModel(String typeHint) {
return new TestDebuggerObjectModel(typeHint);
}
public void createTestModel() {
createTestModel("Session");
}
public void createTestModel(String typeHint) {
testModel = new TestDebuggerObjectModel(typeHint);
testModel = newModel(typeHint);
}
public Address addr(long offset) {

View file

@ -16,15 +16,19 @@
package ghidra.dbg.model;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.*;
import org.jdom.JDOMException;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
// TODO: Refactor with other Fake and Test model stuff.
public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
@ -60,13 +64,145 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
this("Session");
}
public Executor getClientExecutor() {
return clientExecutor;
public TestDebuggerObjectModel(String rootHint) {
this.session = newTestTargetSession(rootHint);
addModelRoot(session);
}
public TestDebuggerObjectModel(String rootHint) {
this.session = new TestTargetSession(this, rootHint, ROOT_SCHEMA);
addModelRoot(session);
protected TestTargetSession newTestTargetSession(String rootHint) {
return new TestTargetSession(this, rootHint, ROOT_SCHEMA);
}
protected TestTargetEnvironment newTestTargetEnvironment(TestTargetSession session) {
return new TestTargetEnvironment(session);
}
protected TestTargetProcessContainer newTestTargetProcessContainer(TestTargetSession session) {
return new TestTargetProcessContainer(session);
}
protected TestTargetProcess newTestTargetProcess(TestTargetProcessContainer container, int pid,
AddressSpace space) {
return new TestTargetProcess(container, pid, space);
}
protected TestTargetBreakpointContainer newTestTargetBreakpointContainer(
TestTargetProcess process) {
return new TestTargetBreakpointContainer(process);
}
protected TestTargetBreakpoint newTestTargetBreakpoint(TestTargetBreakpointContainer container,
int num, Address address, int length, Set<TargetBreakpointKind> kinds) {
return new TestTargetBreakpoint(container, num, address, length, kinds);
}
protected TestTargetMemory newTestTargetMemory(TestTargetProcess process, AddressSpace space) {
return new TestTargetMemory(process, space);
}
protected TestTargetMemoryRegion newTestTargetMemoryRegion(TestTargetMemory memory, String name,
AddressRange range, String flags) {
return new TestTargetMemoryRegion(memory, name, range, flags);
}
protected TestTargetModuleContainer newTestTargetModuleContainer(TestTargetProcess process) {
return new TestTargetModuleContainer(process);
}
protected TestTargetModule newTestTargetModule(TestTargetModuleContainer container, String name,
AddressRange range) {
return new TestTargetModule(container, name, range);
}
protected TestTargetSectionContainer newTestTargetSectionContainer(TestTargetModule module) {
return new TestTargetSectionContainer(module);
}
protected TestTargetSection newTestTargetSection(TestTargetSectionContainer container,
String name, AddressRange range) {
return new TestTargetSection(container, name, range);
}
protected TestTargetSymbolNamespace newTestTargetSymbolNamespace(TestTargetModule module) {
return new TestTargetSymbolNamespace(module);
}
protected TestTargetSymbol newTestTargetSymbol(TestTargetSymbolNamespace namespace, String name,
Address address, long size, TargetDataType dataType) {
return new TestTargetSymbol(namespace, name, address, size, dataType);
}
protected TestTargetDataTypeNamespace newTestTargetDataTypeNamespace(TestTargetModule module) {
return new TestTargetDataTypeNamespace(module);
}
protected TestTargetTypedefDataType newTestTargetTypedefDataType(
TestTargetDataTypeNamespace namespace, String name, TargetDataType defDataType) {
return new TestTargetTypedefDataType(namespace, name, defDataType);
}
protected TestTargetTypedefDef newTestTargetTypedefDef(TestTargetTypedefDataType typedef,
TargetDataType dataType) {
return new TestTargetTypedefDef(typedef, dataType);
}
protected TestTargetRegisterContainer newTestTargetRegisterContainer(
TestTargetProcess process) {
return new TestTargetRegisterContainer(process);
}
protected TestTargetRegister newTestTargetRegister(TestTargetRegisterContainer container,
Register register) {
return TestTargetRegister.fromLanguageRegister(container, register);
}
protected TestTargetThreadContainer newTestTargetThreadContainer(TestTargetProcess process) {
return new TestTargetThreadContainer(process);
}
protected TestTargetThread newTestTargetThread(TestTargetThreadContainer container, int tid) {
return new TestTargetThread(container, tid);
}
protected TestTargetRegisterBankInThread newTestTargetRegisterBankInThread(
TestTargetThread thread) {
return new TestTargetRegisterBankInThread(thread);
}
protected TestTargetStack newTestTargetStack(TestTargetThread thread) {
return new TestTargetStack(thread);
}
protected TestTargetStackFrameNoRegisterBank newTestTargetStackFrameNoRegisterBank(
TestTargetStack stack, int level, Address pc) {
return new TestTargetStackFrameNoRegisterBank(stack, level, pc);
}
protected TestTargetStackFrameHasRegisterBank newTestTargetStackFrameHasRegisterBank(
TestTargetStack stack, int level, Address pc) {
return new TestTargetStackFrameHasRegisterBank(stack, level, pc);
}
protected TestTargetRegisterBankInFrame newTestTargetRegisterBankInFrame(
TestTargetStackFrameHasRegisterBank frame) {
return new TestTargetRegisterBankInFrame(frame);
}
protected TestTargetStackFrameIsRegisterBank newTestTargetStackFrameIsRegisterBank(
TestTargetStack stack, int level, Address pc) {
return new TestTargetStackFrameIsRegisterBank(stack, level, pc);
}
protected TestTargetInterpreter newTestTargetInterpreter(TestTargetSession session) {
return new TestTargetInterpreter(session);
}
protected TestMimickJavaLauncher newTestMimickJavaLauncher(TestTargetSession session) {
return new TestMimickJavaLauncher(session);
}
public Executor getClientExecutor() {
return clientExecutor;
}
@Override

View file

@ -56,7 +56,8 @@ public class TestTargetBreakpointContainer
@Override
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
Set<TargetBreakpointKind> kinds) {
TestTargetBreakpoint bpt = new TestTargetBreakpoint(this, counter.getAndIncrement(),
TestTargetBreakpoint bpt =
getModel().newTestTargetBreakpoint(this, counter.getAndIncrement(),
range.getMinAddress(), (int) range.getLength(), kinds);
changeElements(List.of(), List.of(bpt), "Breakpoint Added");
return getModel().future(null);

View file

@ -30,7 +30,8 @@ public class TestTargetDataTypeNamespace
public TestTargetTypedefDataType addTypedefDataType(String name,
TargetDataType defDataType) {
TestTargetTypedefDataType dataType = new TestTargetTypedefDataType(this, name, defDataType);
TestTargetTypedefDataType dataType =
getModel().newTestTargetTypedefDataType(this, name, defDataType);
changeElements(List.of(), List.of(dataType), "Added typedef " + name);
return dataType;
}

View file

@ -73,7 +73,8 @@ public class TestTargetMemory
}
public TestTargetMemoryRegion addRegion(String name, AddressRange range, String flags) {
TestTargetMemoryRegion region = new TestTargetMemoryRegion(this, name, range, flags);
TestTargetMemoryRegion region =
getModel().newTestTargetMemoryRegion(this, name, range, flags);
changeElements(List.of(), List.of(region), "Add test region: " + range);
return region;
}

View file

@ -32,9 +32,9 @@ public class TestTargetModule
public TestTargetModule(TestTargetModuleContainer parent, String name, AddressRange range) {
super(parent, PathUtils.makeKey(name), "Module");
sections = new TestTargetSectionContainer(this);
symbols = new TestTargetSymbolNamespace(this);
types = new TestTargetDataTypeNamespace(this);
sections = getModel().newTestTargetSectionContainer(this);
symbols = getModel().newTestTargetSymbolNamespace(this);
types = getModel().newTestTargetDataTypeNamespace(this);
changeAttributes(List.of(), Map.of(
RANGE_ATTRIBUTE_NAME, range,

View file

@ -29,7 +29,7 @@ public class TestTargetModuleContainer
}
public TestTargetModule addModule(String name, AddressRange range) {
TestTargetModule module = new TestTargetModule(this, name, range);
TestTargetModule module = getModel().newTestTargetModule(this, name, range);
changeElements(List.of(), List.of(module), "Add test module: " + name);
return module;
}

View file

@ -35,11 +35,11 @@ public class TestTargetProcess extends
public TestTargetProcess(DefaultTestTargetObject<?, ?> parent, int pid, AddressSpace space) {
super(parent, PathUtils.makeKey(PathUtils.makeIndex(pid)), "Process");
breaks = new TestTargetBreakpointContainer(this);
memory = new TestTargetMemory(this, space);
modules = new TestTargetModuleContainer(this);
regs = new TestTargetRegisterContainer(this);
threads = new TestTargetThreadContainer(this);
breaks = getModel().newTestTargetBreakpointContainer(this);
memory = getModel().newTestTargetMemory(this, space);
modules = getModel().newTestTargetModuleContainer(this);
regs = getModel().newTestTargetRegisterContainer(this);
threads = getModel().newTestTargetThreadContainer(this);
changeAttributes(List.of(), List.of(
breaks,

View file

@ -27,7 +27,7 @@ public class TestTargetProcessContainer
}
public TestTargetProcess addProcess(int pid, AddressSpace space) {
TestTargetProcess proc = new TestTargetProcess(this, pid, space);
TestTargetProcess proc = getModel().newTestTargetProcess(this, pid, space);
changeElements(List.of(), List.of(proc), Map.of(), "Test Process Added");
return proc;
}

View file

@ -41,14 +41,14 @@ public class TestTargetRegisterContainer
if (!predicate.test(register)) {
continue;
}
add.add(TestTargetRegister.fromLanguageRegister(this, register));
add.add(getModel().newTestTargetRegister(this, register));
}
changeElements(List.of(), add, "Added registers from Ghidra language: " + language);
return add;
}
public TestTargetRegister addRegister(Register register) {
TestTargetRegister tr = TestTargetRegister.fromLanguageRegister(this, register);
TestTargetRegister tr = getModel().newTestTargetRegister(this, register);
changeElements(List.of(), List.of(tr), "Added " + register + " from Ghidra language");
return tr;
}

View file

@ -29,7 +29,7 @@ public class TestTargetSectionContainer
}
public TestTargetSection addSection(String name, AddressRange range) {
TestTargetSection section = new TestTargetSection(this, name, range);
TestTargetSection section = getModel().newTestTargetSection(this, name, range);
changeElements(List.of(), List.of(section), "Add test section: " + name);
return section;
}

View file

@ -42,10 +42,10 @@ public class TestTargetSession extends DefaultTargetModelRoot
public TestTargetSession(TestDebuggerObjectModel model, String rootHint,
TargetObjectSchema schema) {
super(model, rootHint, schema);
environment = new TestTargetEnvironment(this);
processes = new TestTargetProcessContainer(this);
interpreter = new TestTargetInterpreter(this);
mimickJavaLauncher = new TestMimickJavaLauncher(this);
environment = model.newTestTargetEnvironment(this);
processes = model.newTestTargetProcessContainer(this);
interpreter = model.newTestTargetInterpreter(this);
mimickJavaLauncher = model.newTestMimickJavaLauncher(this);
changeAttributes(List.of(),
List.of(environment, processes, interpreter, mimickJavaLauncher), Map.of(),

View file

@ -39,7 +39,8 @@ public class TestTargetStack extends DefaultTestTargetObject<TestTargetStackFram
}
public TestTargetStackFrameNoRegisterBank pushFrameNoBank(Address pc) {
return pushFrame(new TestTargetStackFrameNoRegisterBank(this, elements.size(), pc));
return pushFrame(
getModel().newTestTargetStackFrameNoRegisterBank(this, elements.size(), pc));
}
/**
@ -48,7 +49,8 @@ public class TestTargetStack extends DefaultTestTargetObject<TestTargetStackFram
* @return the "new" highest-indexed frame, into which old data was pushed
*/
public TestTargetStackFrameHasRegisterBank pushFrameHasBank(Address pc) {
return pushFrame(new TestTargetStackFrameHasRegisterBank(this, elements.size(), pc));
return pushFrame(
getModel().newTestTargetStackFrameHasRegisterBank(this, elements.size(), pc));
}
/**
@ -57,6 +59,7 @@ public class TestTargetStack extends DefaultTestTargetObject<TestTargetStackFram
* @return the "new" highest-indexed frame, into which old data was pushed
*/
public TestTargetStackFrameIsRegisterBank pushFrameIsBank(Address pc) {
return pushFrame(new TestTargetStackFrameIsRegisterBank(this, elements.size(), pc));
return pushFrame(
getModel().newTestTargetStackFrameIsRegisterBank(this, elements.size(), pc));
}
}

View file

@ -29,7 +29,7 @@ public class TestTargetStackFrameHasRegisterBank
public TestTargetStackFrameHasRegisterBank(TestTargetStack parent, int level, Address pc) {
super(parent, PathUtils.makeKey(PathUtils.makeIndex(level)), "Frame");
bank = new TestTargetRegisterBankInFrame(this);
bank = getModel().newTestTargetRegisterBankInFrame(this);
changeAttributes(List.of(), Map.of(
bank.getName(), bank, //

View file

@ -31,7 +31,8 @@ public class TestTargetSymbolNamespace
public TestTargetSymbol addSymbol(String name, Address address, long size,
TargetDataType dataType) {
TestTargetSymbol symbol = new TestTargetSymbol(this, name, address, size, dataType);
TestTargetSymbol symbol =
getModel().newTestTargetSymbol(this, name, address, size, dataType);
changeElements(List.of(), List.of(symbol), "Added symbol " + name);
return symbol;
}

View file

@ -17,20 +17,26 @@ package ghidra.dbg.model;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetExecutionStateful;
import ghidra.dbg.target.TargetThread;
import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
public class TestTargetThread
extends DefaultTestTargetObject<TestTargetObject, TestTargetThreadContainer>
implements TargetThread, TargetExecutionStateful {
implements TargetThread, TargetExecutionStateful, TargetSteppable, TargetResumable,
TargetInterruptible, TargetKillable {
public static final TargetStepKindSet SUPPORTED_KINDS =
TargetStepKindSet.of(TargetStepKind.values());
public TestTargetThread(TestTargetThreadContainer parent, int tid) {
super(parent, PathUtils.makeKey(PathUtils.makeIndex(tid)), "Thread");
changeAttributes(List.of(), List.of(), Map.of(
STATE_ATTRIBUTE_NAME, TargetExecutionState.STOPPED //
), "Initialized");
STATE_ATTRIBUTE_NAME, TargetExecutionState.STOPPED,
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS),
"Initialized");
}
/**
@ -39,7 +45,7 @@ public class TestTargetThread
* @return the created register bank
*/
public TestTargetRegisterBankInThread addRegisterBank() {
TestTargetRegisterBankInThread regs = new TestTargetRegisterBankInThread(this);
TestTargetRegisterBankInThread regs = getModel().newTestTargetRegisterBankInThread(this);
changeAttributes(List.of(), List.of(
regs),
Map.of(), "Add Test Register Bank");
@ -47,7 +53,7 @@ public class TestTargetThread
}
public TestTargetStack addStack() {
TestTargetStack stack = new TestTargetStack(this);
TestTargetStack stack = getModel().newTestTargetStack(this);
changeAttributes(List.of(), List.of(
stack),
Map.of(), "Add Test Stack");
@ -55,8 +61,28 @@ public class TestTargetThread
}
public void setState(TargetExecutionState state) {
Delta<?, ?> delta = changeAttributes(List.of(), List.of(), Map.of(
STATE_ATTRIBUTE_NAME, state //
), "Changed state");
changeAttributes(List.of(), List.of(), Map.of(
STATE_ATTRIBUTE_NAME, state),
"Changed state");
}
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
return AsyncUtils.NIL;
}
@Override
public CompletableFuture<Void> resume() {
return AsyncUtils.NIL;
}
@Override
public CompletableFuture<Void> interrupt() {
return AsyncUtils.NIL;
}
@Override
public CompletableFuture<Void> kill() {
return AsyncUtils.NIL;
}
}

View file

@ -24,12 +24,13 @@ import ghidra.dbg.target.TargetObject;
public class TestTargetThreadContainer
extends DefaultTestTargetObject<TestTargetThread, TestTargetProcess> {
public TestTargetThreadContainer(TestTargetProcess parent) {
super(parent, "Threads", "Threads");
}
public TestTargetThread addThread(int tid) {
TestTargetThread thread = new TestTargetThread(this, tid);
TestTargetThread thread = getModel().newTestTargetThread(this, tid);
changeElements(List.of(), List.of(thread), Map.of(), "Test Thread Added");
return thread;
}

View file

@ -21,9 +21,11 @@ import ghidra.dbg.attributes.TargetDataType;
public class TestTargetTypedefDataType
extends TestTargetNamedDataType<TestTargetTypedefDef> {
public TestTargetTypedefDataType(TestTargetDataTypeNamespace parent, String name,
TargetDataType dataType) {
super(parent, name, NamedDataTypeKind.TYPEDEF, "TypedefType");
changeElements(List.of(), List.of(new TestTargetTypedefDef(this, dataType)), "Initialized");
changeElements(List.of(), List.of(getModel().newTestTargetTypedefDef(this, dataType)),
"Initialized");
}
}

View file

@ -172,7 +172,10 @@
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="ExecutionStateful" />
<!--interface name="Steppable" / -->
<interface name="Steppable" />
<interface name="Resumable" />
<interface name="Interruptible" />
<interface name="Killable" />
<element schema="VOID" />
<attribute name="_tid" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />

View file

@ -79,8 +79,9 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D
@Override
public DBTraceStaticMapping add(AddressRange range, Range<Long> lifespan, URL toProgramURL,
String toAddress)
throws TraceConflictedMappingException {
String toAddress) throws TraceConflictedMappingException {
Objects.requireNonNull(toProgramURL,
"Program URL cannot be null. Program must be in a project to have a URL.");
if (lifespan.hasLowerBound() && lifespan.lowerBoundType() != BoundType.CLOSED) {
throw new IllegalArgumentException("Lower bound must be closed");
}

View file

@ -38,6 +38,13 @@ public enum TraceBreakpointKind {
SW_EXECUTE(1 << 3);
public static class TraceBreakpointKindSet extends AbstractSetDecorator<TraceBreakpointKind> {
public static final TraceBreakpointKindSet SW_EXECUTE = of(TraceBreakpointKind.SW_EXECUTE);
public static final TraceBreakpointKindSet HW_EXECUTE = of(TraceBreakpointKind.HW_EXECUTE);
public static final TraceBreakpointKindSet READ = of(TraceBreakpointKind.READ);
public static final TraceBreakpointKindSet WRITE = of(TraceBreakpointKind.WRITE);
public static final TraceBreakpointKindSet ACCESS =
of(TraceBreakpointKind.READ, TraceBreakpointKind.WRITE);
public static TraceBreakpointKindSet of(TraceBreakpointKind... kinds) {
return new TraceBreakpointKindSet(Set.of(kinds));
}

View file

@ -412,9 +412,6 @@ public interface TraceMemoryOperations {
/**
* Search the given address range at the given snap for a given byte pattern
*
* <p>
* TODO: Implement me
*
* @param snap the time to search
* @param range the address range to search
* @param data the values to search for
@ -422,7 +419,7 @@ public interface TraceMemoryOperations {
* @param forward true to return the match with the lowest address in {@code range}, false for
* the highest address.
* @param monitor a monitor for progress reporting and canceling
* @return the address of the match, or {@code null} if not found
* @return the minimum address of the matched bytes, or {@code null} if not found
*/
Address findBytes(long snap, AddressRange range, ByteBuffer data, ByteBuffer mask,
boolean forward, TaskMonitor monitor);

View file

@ -479,6 +479,19 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
return new TraceSchedule(snap, steps.clone(), pTicks);
}
/**
* Behaves as in {@link #steppedPcodeForward(TraceThread, int)}, but by appending skips
*
* @param thread the thread to step, or null for the "last thread"
* @param pTickCount the number of p-code skips to take the thread forward
* @return the resulting schedule
*/
public TraceSchedule skippedPcodeForward(TraceThread thread, int pTickCount) {
Sequence pTicks = this.pSteps.clone();
pTicks.advance(new SkipStep(thread == null ? -1 : thread.getKey(), pTickCount));
return new TraceSchedule(snap, steps.clone(), pTicks);
}
/**
* Returns the equivalent of executing count p-code operations less than this schedule
*