mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-2189: Add FlatDebuggerAPI interface
This commit is contained in:
parent
58066601fc
commit
c7b464a0be
46 changed files with 4619 additions and 129 deletions
130
Ghidra/Debug/Debugger/ghidra_scripts/DemoDebuggerScript.java
Normal file
130
Ghidra/Debug/Debugger/ghidra_scripts/DemoDebuggerScript.java
Normal 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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,39 +869,21 @@ 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);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
breakpointService.disableAll(bs, trace).exceptionally(ex -> {
|
||||
breakpointError(title, "Could not disable breakpoints", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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?");
|
||||
}
|
||||
|
||||
|
|
|
@ -562,9 +562,13 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
return true;
|
||||
}
|
||||
|
||||
// UNUSED?
|
||||
@Override
|
||||
public CompletableFuture<Void> flushTransactions() {
|
||||
return parTx.flush();
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
}, privateQueue).thenCompose(__ -> {
|
||||
return objectManager.flushEvents();
|
||||
}).thenCompose(__ -> {
|
||||
return parTx.flush();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,4 +285,8 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
|
|||
reorderer.dispose();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> flushEvents() {
|
||||
return reorderer.flushEvents();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -702,4 +702,10 @@ public class TraceObjectManager {
|
|||
objectListener.dispose();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> flushEvents() {
|
||||
return eventListener.flushEvents().thenCompose(__ -> {
|
||||
return objectListener.flushEvents();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
return null;
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -599,7 +599,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
|||
|
||||
@Override
|
||||
public CompletableFuture<Void> changesSettled() {
|
||||
return changeDebouncer.settled();
|
||||
return changeDebouncer.stable();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -374,4 +374,8 @@ public class DebuggerCallbackReorderer implements DebuggerModelListener {
|
|||
rec.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> flushEvents() {
|
||||
return lastEvent.thenApply(v -> v);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -56,8 +56,9 @@ public class TestTargetBreakpointContainer
|
|||
@Override
|
||||
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
|
||||
Set<TargetBreakpointKind> kinds) {
|
||||
TestTargetBreakpoint bpt = new TestTargetBreakpoint(this, counter.getAndIncrement(),
|
||||
range.getMinAddress(), (int) range.getLength(), kinds);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, //
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue