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);
|
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) {
|
public DebuggerCoordinates withView(TraceProgramView newView) {
|
||||||
return all(trace, recorder, thread, newView, time, frame);
|
return all(trace, recorder, thread, newView, time, frame);
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,8 @@ public class DebuggerStaticSyncTrait {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSyncCursorIntoStatic(ProgramLocation location) {
|
protected void doSyncCursorIntoStatic(ProgramLocation location) {
|
||||||
if (location == null) {
|
DebuggerStaticMappingService mappingService = this.mappingService;
|
||||||
|
if (location == null || mappingService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
|
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
|
||||||
|
@ -207,8 +208,10 @@ public class DebuggerStaticSyncTrait {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSyncCursorFromStatic() {
|
protected void doSyncCursorFromStatic() {
|
||||||
|
ProgramLocation currentStaticLocation = this.currentStaticLocation;
|
||||||
TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation dynamicLoc =
|
ProgramLocation dynamicLoc =
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.breakpoint;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
@ -861,7 +862,6 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doToggleBreakpointsAt(String title, ActionContext context) {
|
protected void doToggleBreakpointsAt(String title, ActionContext context) {
|
||||||
// TODO: Seems like this should be in logical breakpoint service?
|
|
||||||
if (breakpointService == null) {
|
if (breakpointService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -869,40 +869,22 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
||||||
if (loc == null) {
|
if (loc == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Set<LogicalBreakpoint> bs = breakpointService.getBreakpointsAt(loc);
|
breakpointService.toggleBreakpointsAt(loc, () -> {
|
||||||
if (bs == null || bs.isEmpty()) {
|
|
||||||
Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context);
|
Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context);
|
||||||
if (supported.isEmpty()) {
|
if (supported.isEmpty()) {
|
||||||
breakpointError(title, "It seems this target does not support breakpoints.");
|
breakpointError(title, "It seems this target does not support breakpoints.");
|
||||||
return;
|
return CompletableFuture.completedFuture(Set.of());
|
||||||
}
|
}
|
||||||
Set<TraceBreakpointKind> kinds = computeDefaultKinds(context, supported);
|
Set<TraceBreakpointKind> kinds = computeDefaultKinds(context, supported);
|
||||||
long length = computeDefaultLength(context, kinds);
|
long length = computeDefaultLength(context, kinds);
|
||||||
placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds, "");
|
placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds, "");
|
||||||
return;
|
// Not great, but I'm not sticking around for the dialog
|
||||||
}
|
return CompletableFuture.completedFuture(Set.of());
|
||||||
State state = breakpointService.computeState(bs, loc);
|
}).exceptionally(ex -> {
|
||||||
/**
|
breakpointError(title, "Could not toggle breakpoints", ex);
|
||||||
* 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;
|
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
|
* Instantiate a marker set for the given program or trace view
|
||||||
|
|
|
@ -1492,7 +1492,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
public void performLaunch(ActionContext context) {
|
public void performLaunch(ActionContext context) {
|
||||||
performAction(context, true, TargetLauncher.class, launcher -> {
|
performAction(context, true, TargetLauncher.class, launcher -> {
|
||||||
|
|
||||||
Map<String, ?> args = launchOffer.getLauncherArgs(launcher.getParameters(), true);
|
Map<String, ?> args = launchOffer.getLauncherArgs(launcher, true);
|
||||||
if (args == null) {
|
if (args == null) {
|
||||||
// Cancelled
|
// Cancelled
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
|
|
|
@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.service.breakpoint;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.collections4.IteratorUtils;
|
import org.apache.commons.collections4.IteratorUtils;
|
||||||
|
@ -785,7 +784,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||||
|
|
||||||
protected void processChange(Consumer<ChangeCollector> processor, String description) {
|
protected void processChange(Consumer<ChangeCollector> processor, String description) {
|
||||||
executor.submit(() -> {
|
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)) {
|
try (ChangeCollector c = new ChangeCollector(changeListeners.fire)) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
processor.accept(c);
|
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
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
if (event instanceof ProgramOpenedPluginEvent) {
|
if (event instanceof ProgramOpenedPluginEvent) {
|
||||||
|
|
|
@ -87,7 +87,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
private static final DebuggerProgramLaunchOffer DUMMY_LAUNCH_OFFER =
|
private static final DebuggerProgramLaunchOffer DUMMY_LAUNCH_OFFER =
|
||||||
new DebuggerProgramLaunchOffer() {
|
new DebuggerProgramLaunchOffer() {
|
||||||
@Override
|
@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?");
|
throw new AssertionError("Who clicked me?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -562,9 +562,13 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNUSED?
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> flushTransactions() {
|
public CompletableFuture<Void> flushTransactions() {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
}, privateQueue).thenCompose(__ -> {
|
||||||
|
return objectManager.flushEvents();
|
||||||
|
}).thenCompose(__ -> {
|
||||||
return parTx.flush();
|
return parTx.flush();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,4 +285,8 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
|
||||||
reorderer.dispose();
|
reorderer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> flushEvents() {
|
||||||
|
return reorderer.flushEvents();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,6 +220,10 @@ public class TraceObjectListener implements DebuggerModelListener {
|
||||||
reorderer.dispose();
|
reorderer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> flushEvents() {
|
||||||
|
return reorderer.flushEvents();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private CompletableFuture<List<TargetObject>> findDependenciesTop(TargetObject added) {
|
private CompletableFuture<List<TargetObject>> findDependenciesTop(TargetObject added) {
|
||||||
List<TargetObject> result = new ArrayList<>();
|
List<TargetObject> result = new ArrayList<>();
|
||||||
|
|
|
@ -702,4 +702,10 @@ public class TraceObjectManager {
|
||||||
objectListener.dispose();
|
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 javax.swing.JOptionPane;
|
||||||
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
import org.jdom.JDOMException;
|
import org.jdom.JDOMException;
|
||||||
|
|
||||||
|
@ -35,6 +34,7 @@ import ghidra.dbg.*;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
|
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
|
@ -276,11 +276,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
* @param params the parameters of the model's launcher
|
* @param params the parameters of the model's launcher
|
||||||
* @return the arguments given by the user, or null if cancelled
|
* @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 =
|
DebuggerMethodInvocationDialog dialog =
|
||||||
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
|
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
|
||||||
// NB. Do not invoke read/writeConfigState
|
// 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()) {
|
for (ParameterDescription<?> param : params.values()) {
|
||||||
Object val = args.get(param.name);
|
Object val = args.get(param.name);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
|
@ -311,13 +314,13 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
* @param forPrompt true if the user will be confirming the arguments
|
* @param forPrompt true if the user will be confirming the arguments
|
||||||
* @return the loaded arguments, or defaults
|
* @return the loaded arguments, or defaults
|
||||||
*/
|
*/
|
||||||
protected Map<String, ?> loadLastLauncherArgs(
|
protected Map<String, ?> loadLastLauncherArgs(TargetLauncher launcher, boolean forPrompt) {
|
||||||
Map<String, ParameterDescription<?>> params, boolean forPrompt) {
|
|
||||||
/**
|
/**
|
||||||
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
|
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
|
||||||
* Re-examine this if/when that gets merged
|
* Re-examine this if/when that gets merged
|
||||||
*/
|
*/
|
||||||
if (program != null) {
|
if (program != null) {
|
||||||
|
TargetParameterMap params = launcher.getParameters();
|
||||||
ProgramUserData userData = program.getProgramUserData();
|
ProgramUserData userData = program.getProgramUserData();
|
||||||
String property =
|
String property =
|
||||||
userData.getStringProperty(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, null);
|
userData.getStringProperty(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, null);
|
||||||
|
@ -354,7 +357,6 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LinkedHashMap<>();
|
return new LinkedHashMap<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -368,11 +370,17 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
* @param params the parameters of the model's launcher
|
* @param params the parameters of the model's launcher
|
||||||
* @return the chosen arguments, or null if the user cancels at the prompt
|
* @return the chosen arguments, or null if the user cancels at the prompt
|
||||||
*/
|
*/
|
||||||
public Map<String, ?> getLauncherArgs(Map<String, ParameterDescription<?>> params,
|
public Map<String, ?> getLauncherArgs(TargetLauncher launcher,
|
||||||
boolean prompt) {
|
boolean prompt, LaunchConfigurator configurator) {
|
||||||
return prompt
|
return prompt
|
||||||
? promptLauncherArgs(params)
|
? configurator.configureLauncher(launcher,
|
||||||
: loadLastLauncherArgs(params, false);
|
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,
|
protected CompletableFuture<DebuggerObjectModel> connect(DebuggerModelService service,
|
||||||
boolean prompt) {
|
boolean prompt, LaunchConfigurator configurator) {
|
||||||
DebuggerModelFactory factory = getModelFactory();
|
DebuggerModelFactory factory = getModelFactory();
|
||||||
|
configurator.configureConnector(factory);
|
||||||
if (prompt) {
|
if (prompt) {
|
||||||
return service.showConnectDialog(factory);
|
return service.showConnectDialog(factory);
|
||||||
}
|
}
|
||||||
|
@ -454,8 +463,8 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
|
|
||||||
// Eww.
|
// Eww.
|
||||||
protected CompletableFuture<Void> launch(TargetLauncher launcher,
|
protected CompletableFuture<Void> launch(TargetLauncher launcher,
|
||||||
boolean prompt) {
|
boolean prompt, LaunchConfigurator configurator) {
|
||||||
Map<String, ?> args = getLauncherArgs(launcher.getParameters(), prompt);
|
Map<String, ?> args = getLauncherArgs(launcher, prompt, configurator);
|
||||||
if (args == null) {
|
if (args == null) {
|
||||||
throw new CancellationException();
|
throw new CancellationException();
|
||||||
}
|
}
|
||||||
|
@ -551,17 +560,27 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
DebuggerModelService service = tool.getService(DebuggerModelService.class);
|
||||||
DebuggerStaticMappingService mappingService =
|
DebuggerStaticMappingService mappingService =
|
||||||
tool.getService(DebuggerStaticMappingService.class);
|
tool.getService(DebuggerStaticMappingService.class);
|
||||||
monitor.initialize(6);
|
monitor.initialize(6);
|
||||||
monitor.setMessage("Connecting");
|
monitor.setMessage("Connecting");
|
||||||
var locals = new Object() {
|
var locals = new Object() {
|
||||||
|
DebuggerObjectModel model;
|
||||||
CompletableFuture<TargetObject> futureTarget;
|
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);
|
checkCancelled(monitor);
|
||||||
|
locals.model = m;
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
monitor.setMessage("Finding Launcher");
|
monitor.setMessage("Finding Launcher");
|
||||||
return AsyncTimer.DEFAULT_TIMER.mark()
|
return AsyncTimer.DEFAULT_TIMER.mark()
|
||||||
|
@ -573,7 +592,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
monitor.setMessage("Launching");
|
monitor.setMessage("Launching");
|
||||||
locals.futureTarget = listenForTarget(l.getModel());
|
locals.futureTarget = listenForTarget(l.getModel());
|
||||||
return AsyncTimer.DEFAULT_TIMER.mark()
|
return AsyncTimer.DEFAULT_TIMER.mark()
|
||||||
.timeOut(launch(l, prompt), getTimeoutMillis(),
|
.timeOut(launch(l, prompt, configurator), getTimeoutMillis(),
|
||||||
() -> onTimedOutLaunch(monitor));
|
() -> onTimedOutLaunch(monitor));
|
||||||
}).thenCompose(__ -> {
|
}).thenCompose(__ -> {
|
||||||
checkCancelled(monitor);
|
checkCancelled(monitor);
|
||||||
|
@ -584,6 +603,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
() -> onTimedOutTarget(monitor));
|
() -> onTimedOutTarget(monitor));
|
||||||
}).thenCompose(t -> {
|
}).thenCompose(t -> {
|
||||||
checkCancelled(monitor);
|
checkCancelled(monitor);
|
||||||
|
locals.target = t;
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
monitor.setMessage("Waiting for recorder");
|
monitor.setMessage("Waiting for recorder");
|
||||||
return AsyncTimer.DEFAULT_TIMER.mark()
|
return AsyncTimer.DEFAULT_TIMER.mark()
|
||||||
|
@ -591,6 +611,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
() -> onTimedOutRecorder(monitor, service, t));
|
() -> onTimedOutRecorder(monitor, service, t));
|
||||||
}).thenCompose(r -> {
|
}).thenCompose(r -> {
|
||||||
checkCancelled(monitor);
|
checkCancelled(monitor);
|
||||||
|
locals.recorder = r;
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
throw new CancellationException();
|
throw new CancellationException();
|
||||||
|
@ -600,10 +621,16 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
.timeOut(listenForMapping(mappingService, r), getTimeoutMillis(),
|
.timeOut(listenForMapping(mappingService, r), getTimeoutMillis(),
|
||||||
() -> onTimedOutMapping(monitor, mappingService, r));
|
() -> onTimedOutMapping(monitor, mappingService, r));
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
if (AsyncUtils.unwrapThrowable(ex) instanceof CancellationException) {
|
locals.exception = AsyncUtils.unwrapThrowable(ex);
|
||||||
return null;
|
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;
|
package ghidra.app.plugin.core.debug.service.model.launch;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
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;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,6 +38,89 @@ import ghidra.util.task.TaskMonitor;
|
||||||
*/
|
*/
|
||||||
public interface DebuggerProgramLaunchOffer {
|
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
|
* 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
|
* @param prompt if the user should be prompted to confirm launch parameters
|
||||||
* @return a future which completes when the program is launched
|
* @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
|
* A name so that this offer can be recognized later
|
||||||
|
|
|
@ -599,7 +599,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> changesSettled() {
|
public CompletableFuture<Void> changesSettled() {
|
||||||
return changeDebouncer.settled();
|
return changeDebouncer.stable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.services;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
|
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
|
||||||
import ghidra.app.services.LogicalBreakpoint.State;
|
import ghidra.app.services.LogicalBreakpoint.State;
|
||||||
|
@ -351,4 +352,14 @@ public interface DebuggerLogicalBreakpointService {
|
||||||
* @return a future which completes when the command has been processed
|
* @return a future which completes when the command has been processed
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Void> deleteLocs(Collection<TraceBreakpoint> col);
|
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.
|
* Enable (or create) this breakpoint in the given target.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Presuming the breakpoint is mappable to the given trace, if no breakpoint of the same kind
|
* If the breakpoint already exists, it is enabled. If it's already enabled, this has no effect.
|
||||||
* exists at the mapped address, then this will create a new breakpoint. Note, depending on the
|
* If not, and the breakpoint is mappable to the given trace, the breakpoint is created. Note,
|
||||||
* debugging model, the enabled or created breakpoint may apply to more than the given trace.
|
* 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>
|
* <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.
|
* events are processed.
|
||||||
*
|
*
|
||||||
* @param trace the trace for the given target
|
* @param trace the trace for the given target
|
||||||
|
@ -761,7 +762,7 @@ public interface LogicalBreakpoint {
|
||||||
* <p>
|
* <p>
|
||||||
* Note this will not create any new breakpoints. It will disable all breakpoints of the same
|
* 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
|
* 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>
|
* <p>
|
||||||
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
* 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
|
* 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 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 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
|
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
||||||
* events are processed.
|
* events are processed.
|
||||||
|
@ -794,7 +795,7 @@ public interface LogicalBreakpoint {
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This affects the mapped program, if applicable, and all open and live traces. Note, depending
|
* 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>
|
* <p>
|
||||||
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
||||||
|
@ -809,7 +810,7 @@ public interface LogicalBreakpoint {
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This affects the mapped program, if applicable, and all open and live traces. Note, depending
|
* 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>
|
* <p>
|
||||||
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
||||||
|
@ -825,7 +826,7 @@ public interface LogicalBreakpoint {
|
||||||
* <p>
|
* <p>
|
||||||
* This presumes the breakpoint's specifications are deletable. This affects the mapped program,
|
* 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
|
* 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>
|
* <p>
|
||||||
* This simply issues the command. The logical breakpoint is updated only when the resulting
|
* 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.DebuggerProgramLaunchOffer;
|
||||||
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
|
||||||
import ghidra.app.services.DebuggerModelService;
|
import ghidra.app.services.DebuggerModelService;
|
||||||
import ghidra.async.AsyncUtils;
|
|
||||||
import ghidra.dbg.DebuggerModelFactory;
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.model.TestDebuggerModelFactory;
|
import ghidra.dbg.model.TestDebuggerModelFactory;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -34,10 +33,12 @@ import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class TestDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpinion {
|
public class TestDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpinion {
|
||||||
|
|
||||||
static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
|
public static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
|
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt,
|
||||||
return AsyncUtils.NIL;
|
LaunchConfigurator configurator) {
|
||||||
|
return CompletableFuture
|
||||||
|
.completedFuture(LaunchResult.totalFailure(new AssertionError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.model.record;
|
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 static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
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));
|
mb.testProcess1.memory.setMemory(tb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
|
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
|
||||||
isOneOf(null, TraceMemoryState.UNKNOWN));
|
is(oneOf(null, TraceMemoryState.UNKNOWN)));
|
||||||
|
|
||||||
byte[] data = new byte[10];
|
byte[] data = new byte[10];
|
||||||
waitOn(recorder.readMemory(tb.addr(0x00400123), 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));
|
mb.testProcess1.memory.setMemory(tb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
|
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
|
||||||
isOneOf(null, TraceMemoryState.UNKNOWN));
|
is(oneOf(null, TraceMemoryState.UNKNOWN)));
|
||||||
|
|
||||||
byte[] data = new byte[10];
|
byte[] data = new byte[10];
|
||||||
assertNull(waitOn(recorder.readMemoryBlocks(
|
assertNull(waitOn(recorder.readMemoryBlocks(
|
||||||
|
@ -354,7 +354,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
|
||||||
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rwx");
|
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rwx");
|
||||||
flushAndWait();
|
flushAndWait();
|
||||||
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
|
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
|
||||||
isOneOf(null, TraceMemoryState.UNKNOWN));
|
is(oneOf(null, TraceMemoryState.UNKNOWN)));
|
||||||
|
|
||||||
byte[] data = new byte[10];
|
byte[] data = new byte[10];
|
||||||
waitOn(recorder.writeMemory(tb.addr(0x00400123), tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9)));
|
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
|
* Construct a new debouncer
|
||||||
*
|
*
|
||||||
* @param timer the timer to use for delay
|
* @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) {
|
public AsyncDebouncer(AsyncTimer timer, long windowMillis) {
|
||||||
this.timer = timer;
|
this.timer = timer;
|
||||||
|
@ -115,7 +115,7 @@ public class AsyncDebouncer<T> {
|
||||||
* <p>
|
* <p>
|
||||||
* The returned future completes <em>after</em> all registered listeners have been invoked.
|
* 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() {
|
public synchronized CompletableFuture<T> settled() {
|
||||||
if (settledPromise == null) {
|
if (settledPromise == null) {
|
||||||
|
@ -123,4 +123,22 @@ public class AsyncDebouncer<T> {
|
||||||
}
|
}
|
||||||
return settledPromise;
|
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.dbg.target.TargetObject;
|
||||||
import ghidra.util.Msg;
|
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 {
|
public abstract class AnnotatedDebuggerAttributeListener implements DebuggerModelListener {
|
||||||
private static final String ATTR_METHODS =
|
private static final String ATTR_METHODS =
|
||||||
"@" + AttributeCallback.class.getSimpleName() + "-annotated methods";
|
"@" + AttributeCallback.class.getSimpleName() + "-annotated methods";
|
||||||
private static final String PARAMS_ERR =
|
private static final String PARAMS_ERR =
|
||||||
ATTR_METHODS + " must accept 2 parameters: (TargetObject, T)";
|
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)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
protected @interface AttributeCallback {
|
protected @interface AttributeCallback {
|
||||||
|
|
|
@ -374,4 +374,8 @@ public class DebuggerCallbackReorderer implements DebuggerModelListener {
|
||||||
rec.cancel();
|
rec.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> flushEvents() {
|
||||||
|
return lastEvent.thenApply(v -> v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,16 @@ public class TestDebuggerModelBuilder {
|
||||||
public TestTargetRegister testRegisterPC;
|
public TestTargetRegister testRegisterPC;
|
||||||
public TestTargetRegister testRegisterSP;
|
public TestTargetRegister testRegisterSP;
|
||||||
|
|
||||||
|
protected TestDebuggerObjectModel newModel(String typeHint) {
|
||||||
|
return new TestDebuggerObjectModel(typeHint);
|
||||||
|
}
|
||||||
|
|
||||||
public void createTestModel() {
|
public void createTestModel() {
|
||||||
createTestModel("Session");
|
createTestModel("Session");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createTestModel(String typeHint) {
|
public void createTestModel(String typeHint) {
|
||||||
testModel = new TestDebuggerObjectModel(typeHint);
|
testModel = newModel(typeHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Address addr(long offset) {
|
public Address addr(long offset) {
|
||||||
|
|
|
@ -16,15 +16,19 @@
|
||||||
package ghidra.dbg.model;
|
package ghidra.dbg.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import org.jdom.JDOMException;
|
import org.jdom.JDOMException;
|
||||||
|
|
||||||
import ghidra.dbg.DebuggerModelListener;
|
import ghidra.dbg.DebuggerModelListener;
|
||||||
|
import ghidra.dbg.attributes.TargetDataType;
|
||||||
|
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
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.
|
// TODO: Refactor with other Fake and Test model stuff.
|
||||||
public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
|
public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
|
||||||
|
@ -60,13 +64,145 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
|
||||||
this("Session");
|
this("Session");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Executor getClientExecutor() {
|
public TestDebuggerObjectModel(String rootHint) {
|
||||||
return clientExecutor;
|
this.session = newTestTargetSession(rootHint);
|
||||||
|
addModelRoot(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestDebuggerObjectModel(String rootHint) {
|
protected TestTargetSession newTestTargetSession(String rootHint) {
|
||||||
this.session = new TestTargetSession(this, rootHint, ROOT_SCHEMA);
|
return new TestTargetSession(this, rootHint, ROOT_SCHEMA);
|
||||||
addModelRoot(session);
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
|
|
|
@ -56,7 +56,8 @@ public class TestTargetBreakpointContainer
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
|
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
|
||||||
Set<TargetBreakpointKind> kinds) {
|
Set<TargetBreakpointKind> kinds) {
|
||||||
TestTargetBreakpoint bpt = new TestTargetBreakpoint(this, counter.getAndIncrement(),
|
TestTargetBreakpoint bpt =
|
||||||
|
getModel().newTestTargetBreakpoint(this, counter.getAndIncrement(),
|
||||||
range.getMinAddress(), (int) range.getLength(), kinds);
|
range.getMinAddress(), (int) range.getLength(), kinds);
|
||||||
changeElements(List.of(), List.of(bpt), "Breakpoint Added");
|
changeElements(List.of(), List.of(bpt), "Breakpoint Added");
|
||||||
return getModel().future(null);
|
return getModel().future(null);
|
||||||
|
|
|
@ -30,7 +30,8 @@ public class TestTargetDataTypeNamespace
|
||||||
|
|
||||||
public TestTargetTypedefDataType addTypedefDataType(String name,
|
public TestTargetTypedefDataType addTypedefDataType(String name,
|
||||||
TargetDataType defDataType) {
|
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);
|
changeElements(List.of(), List.of(dataType), "Added typedef " + name);
|
||||||
return dataType;
|
return dataType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,8 @@ public class TestTargetMemory
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetMemoryRegion addRegion(String name, AddressRange range, String flags) {
|
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);
|
changeElements(List.of(), List.of(region), "Add test region: " + range);
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,9 @@ public class TestTargetModule
|
||||||
|
|
||||||
public TestTargetModule(TestTargetModuleContainer parent, String name, AddressRange range) {
|
public TestTargetModule(TestTargetModuleContainer parent, String name, AddressRange range) {
|
||||||
super(parent, PathUtils.makeKey(name), "Module");
|
super(parent, PathUtils.makeKey(name), "Module");
|
||||||
sections = new TestTargetSectionContainer(this);
|
sections = getModel().newTestTargetSectionContainer(this);
|
||||||
symbols = new TestTargetSymbolNamespace(this);
|
symbols = getModel().newTestTargetSymbolNamespace(this);
|
||||||
types = new TestTargetDataTypeNamespace(this);
|
types = getModel().newTestTargetDataTypeNamespace(this);
|
||||||
|
|
||||||
changeAttributes(List.of(), Map.of(
|
changeAttributes(List.of(), Map.of(
|
||||||
RANGE_ATTRIBUTE_NAME, range,
|
RANGE_ATTRIBUTE_NAME, range,
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class TestTargetModuleContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetModule addModule(String name, AddressRange range) {
|
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);
|
changeElements(List.of(), List.of(module), "Add test module: " + name);
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,11 @@ public class TestTargetProcess extends
|
||||||
|
|
||||||
public TestTargetProcess(DefaultTestTargetObject<?, ?> parent, int pid, AddressSpace space) {
|
public TestTargetProcess(DefaultTestTargetObject<?, ?> parent, int pid, AddressSpace space) {
|
||||||
super(parent, PathUtils.makeKey(PathUtils.makeIndex(pid)), "Process");
|
super(parent, PathUtils.makeKey(PathUtils.makeIndex(pid)), "Process");
|
||||||
breaks = new TestTargetBreakpointContainer(this);
|
breaks = getModel().newTestTargetBreakpointContainer(this);
|
||||||
memory = new TestTargetMemory(this, space);
|
memory = getModel().newTestTargetMemory(this, space);
|
||||||
modules = new TestTargetModuleContainer(this);
|
modules = getModel().newTestTargetModuleContainer(this);
|
||||||
regs = new TestTargetRegisterContainer(this);
|
regs = getModel().newTestTargetRegisterContainer(this);
|
||||||
threads = new TestTargetThreadContainer(this);
|
threads = getModel().newTestTargetThreadContainer(this);
|
||||||
|
|
||||||
changeAttributes(List.of(), List.of(
|
changeAttributes(List.of(), List.of(
|
||||||
breaks,
|
breaks,
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class TestTargetProcessContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetProcess addProcess(int pid, AddressSpace space) {
|
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");
|
changeElements(List.of(), List.of(proc), Map.of(), "Test Process Added");
|
||||||
return proc;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,14 @@ public class TestTargetRegisterContainer
|
||||||
if (!predicate.test(register)) {
|
if (!predicate.test(register)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
add.add(TestTargetRegister.fromLanguageRegister(this, register));
|
add.add(getModel().newTestTargetRegister(this, register));
|
||||||
}
|
}
|
||||||
changeElements(List.of(), add, "Added registers from Ghidra language: " + language);
|
changeElements(List.of(), add, "Added registers from Ghidra language: " + language);
|
||||||
return add;
|
return add;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetRegister addRegister(Register register) {
|
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");
|
changeElements(List.of(), List.of(tr), "Added " + register + " from Ghidra language");
|
||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class TestTargetSectionContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetSection addSection(String name, AddressRange range) {
|
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);
|
changeElements(List.of(), List.of(section), "Add test section: " + name);
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,10 @@ public class TestTargetSession extends DefaultTargetModelRoot
|
||||||
public TestTargetSession(TestDebuggerObjectModel model, String rootHint,
|
public TestTargetSession(TestDebuggerObjectModel model, String rootHint,
|
||||||
TargetObjectSchema schema) {
|
TargetObjectSchema schema) {
|
||||||
super(model, rootHint, schema);
|
super(model, rootHint, schema);
|
||||||
environment = new TestTargetEnvironment(this);
|
environment = model.newTestTargetEnvironment(this);
|
||||||
processes = new TestTargetProcessContainer(this);
|
processes = model.newTestTargetProcessContainer(this);
|
||||||
interpreter = new TestTargetInterpreter(this);
|
interpreter = model.newTestTargetInterpreter(this);
|
||||||
mimickJavaLauncher = new TestMimickJavaLauncher(this);
|
mimickJavaLauncher = model.newTestMimickJavaLauncher(this);
|
||||||
|
|
||||||
changeAttributes(List.of(),
|
changeAttributes(List.of(),
|
||||||
List.of(environment, processes, interpreter, mimickJavaLauncher), Map.of(),
|
List.of(environment, processes, interpreter, mimickJavaLauncher), Map.of(),
|
||||||
|
|
|
@ -39,7 +39,8 @@ public class TestTargetStack extends DefaultTestTargetObject<TestTargetStackFram
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetStackFrameNoRegisterBank pushFrameNoBank(Address pc) {
|
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
|
* @return the "new" highest-indexed frame, into which old data was pushed
|
||||||
*/
|
*/
|
||||||
public TestTargetStackFrameHasRegisterBank pushFrameHasBank(Address pc) {
|
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
|
* @return the "new" highest-indexed frame, into which old data was pushed
|
||||||
*/
|
*/
|
||||||
public TestTargetStackFrameIsRegisterBank pushFrameIsBank(Address pc) {
|
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) {
|
public TestTargetStackFrameHasRegisterBank(TestTargetStack parent, int level, Address pc) {
|
||||||
super(parent, PathUtils.makeKey(PathUtils.makeIndex(level)), "Frame");
|
super(parent, PathUtils.makeKey(PathUtils.makeIndex(level)), "Frame");
|
||||||
bank = new TestTargetRegisterBankInFrame(this);
|
bank = getModel().newTestTargetRegisterBankInFrame(this);
|
||||||
|
|
||||||
changeAttributes(List.of(), Map.of(
|
changeAttributes(List.of(), Map.of(
|
||||||
bank.getName(), bank, //
|
bank.getName(), bank, //
|
||||||
|
|
|
@ -31,7 +31,8 @@ public class TestTargetSymbolNamespace
|
||||||
|
|
||||||
public TestTargetSymbol addSymbol(String name, Address address, long size,
|
public TestTargetSymbol addSymbol(String name, Address address, long size,
|
||||||
TargetDataType dataType) {
|
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);
|
changeElements(List.of(), List.of(symbol), "Added symbol " + name);
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,20 +17,26 @@ package ghidra.dbg.model;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetExecutionStateful;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.dbg.target.TargetThread;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
|
||||||
public class TestTargetThread
|
public class TestTargetThread
|
||||||
extends DefaultTestTargetObject<TestTargetObject, TestTargetThreadContainer>
|
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) {
|
public TestTargetThread(TestTargetThreadContainer parent, int tid) {
|
||||||
super(parent, PathUtils.makeKey(PathUtils.makeIndex(tid)), "Thread");
|
super(parent, PathUtils.makeKey(PathUtils.makeIndex(tid)), "Thread");
|
||||||
changeAttributes(List.of(), List.of(), Map.of(
|
changeAttributes(List.of(), List.of(), Map.of(
|
||||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.STOPPED //
|
STATE_ATTRIBUTE_NAME, TargetExecutionState.STOPPED,
|
||||||
), "Initialized");
|
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS),
|
||||||
|
"Initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,7 +45,7 @@ public class TestTargetThread
|
||||||
* @return the created register bank
|
* @return the created register bank
|
||||||
*/
|
*/
|
||||||
public TestTargetRegisterBankInThread addRegisterBank() {
|
public TestTargetRegisterBankInThread addRegisterBank() {
|
||||||
TestTargetRegisterBankInThread regs = new TestTargetRegisterBankInThread(this);
|
TestTargetRegisterBankInThread regs = getModel().newTestTargetRegisterBankInThread(this);
|
||||||
changeAttributes(List.of(), List.of(
|
changeAttributes(List.of(), List.of(
|
||||||
regs),
|
regs),
|
||||||
Map.of(), "Add Test Register Bank");
|
Map.of(), "Add Test Register Bank");
|
||||||
|
@ -47,7 +53,7 @@ public class TestTargetThread
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetStack addStack() {
|
public TestTargetStack addStack() {
|
||||||
TestTargetStack stack = new TestTargetStack(this);
|
TestTargetStack stack = getModel().newTestTargetStack(this);
|
||||||
changeAttributes(List.of(), List.of(
|
changeAttributes(List.of(), List.of(
|
||||||
stack),
|
stack),
|
||||||
Map.of(), "Add Test Stack");
|
Map.of(), "Add Test Stack");
|
||||||
|
@ -55,8 +61,28 @@ public class TestTargetThread
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(TargetExecutionState state) {
|
public void setState(TargetExecutionState state) {
|
||||||
Delta<?, ?> delta = changeAttributes(List.of(), List.of(), Map.of(
|
changeAttributes(List.of(), List.of(), Map.of(
|
||||||
STATE_ATTRIBUTE_NAME, state //
|
STATE_ATTRIBUTE_NAME, state),
|
||||||
), "Changed 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
|
public class TestTargetThreadContainer
|
||||||
extends DefaultTestTargetObject<TestTargetThread, TestTargetProcess> {
|
extends DefaultTestTargetObject<TestTargetThread, TestTargetProcess> {
|
||||||
|
|
||||||
public TestTargetThreadContainer(TestTargetProcess parent) {
|
public TestTargetThreadContainer(TestTargetProcess parent) {
|
||||||
super(parent, "Threads", "Threads");
|
super(parent, "Threads", "Threads");
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestTargetThread addThread(int tid) {
|
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");
|
changeElements(List.of(), List.of(thread), Map.of(), "Test Thread Added");
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,11 @@ import ghidra.dbg.attributes.TargetDataType;
|
||||||
|
|
||||||
public class TestTargetTypedefDataType
|
public class TestTargetTypedefDataType
|
||||||
extends TestTargetNamedDataType<TestTargetTypedefDef> {
|
extends TestTargetNamedDataType<TestTargetTypedefDef> {
|
||||||
|
|
||||||
public TestTargetTypedefDataType(TestTargetDataTypeNamespace parent, String name,
|
public TestTargetTypedefDataType(TestTargetDataTypeNamespace parent, String name,
|
||||||
TargetDataType dataType) {
|
TargetDataType dataType) {
|
||||||
super(parent, name, NamedDataTypeKind.TYPEDEF, "TypedefType");
|
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">
|
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<interface name="Thread" />
|
<interface name="Thread" />
|
||||||
<interface name="ExecutionStateful" />
|
<interface name="ExecutionStateful" />
|
||||||
<!--interface name="Steppable" / -->
|
<interface name="Steppable" />
|
||||||
|
<interface name="Resumable" />
|
||||||
|
<interface name="Interruptible" />
|
||||||
|
<interface name="Killable" />
|
||||||
<element schema="VOID" />
|
<element schema="VOID" />
|
||||||
<attribute name="_tid" schema="INT" hidden="yes" />
|
<attribute name="_tid" schema="INT" hidden="yes" />
|
||||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||||
|
|
|
@ -79,8 +79,9 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DBTraceStaticMapping add(AddressRange range, Range<Long> lifespan, URL toProgramURL,
|
public DBTraceStaticMapping add(AddressRange range, Range<Long> lifespan, URL toProgramURL,
|
||||||
String toAddress)
|
String toAddress) throws TraceConflictedMappingException {
|
||||||
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) {
|
if (lifespan.hasLowerBound() && lifespan.lowerBoundType() != BoundType.CLOSED) {
|
||||||
throw new IllegalArgumentException("Lower bound must be closed");
|
throw new IllegalArgumentException("Lower bound must be closed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,13 @@ public enum TraceBreakpointKind {
|
||||||
SW_EXECUTE(1 << 3);
|
SW_EXECUTE(1 << 3);
|
||||||
|
|
||||||
public static class TraceBreakpointKindSet extends AbstractSetDecorator<TraceBreakpointKind> {
|
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) {
|
public static TraceBreakpointKindSet of(TraceBreakpointKind... kinds) {
|
||||||
return new TraceBreakpointKindSet(Set.of(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
|
* 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 snap the time to search
|
||||||
* @param range the address range to search
|
* @param range the address range to search
|
||||||
* @param data the values to search for
|
* @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
|
* @param forward true to return the match with the lowest address in {@code range}, false for
|
||||||
* the highest address.
|
* the highest address.
|
||||||
* @param monitor a monitor for progress reporting and canceling
|
* @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,
|
Address findBytes(long snap, AddressRange range, ByteBuffer data, ByteBuffer mask,
|
||||||
boolean forward, TaskMonitor monitor);
|
boolean forward, TaskMonitor monitor);
|
||||||
|
|
|
@ -479,6 +479,19 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
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
|
* Returns the equivalent of executing count p-code operations less than this schedule
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue