GP-1978: Port tests to TraceRmi and delete more stuff.

This commit is contained in:
Dan 2024-12-12 11:43:47 -05:00
parent dadfc465df
commit 5813548a84
110 changed files with 1762 additions and 17529 deletions

View file

@ -1,372 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.model.*;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.datastruct.CollectionChangeListener;
@ServiceInfo(
defaultProviderName = "ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin",
description = "Service for managing debug sessions and connections")
@Deprecated(forRemoval = true, since = "11.2")
public interface DebuggerModelService {
/**
* Get the set of model factories found on the classpath
*
* @return the set of factories
*/
Set<DebuggerModelFactory> getModelFactories();
/**
* Get the set of models registered with this service
*
* @return the set of models
*/
Set<DebuggerObjectModel> getModels();
/**
* Disconnect or close all debugging models (connections)
*
* @return a future which completes when all models are closed
*/
CompletableFuture<Void> closeAllModels();
/**
* Get the set of active recorders
*
* <p>
* A recorder is active as long as its target (usually a process) is valid. It becomes inactive
* when the target becomes invalid, or when the user stops the recording.
*
* @return the set of recorders
*/
Collection<TraceRecorder> getTraceRecorders();
/**
* Register a model with this service
*
* <p>
* In general, the tool will only display models registered here
*
* @param model the model to register
* @return true if the model was not already registered
*/
boolean addModel(DebuggerObjectModel model);
/**
* Un-register a model
*
* @param model the model to un-register
* @return true if the model was successfully un-registered, false if the model had never been
* registered or was already un-registered.
*/
boolean removeModel(DebuggerObjectModel model);
/**
* Start a new trace on the given target
*
* <p>
* Following conventions, the target must be a container, usually a process. Ideally, the model
* will present the process as having memory, modules, and threads; and the model will present
* each thread as having registers, or a stack with frame 0 presenting the registers.
*
* <p>
* Any given container can be traced by at most one recorder.
*
* <p>
* TODO: If mappers remain bound to a prospective target, then remove target from the parameters
* here.
*
* @param target a target to record
* @param mapper a mapper between target and trace objects
* @param source the source of the request
* @return the destination trace
* @throws IOException if a trace cannot be created
* @see DebuggerMappingOpinion#getOffers(TargetObject)
*/
TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper,
ActionSource source) throws IOException;
/**
* Query mapping opinions and record the given target using the "best" offer
*
* <p>
* If exactly one offer is given, this simply uses it. If multiple are given, this automatically
* chooses the "best" one without prompting the user. If none are given, this fails. This
* invocation is assumed to come from an {@link ActionSource#AUTOMATIC} source.
*
* @see DebuggerMappingOpinion#queryOpinions(TargetObject)
* @param target the target to record.
* @return a future which completes with the recorder, or completes exceptionally
*/
TraceRecorder recordTargetBestOffer(TargetObject target);
/**
* Query mapping opinions, prompt the user, and record the given target
*
* <p>
* Even if exactly one offer is given, the user is prompted to provide information about the new
* recording, and to give the user an opportunity to cancel. If none are given, the prompt says
* as much. If the user cancels, the returned future completes with {@code null}. The invocation
* is assumed to come from a {@link ActionSource#MANUAL} source.
*
* @see DebuggerMappingOpinion#queryOpinions(TargetObject)
* @param target the target to record.
* @return a future which completes with the recorder, or completes exceptionally
*/
TraceRecorder recordTargetPromptOffers(TargetObject target);
/**
* Start and open a new trace on the given target
*
* <p>
* Starts a new trace, and opens it in the tool. The invocation is assumed to come from an
* {@link ActionSource#AUTOMATIC} source.
*
* @see #recordTarget(TargetObject)
*/
TraceRecorder recordTargetAndActivateTrace(TargetObject target,
DebuggerTargetTraceMapper mapper) throws IOException;
/**
* Get the recorder which is tracing the given target
*
* @param target an object being recorded, usually a process
* @return the recorder, or null
*/
TraceRecorder getRecorder(TargetObject target);
/**
* Get the recorder which is recording the nearest ancestor for the given object
*
* @param obj the object
* @return the recorder, or null
*/
TraceRecorder getRecorderForSuccessor(TargetObject obj);
/**
* Get the recorder whose destination is the given trace
*
* @param trace the destination trace
* @return the recorder, or null
*/
TraceRecorder getRecorder(Trace trace);
/**
* Get the object (usually a process) associated with the given destination trace
*
* <p>
* A recorder uses conventions to discover the "process" in the model, given a target object.
*
* <p>
* TODO: Conventions for targets other than processes are not yet specified.
*
* @param trace the destination trace
* @return the target object
*/
TargetObject getTarget(Trace trace);
/**
* Get the destination trace for a given source target
*
* @param target the source target, usually a process
* @return the destination trace
*/
Trace getTrace(TargetObject target);
/**
* Get the object associated with the given destination trace thread
*
* <p>
* A recorder uses conventions to discover "threads" for a given target object, usually a
* process. Those threads are then assigned to corresponding destination trace threads. Assuming
* the given trace thread is the destination of an active recorder, this method finds the
* corresponding model "thread."
*
* <p>
* TODO: Conventions for targets other than processes (containing threads) are not yet
* specified.
*
* @param thread the destination trace thread
* @return the source model "thread"
*/
TargetThread getTargetThread(TraceThread thread);
/**
* Get the destination trace thread, if applicable, for a given source thread
*
* <p>
* Consider {@link #getTraceThread(TargetObject, TargetExecutionStateful)} if the caller already
* has a handle to the thread's container.
*
* @param thread the source model "thread"
* @return the destination trace thread
*/
TraceThread getTraceThread(TargetThread thread);
/**
* Get the destination trace thread, if applicable, for a given source thread
*
* <p>
* This method is slightly faster than {@link #getTraceThread(TargetExecutionStateful)}, since
* it doesn't have to search for the applicable recorder. However, if the wrong container is
* given, this method will fail to find the given thread.
*
* @param target the source target, usually a process
* @param thread the source model thread
* @return the destination trace thread
*/
TraceThread getTraceThread(TargetObject target, TargetThread thread);
/**
* Signal to plugins that the user's focus has changed to another model
*
* @param model the new model to focus
*/
void activateModel(DebuggerObjectModel model);
/**
* Get the active model
*
* @return the model
*/
DebuggerObjectModel getCurrentModel();
/**
* Get the last focused object related to the given target
*
* <p>
* Assuming the target object is being actively traced, find the last focused object among those
* being traced by the same recorder. Essentially, given that the target likely belongs to a
* process, find the object within that process that last had focus. This is primarily used
* wh@Override en switching focus between traces. Since the user has not explicitly selected a
* model object, the UI should choose the one which had focus when the newly-activated trace was
* last active.
*
* @param target a source model object being actively traced
* @return the last focused object being traced by the same recorder
*/
TargetObject getTargetFocus(TargetObject target);
/**
* Listen for changes in available model factories
*
* <p>
* The caller must keep a strong reference to the listener, or it will be automatically removed.
*
* @param listener the listener
*/
void addFactoriesChangedListener(CollectionChangeListener<DebuggerModelFactory> listener);
/**
* Remove a listener for changes in available model factories
*
* @param listener the listener
*/
void removeFactoriesChangedListener(CollectionChangeListener<DebuggerModelFactory> listener);
/**
* Listen for changes in registered models
*
* <p>
* The caller must beep a strong reference to the listener, or it will be automatically removed.
*
* <p>
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
*/
void addModelsChangedListener(CollectionChangeListener<DebuggerObjectModel> listener);
/**
* Remove a listener for changes in registered models
*
* <p>
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
*/
void removeModelsChangedListener(CollectionChangeListener<DebuggerObjectModel> listener);
/**
* Listen for changes in active trace recorders
*
* <p>
* The caller must beep a strong reference to the listener, or it will be automatically removed.
*
* <p>
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
*/
void addTraceRecordersChangedListener(CollectionChangeListener<TraceRecorder> listener);
/**
* Remove a listener for changes in active trace recorders
*
* <p>
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
*/
void removeTraceRecordersChangedListener(CollectionChangeListener<TraceRecorder> listener);
/**
* Collect all offers for launching the given program
*
* @param program the program to launch
* @return the offers
*/
Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program);
/**
* Prompt the user to create a new connection
*
* @return a future which completes with the new connection, possibly cancelled
*/
CompletableFuture<DebuggerObjectModel> showConnectDialog();
/**
* Prompt the user to create a new connection, hinting at the program to launch
*
* @param program the current program used to help select a default
* @return a future which completes with the new connection, possibly cancelled
*/
CompletableFuture<DebuggerObjectModel> showConnectDialog(Program program);
/**
* Prompt the user to create a new connection, optionally fixing the factory
*
* @param factory the required factory
* @return a future which completes with the new connection, possible cancelled
*/
CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -52,9 +52,9 @@ public interface DebuggerTraceManagerService {
*/
USER,
/**
* A trace was activated because a recording was started, usually when a target is launched
* A trace was activated because a target was published or withdrawn
*/
START_RECORDING,
TARGET_UPDATED,
/**
* The change was driven by the model activation, possibly indirectly by the user
*/

View file

@ -4,9 +4,9 @@
* 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.
@ -126,7 +126,7 @@ public enum ControlMode {
return false;
}
Target target = coordinates.getTarget();
return target.isVariableExists(coordinates.getPlatform(),
return target.isVariableExists(platformFor(coordinates, address),
coordinates.getThread(), coordinates.getFrame(), address, length);
}
@ -142,8 +142,8 @@ public enum ControlMode {
return CompletableFuture
.failedFuture(new MemoryAccessException("View is not the present"));
}
return target.writeVariableAsync(coordinates.getPlatform(), coordinates.getThread(),
coordinates.getFrame(), address, data);
return target.writeVariableAsync(platformFor(coordinates, address),
coordinates.getThread(), coordinates.getFrame(), address, data);
}
@Override
@ -228,7 +228,7 @@ public enum ControlMode {
public CompletableFuture<Void> setVariable(PluginTool tool,
DebuggerCoordinates coordinates, Address guestAddress, byte[] data) {
Trace trace = coordinates.getTrace();
TracePlatform platform = coordinates.getPlatform();
TracePlatform platform = platformFor(coordinates, guestAddress);
long snap = coordinates.getViewSnap();
Address hostAddress = platform.mapGuestToHost(guestAddress);
if (hostAddress == null) {
@ -408,6 +408,14 @@ public enum ControlMode {
return coordinates;
}
protected TracePlatform platformFor(DebuggerCoordinates coordinates, Address address) {
if (address.isRegisterAddress()) {
return coordinates.getPlatform();
}
// This seems odd, but the memory UI components are displaying *host* addresses.
return coordinates.getTrace().getPlatformManager().getHostPlatform();
}
/**
* Check if (broadly speaking) the mode supports editing the given coordinates
*

View file

@ -1,125 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg;
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerMappingOffer {
/**
* Get the first offer from the collection which successfully yields a mapper.
*
* <p>
* Manual overrides are excluded.
*
* @param offers the collection of offers, usually ordered by highest confidence first
* @return the first mapper from the offers, or {@code null} if there are no offers, or none
* yield a mapper
*/
static DebuggerTargetTraceMapper first(Collection<DebuggerMappingOffer> offers) {
for (DebuggerMappingOffer offer : offers) {
if (offer.isOverride()) {
continue;
}
try {
DebuggerTargetTraceMapper mapper = offer.take();
Msg.info(DebuggerMappingOffer.class, "Selected first mapping offer: " + offer);
return mapper;
}
catch (Throwable t) {
Msg.error(DebuggerMappingOffer.class,
"Offer " + offer + " failed to take. Trying next.");
}
}
return null;
}
/**
* Get the single offer from the collection that is not a manual override.
*
* @param offers the collection of offers
* @return the offer or {@code null} if more than one non-override offer is present
*/
static DebuggerMappingOffer unique(Collection<DebuggerMappingOffer> offers) {
List<DebuggerMappingOffer> filt =
offers.stream().filter(o -> !o.isOverride()).collect(Collectors.toList());
if (filt.size() != 1) {
return null;
}
return filt.get(0);
}
/**
* Get the confidence of this offer.
*
* <p>
* Offers with numerically higher confidence are preferred. Negative confidence values are
* considered "manual overrides," and so are never selected automatically and are hidden from
* prompts by default.
*
* <p>
* TODO: Spec out some standard numbers. Maybe an enum?
*
* @return the confidence
*/
int getConfidence();
/**
* Check if the confidence indicates this offer is a manual override.
*
* @return true if the confidence is negative
*/
default boolean isOverride() {
return getConfidence() < 0;
}
/**
* Get a human-readable description of the offer.
*
* <p>
* Generally, more detailed descriptions imply a higher confidence.
*
* @return the description
*/
String getDescription();
/**
* Get the language id for the destination trace.
*
* @return the language id
*/
LanguageID getTraceLanguageID();
/**
* Get the compiler spec id for the destination trace.
*
* @return the compiler spec id
*/
CompilerSpecID getTraceCompilerSpecID();
/**
* Get the mapper which implements this offer
*
* @return the mapper
*/
DebuggerTargetTraceMapper take();
}

View file

@ -1,131 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import java.util.*;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.Endian;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint;
/**
* An opinion governing selection of language and compiler spec when recording a target
*
* <p>
* When a target is recorded, the model service collects offers for the target and its environment
* by querying all opinions discovered in the classpath. See
* {@link #queryOpinions(TargetObject, boolean)}. If the recording was triggered automatically, the
* highest-confidence offer is taken, so long as it is not an "override" offer. If triggered
* manually, all offers are displayed for the user to choose from.
*
* <p>
* Override offers have negative confidence, and typically, one is only selected by the user as a
* last-ditch effort when no opinion exists for the desired target. As such, one is never selected
* automatically, and they are hidden from the manual record prompt by default.
*/
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerMappingOpinion extends ExtensionPoint {
/**
* A comparator for sorting offers by decreasing confidence
*/
Comparator<DebuggerMappingOffer> HIGHEST_CONFIDENCE_FIRST =
Comparator.comparing(o -> -o.getConfidence());
/**
* Get the endianness from the given environment
*
* @param env the target environment
* @return the endianness
*/
public static Endian getEndian(TargetEnvironment env) {
String strEndian = env.getEndian();
if (strEndian.contains("little")) {
return Endian.LITTLE;
}
if (strEndian.contains("big")) {
return Endian.BIG;
}
return null;
}
/**
* Query all known opinions for recording/tracing a debug session
*
* <p>
* The returned offers are ordered highest-confidence first.
*
* @param target the target to be recorded, usually a process
* @param includeOverrides true to include offers with negative confidence
* @return a list of offers ordered highest confidence first
*/
public static List<DebuggerMappingOffer> queryOpinions(TargetObject target,
boolean includeOverrides) {
List<DebuggerMappingOffer> result = new ArrayList<>();
for (DebuggerMappingOpinion opinion : ClassSearcher
.getInstances(DebuggerMappingOpinion.class)) {
try {
Set<DebuggerMappingOffer> offers = opinion.getOffers(target, includeOverrides);
synchronized (result) {
result.addAll(offers);
}
}
catch (Throwable t) {
Msg.error(DebuggerMappingOpinion.class,
"Problem querying opinion " + opinion + " for recording/mapping offers: " + t);
}
}
result.sort(HIGHEST_CONFIDENCE_FIRST);
return result;
}
/**
* Checks if this opinion knows how to handle the given target
*
* @param target the target, usually a process
* @return a future which completes with true if it knows, false if not
*/
public default Set<DebuggerMappingOffer> getOffers(TargetObject target,
boolean includeOverrides) {
// TODO: Remove this check?
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
DebuggerObjectModel model = process.getModel();
List<String> pathToEnv =
model.getRootSchema().searchForSuitable(TargetEnvironment.class, process.getPath());
if (pathToEnv == null) {
Msg.error(this, "Could not find path to environment");
return Set.of();
}
TargetEnvironment env = (TargetEnvironment) model.getModelObject(pathToEnv);
return offersForEnv(env, process, includeOverrides);
}
/**
* Produce this opinion's offers for the given environment and target
*
* @param env the environment associated with the target
* @param target the target (usually a process)
* @param includeOverrides true to include override offers, i.e., those with negative confidence
* @return the offers, possibly empty, but never null
*/
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides);
}

View file

@ -1,68 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import ghidra.program.model.address.*;
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerMemoryMapper {
/**
* Map the given address from the trace into the target process
*
* @param traceAddr the address in the view's address space
* @return the "same address" in the target's address space
*/
Address traceToTarget(Address traceAddr);
/**
* Map the given address range from the trace into the target process
*
* @param traceRange the range in the view's address space
* @return the "same range" in the target's address space
*/
default AddressRange traceToTarget(AddressRange traceRange) {
return new AddressRangeImpl(traceToTarget(traceRange.getMinAddress()),
traceToTarget(traceRange.getMaxAddress()));
}
/**
* Map the given address from the target process into the trace
*
* @param targetAddr the address in the target's address space
* @return the "same address" in the trace's address space
*/
Address targetToTrace(Address targetAddr);
/**
* Map the given address range from the target process into the trace
*
* @param targetRange the range in the target's address space
* @return the "same range" in the trace's address space
*/
default AddressRange targetToTrace(AddressRange targetRange) {
return new AddressRangeImpl(targetToTrace(targetRange.getMinAddress()),
targetToTrace(targetRange.getMaxAddress()));
}
/**
* Map the given address range from the target process into the trace, truncating it to the
* portion intersecting the trace-side address space
*
* @param targetRange the range in the target's address space
* @return the intersection of the "same range" and the trace's address space
*/
AddressRange targetToTraceTruncated(AddressRange targetRange);
}

View file

@ -1,226 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetLauncher;
import ghidra.dbg.target.TargetObject;
import ghidra.debug.api.ValStr;
import ghidra.util.task.TaskMonitor;
/**
* An offer to launch a program with a given mechanism
*
* <p>
* Typically each offer is configured with the program it's going to launch, and knows how to work a
* specific connector and platform to obtain a target executing the program's image. The mechanisms
* may vary wildly from platform to platform.
*/
@Deprecated(forRemoval = true, since = "11.3")
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;
}
public enum PromptMode {
/**
* The user is always prompted for parameters.
*/
ALWAYS,
/**
* The user is never prompted for parameters.
*/
NEVER,
/**
* The user is prompted after an error.
*/
ON_ERROR;
}
/**
* 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, ValStr<?>> configureLauncher(TargetLauncher launcher,
Map<String, ValStr<?>> 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, PromptMode prompt,
LaunchConfigurator configurator);
/**
* 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
* @return a future which completes when the program is launched
*/
default CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode prompt) {
return launchProgram(monitor, prompt, LaunchConfigurator.NOP);
}
/**
* A name so that this offer can be recognized later
*
* <p>
* The name is saved to configuration files, so that user preferences and priorities can be
* memorized. The opinion will generate each offer fresh each time, so it's important that the
* "same offer" have the same configuration name. Note that the name <em>cannot</em> depend on
* the program name, but can depend on the model factory and program language and/or compiler
* spec. This name cannot contain semicolons ({@ code ;}).
*
* @return the configuration name
*/
String getConfigName();
/**
* Get the icon displayed in the UI for this offer
*
* <p>
* Don't override this except for good reason. If you do override, please return a variant that
* still resembles this icon, e.g., just overlay on this one.
*
* @return the icon
*/
Icon getIcon();
/**
* Get the text display on the parent menu for this offer
*
* <p>
* Unless there's good reason, this should always be "Debug [executablePath]".
*
* @return the title
*/
String getMenuParentTitle();
/**
* Get the text displayed on the menu for this offer
*
* @return the title
*/
String getMenuTitle();
/**
* Get the text displayed if the user will not be prompted
*
* <p>
* Sometimes when "the last options" are being used without prompting, it's a good idea to
* remind the user what those options were.
*
* @return the title
*/
default String getQuickTitle() {
return getMenuTitle();
}
/**
* Get the text displayed on buttons for this offer
*
* @return the title
*/
default String getButtonTitle() {
return getMenuParentTitle() + " " + getQuickTitle();
}
/**
* Get the default priority (position in the menu) of the offer
*
* <p>
* Note that greater priorities will be listed first, with the greatest being the default "quick
* launch" offer.
*
* @return the priority
*/
default int defaultPriority() {
return 50;
}
}

View file

@ -1,194 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.util.ConversionUtils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
/**
* A mapper which can convert register names and values from trace to target and vice versa.
*
* <P>
* As a general principle, and as a means of avoiding aliasing within caches, the debugger transfers
* register values to and from the target using its base registers only. This has its own drawbacks,
* but I find it preferable to requiring register caches (which are implemented generically) to know
* the aliasing structure of the register set. That principle could change if the base-register-only
* scheme becomes unwieldy. That said, a register mapper can map to non-base registers on target,
* but the trace side of the mapping must deal exclusively in base registers.
*/
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerRegisterMapper {
/**
* Get a target register (name) by string
*
* @param name the name of the register as a string
* @return the register description (name) on target
*/
TargetRegister getTargetRegister(String name);
/**
* Get a trace register (name) by string
*
* @param name the name of the register as a string
* @return the register description (as defined by the Ghidra language) in the trace
*/
Register getTraceRegister(String name);
/**
* Convert a register value to a string-byte-array entry suitable for the debug API
*
* <P>
* Byte arrays for the debug model API are always big-endian, no matter the target architecture.
*
* @param registerValue the register value
* @return the entry
*/
default Entry<String, byte[]> traceToTarget(RegisterValue registerValue) {
Register lReg = registerValue.getRegister();
if (!lReg.isBaseRegister()) {
throw new IllegalArgumentException();
}
TargetRegister tReg = traceToTarget(lReg);
if (tReg == null) {
return null;
}
return Map.entry(tReg.getIndex(), ConversionUtils
.bigIntegerToBytes(lReg.getMinimumByteSize(), registerValue.getUnsignedValue()));
}
/**
* Convert a collection of register values to a string-byte-array map suitable for the debug API
*
* @param registerValues the collection of values
* @return the map
*/
default Map<String, byte[]> traceToTarget(Collection<RegisterValue> registerValues) {
Map<String, byte[]> result = new LinkedHashMap<>();
for (RegisterValue rv : registerValues) {
Entry<String, byte[]> entry = traceToTarget(rv);
if (entry != null) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
/**
* Convert a trace register name to a target register name
*
* @param register the trace register
* @return the target register
*/
TargetRegister traceToTarget(Register register);
/**
* Convert a target register name and byte array value into a trace register value
*
* @param tRegName the name of the target register as a string
* @param value the value of the target register
* @return the converted register value suitable for trace storage
*/
default RegisterValue targetToTrace(String tRegName, byte[] value) {
TargetRegister tReg = getTargetRegister(tRegName);
if (tReg == null) {
return null;
}
return targetToTrace(tReg, value);
}
/**
* Convert a target register name and byte array value into a trace register value
*
* @param tReg the name of the target register
* @param value the value of the target register
* @return the converted register value suitable for trace storage
*/
default RegisterValue targetToTrace(TargetRegister tReg, byte[] value) {
if (value == null) {
return null;
}
Register lReg = targetToTrace(tReg);
if (lReg == null) {
return null;
}
BigInteger big = new BigInteger(1, value);
return new RegisterValue(lReg, big);
}
/**
* Convert a string-byte-value map to a map of trace register values
*
* @param values the target values
* @return the trace values
*/
default Map<Register, RegisterValue> targetToTrace(Map<String, byte[]> values) {
Map<Register, RegisterValue> result = new LinkedHashMap<>();
for (Map.Entry<String, byte[]> ent : values.entrySet()) {
RegisterValue rv = targetToTrace(ent.getKey(), ent.getValue());
if (rv != null) {
result.put(rv.getRegister(), rv);
}
}
return result;
}
/**
* Convert a target register name to a trace register name
*
* @param tReg the target register name
* @return the trace register name (as defined by the Ghidra language)
*/
Register targetToTrace(TargetRegister tReg);
/**
* Get the (base) registers on target that can be mapped
*
* @return the collection of base registers
*/
Set<Register> getRegistersOnTarget();
/**
* The recorder is informing this mapper of a new target register
*
* <P>
* The mapper should check that the given register is in its scope.
*
* @param register the new register
*/
void targetRegisterAdded(TargetRegister register);
/**
* The recorder is informing this mapper of a removed target register
*
* <P>
* This may seem impossible, but it can happen on architectures that support native emulation,
* and the debugger changes its register definitions (mid-execution) accordingly. One important
* example is WoW64 when switching between the 32-bit executable image and 64-bit system
* libraries. The 64-bit registers are not accessible when the processor is in 32-bit mode.
*
* <P>
* The mapper should check that the given register is/was in its scope.
*
* @param register the old register
*/
void targetRegisterRemoved(TargetRegister register);
}

View file

@ -1,31 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
// TODO: Could probably just have Offer.take() go straight to recording....
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerTargetTraceMapper {
Language getTraceLanguage();
CompilerSpec getTraceCompilerSpec();
TraceRecorder startRecording(PluginTool tool, Trace trace);
}

View file

@ -1,786 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.task.TaskMonitor;
/**
* A recorder from target object, usually a process, to a destination trace
*
* <p>
* The recorder is the glue from a portion of a debugger's model into a Ghidra trace. As such, this
* object maintains a mapping between corresponding objects of interest in the model tree to the
* trace, and that mapping can be queried. In most cases, UI components which deal with tracing need
* only read the trace in order to populate their display. Several methods are provided for
* retrieving corresponding objects from the target or trace given that object in the other. These
* methods may return null for a variety of reasons:
*
* <ol>
* <li>The particular type may not be supported or of interest to the recorder.</li>
* <li>The recorder may not have actually recorded the object yet, despite receiving notice.
* Recording is asynchronous, and it may also be waiting for additional dependencies or attributes
* before it can create the corresponding trace object.</li>
* <li>The target object may no longer exist for a given trace object.</li>
* </ol>
*
* <p>
* The recorder copies information in one direction; thus, if a trace UI component needs to affect
* the target, it must do so via the debugger's model. Such components should use the
* {@code getTarget*} methods in order to find the target object for a given trace object. The
* effects of calls on recorded target objects are recorded into the trace automatically. Thus, it
* is not necessary for trace UI components to react to the successful completion of such calls, so
* long as they're listening for changes to the trace. However, they MUST react to the exceptional
* completion of such calls, most likely displaying an error dialog.
*/
@Deprecated(forRemoval = true, since = "11.3")
public interface TraceRecorder {
/**
* Convert breakpoint kind from the target enum to the trace enum
*
* @param kind the target kind
* @return the trace kind
*/
static TraceBreakpointKind targetToTraceBreakpointKind(TargetBreakpointKind kind) {
switch (kind) {
case READ:
return TraceBreakpointKind.READ;
case WRITE:
return TraceBreakpointKind.WRITE;
case HW_EXECUTE:
return TraceBreakpointKind.HW_EXECUTE;
case SW_EXECUTE:
return TraceBreakpointKind.SW_EXECUTE;
default:
throw new AssertionError();
}
}
/**
* Convert a collection of breakpoint kinds from the target enum to the trace enum
*
* @param kinds the target kinds
* @return the trace kinds
*/
static Set<TraceBreakpointKind> targetToTraceBreakpointKinds(
Collection<TargetBreakpointKind> kinds) {
return kinds.stream()
.map(TraceRecorder::targetToTraceBreakpointKind)
.collect(Collectors.toSet());
}
/**
* Convert breakpoint kind from the trace enum to the target enum
*
* @param kind
* @return
*/
static TargetBreakpointKind traceToTargetBreakpointKind(TraceBreakpointKind kind) {
switch (kind) {
case READ:
return TargetBreakpointKind.READ;
case WRITE:
return TargetBreakpointKind.WRITE;
case HW_EXECUTE:
return TargetBreakpointKind.HW_EXECUTE;
case SW_EXECUTE:
return TargetBreakpointKind.SW_EXECUTE;
default:
throw new AssertionError();
}
}
/**
* Convert a collection of breakpoint kinds from the trace enum to the target enum
*
* @param kinds the trace kinds
* @return the target kinds
*/
static Set<TargetBreakpointKind> traceToTargetBreakpointKinds(
Collection<TraceBreakpointKind> kinds) {
return kinds.stream()
.map(TraceRecorder::traceToTargetBreakpointKind)
.collect(Collectors.toSet());
}
/**
* Initialize this recorder, if not already initialized.
*
* The model service starts the initialization. This method can be used to react to the
* completion of that initialization.
*
* @return the future which completes when initialization is complete
*/
CompletableFuture<Void> init();
/**
* Get the "root" of the sub-tree being recorded.
*
* @return the target object, usually a process
*/
TargetObject getTarget();
/**
* Get the destination trace for this recording
*
* @return the trace
*/
Trace getTrace();
/**
* Get the key for the latest, current snapshot generated by this recorder
*
* @return the snap
*/
long getSnap();
/**
* Force the recorder to take a snapshot
*
* Note that using the {@link TraceTimeManager} of the recorder's destination trace to take a
* new snapshot will not advance the recorder's internal snapshot counter. Use this method
* instead if you want to create a snapshot manually.
*
* @return the new snapshot
*/
TraceSnapshot forceSnapshot();
/**
* Check if this recorder is actively recording
*
* @return true if recording, false if stopped
*/
boolean isRecording();
/**
* Check if recording is active and the given view is at the present
*
* <p>
* To be at the present means the view's trace and snap matches the recorder's trace and snap.
* The recorder must also be actively recording. Otherwise, this returns {@code false}.
*
* @return true if the given view is at the present
*/
void stopRecording();
/**
* Add a listener to observe recorder events
*
* @param listener the listener
*/
void addListener(TraceRecorderListener listener);
/**
* Remove a listener
*
* @param listener the listener
*/
void removeListener(TraceRecorderListener listener);
/**
* Get the target object corresponding to the given trace object
*
* @param obj the trace object
* @return the target object, or null
*/
TargetObject getTargetObject(TraceObject obj);
/**
* Get the target object corresponding to the given path
*
* @param obj the trace object
* @return the target object, or null
*/
TargetObject getTargetObject(TraceObjectKeyPath path);
/**
* Get the trace object corresponding to the given target object
*
* @param obj the target object
* @return the trace object, or null
*/
TraceObject getTraceObject(TargetObject obj);
/**
* Get the target breakpoint location corresponding to the given trace breakpoint
*
* @param obj the trace breakpoint
* @return the target breakpoint location, or null
*/
TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt);
/**
* Get the trace breakpoint corresponding to the given target breakpoint location
*
* @param obj the target breakpoint location
* @return the trace breakpoint, or null
*/
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
/**
* Get the target memory region corresponding to the given trace memory region
*
* @param obj the trace memory region
* @return the target memory region, or null
*/
TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region);
/**
* Get the trace memory region corresponding to the given target memory region
*
* @param obj the target memory region
* @return the trace memory region, or null
*/
TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region);
/**
* Get the target module corresponding to the given trace module
*
* @param obj the trace module
* @return the target module, or null
*/
TargetModule getTargetModule(TraceModule module);
/**
* Get the trace module corresponding to the given target module
*
* @param obj the target module
* @return the trace module, or null
*/
TraceModule getTraceModule(TargetModule module);
/**
* Get the target section corresponding to the given trace section
*
* @param obj the trace section
* @return the target section, or null
*/
TargetSection getTargetSection(TraceSection section);
/**
* Get the trace section corresponding to the given target section
*
* @param obj the target section
* @return the trace section, or null
*/
TraceSection getTraceSection(TargetSection section);
/**
* Get the target thread corresponding to the given trace thread
*
* @param obj the trace thread
* @return the target thread, or null
*/
TargetThread getTargetThread(TraceThread thread);
/**
* Get the execution state of the given target thread
*
* @param thread the target thread
* @return the execution state, or null
*/
TargetExecutionState getTargetThreadState(TargetThread thread);
/**
* Get the execution state of the given trace thread
*
* @param thread the trace thread
* @return the execution state, or null
*/
TargetExecutionState getTargetThreadState(TraceThread thread);
/**
* Get the target register bank for the given trace thread and frame level
*
* <p>
* If the model doesn't provide a bank for every frame, then this should only return non-null
* for frame level 0, in which case it should return the bank for the given thread.
*
* @param thread the thread
* @param frameLevel the frame level
* @return the bank, or null
*/
Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel);
/**
* Get the trace thread corresponding to the given target thread
*
* @param obj the target thread
* @return the trace thread, or null
*/
TraceThread getTraceThread(TargetThread thread);
/**
* Find the trace thread containing the given successor target object
*
* @param successor the target object
* @return the trace thread containing the object, or null
*/
TraceThread getTraceThreadForSuccessor(TargetObject successor);
/**
* Get the trace stack frame for the given target stack frame
*
* @param frame the target stack frame
* @return the trace stack frame, or null
*/
TraceStackFrame getTraceStackFrame(TargetStackFrame frame);
/**
* Get the trace stack frame containing the given successor target object
*
* @param successor the target object
* @return the trace stack frame containing the object, or null
*/
TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor);
/**
* Get the target stack frame for the given trace thread and frame level
*
* @param thread the thread
* @param frameLevel the frame level
* @return the stack frame, or null
*/
TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel);
/**
* Get all the target's threads that are currently alive
*
* @return the set of live target threads
*/
Set<TargetThread> getLiveTargetThreads();
/**
* Get the register mapper for the given trace thread
*
* @param thread the trace thread
* @return the mapper, or null
*/
DebuggerRegisterMapper getRegisterMapper(TraceThread thread);
/**
* Get the memory mapper for the target
*
* @return the mapper, or null
*/
DebuggerMemoryMapper getMemoryMapper();
/**
* Check if the given register bank is accessible
*
* @param bank the target register bank
* @return true if accessible
* @deprecated the accessibility concept was never really implemented nor offered anything of
* value. It has no replacement. Instead a model should reject requests its not
* prepared to handle, or queue them up to be processed when it can. If the latter,
* then ideally it should only allow one instance of a given request to be queued.
*/
@Deprecated
boolean isRegisterBankAccessible(TargetRegisterBank bank);
/**
* Check if the register bank for the given trace thread and frame level is accessible
*
* @param thread the trace thread
* @param frameLevel the frame level
* @see #getTargetStackFrame(TraceThread, int)
* @see #isRegisterBankAccessible(TargetRegisterBank)
* @return true if accessible
* @deprecated for the same reasons as {@link #isRegisterBankAccessible(TargetRegisterBank)}
*/
@Deprecated
boolean isRegisterBankAccessible(TraceThread thread, int frameLevel);
/**
* Get the set of accessible target memory, as viewed in the trace
*
* @return the computed set
*/
AddressSetView getAccessibleMemory();
/**
* Capture a target thread's registers.
*
* <p>
* Ordinarily, debugger models should gratuitously notify of register value changes.
* Nevertheless, this method can force the retrieval of a given set of registers from the
* target.
*
* @param platform the platform whose language defines the registers
* @param thread the trace thread associated with the desired target thread
* @param frameLevel the number of stack frames to "unwind", likely 0
* @param registers the <em>base</em> registers, as viewed by the trace
* @return a future which completes when the commands succeed
* @throws IllegalArgumentException if no {@link TargetRegisterBank} is known for the given
* thread
*/
CompletableFuture<Void> captureThreadRegisters(TracePlatform platform,
TraceThread thread, int frameLevel, Set<Register> registers);
/**
* Write a target thread's registers.
*
* <p>
* Note that the model and recorder should cause values successfully written on the target to be
* updated in the trace. The caller should not update the trace out of band.
*
* @param platform the platform whose language defines the registers
* @param thread the trace thread associated with the desired target thread
* @param frameLevel the number of stack frames to "unwind", likely 0
* @param values the values to write
* @return a future which completes when the registers have been captured.
* @throws IllegalArgumentException if no {@link TargetRegisterBank} is known for the given
* thread
*/
CompletableFuture<Void> writeThreadRegisters(TracePlatform platform, TraceThread thread,
int frameLevel, Map<Register, RegisterValue> values);
/**
* Read (and capture) a range of target memory
*
* @param start the address to start at, as viewed in the trace
* @param length the number of bytes to read
* @return a future which completes with the read bytes
*/
CompletableFuture<byte[]> readMemory(Address start, int length);
/**
* Write (and capture) a range of target memory
*
* @param start the address to start at, as viewed in the trace
* @param data the data to write
* @return a future which completes when the entire write is complete
*/
CompletableFuture<Void> writeMemory(Address start, byte[] data);
/**
* Read (and capture) several blocks of target memory
*
* <p>
* The given address set is quantized to the minimal set of blocks covering the requested set.
* To capture a precise range, use {@link #readMemory(Address, int)} instead. Though this
* function returns immediately, the given monitor will be updated in the background as the task
* progresses. Thus, the caller should ensure the monitor is visible until the returned future
* completes.
*
* <p>
* This task is relatively error tolerant. If a block or region cannot be captured -- a common
* occurrence -- the error is logged, but the task may still complete "successfully."
*
* @param set the addresses to capture, as viewed in the trace
* @param monitor a monitor for displaying task steps
* @param returnResult true to complete with results, false to complete with null
* @return a future which completes when the task finishes
*/
CompletableFuture<Void> readMemoryBlocks(AddressSetView set, TaskMonitor monitor);
/**
* Write a variable (memory or register) of the given thread or the process
*
* <p>
* This is a convenience for writing target memory or registers, based on address. If the given
* address represents a register, this will attempt to map it to a register and write it in the
* given thread and frame. If the address is in memory, it will simply delegate to
* {@link #writeMemory(Address, byte[])}.
*
* @param thread the thread. Ignored (may be null) if address is in memory
* @param frameLevel the frame, usually 0. Ignored if address is in memory
* @param address the starting address
* @param data the value to write
* @return a future which completes when the write is complete
*/
default CompletableFuture<Void> writeVariable(TracePlatform platform, TraceThread thread,
int frameLevel, Address address, byte[] data) {
if (address.isMemoryAddress()) {
return writeMemory(address, data);
}
if (address.isRegisterAddress()) {
return writeRegister(platform, Objects.requireNonNull(thread), frameLevel, address,
data);
}
throw new IllegalArgumentException("Address is not in a recognized space: " + address);
}
/**
* Write a register (by address) of the given thread
*
* @param thread the thread
* @param frameLevel the frame, usually 0.
* @param address the address of the register
* @param data the value to write
* @return a future which completes when the write is complete
*/
default CompletableFuture<Void> writeRegister(TracePlatform platform, TraceThread thread,
int frameLevel, Address address, byte[] data) {
Register register = platform.getLanguage().getRegister(address, data.length);
if (register == null) {
throw new IllegalArgumentException(
"Cannot identify the (single) register to write: " + address);
}
RegisterValue rv = new RegisterValue(register,
Utils.bytesToBigInteger(data, data.length, register.isBigEndian(), false));
TraceMemorySpace regs =
getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false);
Register parent = isRegisterOnTarget(platform, thread, frameLevel, register);
if (parent == null) {
throw new IllegalArgumentException("Cannot find register " + register + " on target");
}
rv = TraceRegisterUtils.combineWithTraceParentRegisterValue(parent, rv, platform, getSnap(),
regs, true);
return writeThreadRegisters(platform, thread, frameLevel, Map.of(rv.getRegister(), rv));
}
/**
* Check if the given register exists on target (is mappable) for the given thread
*
* @param platform the platform whose language defines the registers
* @param thread the thread whose registers to examine
* @param frameLevel the frame, usually 0.
* @param register the register to check
* @return the smallest parent register known for the given thread on target, or null
*/
Register isRegisterOnTarget(TracePlatform platform, TraceThread thread, int frameLevel,
Register register);
/**
* Check if the given trace address exists in target memory
*
* @param address the address to check
* @return true if the given trace address can be mapped to the target's memory
*/
default boolean isMemoryOnTarget(Address address) {
return getMemoryMapper().traceToTarget(address) != null;
}
/**
* Check if a given variable (register or memory) exists on target
*
* @param platform the platform whose language defines the registers
* @param thread if a register, the thread whose registers to examine
* @param frameLevel the frame, usually 0.
* @param address the address of the variable
* @param size the size of the variable. Ignored for memory
* @return true if the variable can be mapped to the target
*/
default boolean isVariableOnTarget(TracePlatform platform, TraceThread thread, int frameLevel,
Address address, int size) {
if (address.isMemoryAddress()) {
return isMemoryOnTarget(address);
}
if (thread == null) { // register-space addresses require a thread
return false;
}
Register register = platform.getLanguage().getRegister(address, size);
if (register == null) {
throw new IllegalArgumentException("Cannot identify the (single) register: " + address);
}
// TODO: Can any debugger modify regs up the stack?
if (frameLevel != 0) {
return false;
}
return isRegisterOnTarget(platform, thread, frameLevel, register) != null;
}
/**
* Capture the data types of a target's module.
*
* <p>
* Though this function returns immediately, the given monitor will be updated in the background
* as the task progresses. Thus, the caller should ensure the monitor is visible until the
* returned future completes.
*
* @param module the module whose types to capture
* @param monitor a monitor for displaying task steps
* @return a future which completes when the types have been captured.
*/
CompletableFuture<Void> captureDataTypes(TraceModule module, TaskMonitor monitor);
/**
* Capture the data types of a target's namespace.
*
* <p>
* Though this function returns immediately, the given monitor will be updated in the background
* as the task progresses. Thus, the caller should ensure the monitor is visible until the
* returned future completes.
*
* @param namespace the namespace whose types to capture
* @param monitor a monitor for displaying task steps
* @return a future which completes when the types have been captured.
*/
CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor);
/**
* Capture the symbols of a target's module.
*
* <p>
* Though this function returns immediately, the given monitor will be updated in the background
* as the task progresses. Thus, the caller should ensure the monitor is visible until the
* returned future completes.
*
* @param module the module whose symbols to capture
* @param monitor a monitor for displaying task steps
* @return a future which completes when the symbols have been captured.
*/
CompletableFuture<Void> captureSymbols(TraceModule module, TaskMonitor monitor);
/**
* Capture the symbols of a target's namespace.
*
* <p>
* Though this function returns immediately, the given monitor will be updated in the background
* as the task progresses. Thus, the caller should ensure the monitor is visible until the
* returned future completes.
*
* @param namespace the namespace whose symbols to capture
* @param monitor a monitor for displaying task steps
* @return a future which completes when the symbols have been captured.
*/
CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace, TaskMonitor monitor);
/**
* Collect breakpoint containers pertinent to the target or a given thread
*
* <p>
* This is commonly used to set a breakpoint on the appropriate container(s), since the recorder
* is already tracking the container according to established conventions. If preferred,
* breakpoints can be set directly on the model. The recorder will keep track accordingly either
* way.
*
* <p>
* If a thread is given, the recorder will include only the breakpoint container(s) pertinent to
* the given thread. Otherwise, it'll prefer only the process's breakpoint container. If the
* process doesn't have a breakpoint container, it'll include all containers pertinent to any
* thread in the process.
*
* @param thread an optional thread, or {@code null} for the process
* @return the list of collected containers, possibly empty
*/
List<TargetBreakpointSpecContainer> collectBreakpointContainers(TargetThread thread);
/**
* Collect effective breakpoint pertinent to the target or a given thread
*
* <p>
* If a thread is given, the recorder will include only the breakpoints pertinent to the given
* thread. Otherwise, it'll include every breakpoint pertinent to the process.
*
* @param thread an optional thread, or {@code null} for the process
* @return the list of collected breakpoints, possibly empty
*/
List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread);
/**
* Get the kinds of breakpoints supported by any of the recorded breakpoint containers.
*
* This is the union of all kinds supported among all {@link TargetBreakpointSpecContainer}s
* found applicable to the target by this recorder. Chances are, there is only one container.
*
* @return the set of supported kinds
*/
Set<TraceBreakpointKind> getSupportedBreakpointKinds();
/**
* Check if the target is subject to a {@link TargetEventScope}.
*
* @return true if an applicable scope is found, false otherwise.
*/
boolean isSupportsFocus();
/**
* Check if the target is subject to a {@link TargetActiveScope}.
*
* @return true if an applicable scope is found, false otherwise.
*/
boolean isSupportsActivation();
/**
* Get the last-focused object as observed by this recorder
*
* @impNote While focus events are not recorded in the trace, it's most fitting to process these
* events in the recorder. We'd like to track them per container, and we already have
* established a one-to-one map of containers to recorders, and each recorder already
* has the appropriate listener installed on the container sub-tree.
* @return the object which last had focus within this container, if applicable
*/
TargetObject getFocus();
/**
* Request focus on a successor of the target
*
* <p>
* The object must also be a successor of the focus scope, which is most cases is an ancestor of
* the target anyway. If this operation succeeds, the returned future completes with true.
* Otherwise, it logs the exception and completes with false. This is a convenience so that
* callers do not need to worry that it returns a future, unless they'd like to check for
* success.
*
* @param focus the object on which to focus
* @return a future which completes with true if the operation was successful, false otherwise.
*/
CompletableFuture<Boolean> requestFocus(TargetObject focus);
/**
* Request activation of a successor of the target
*
* @param active the object to activate
* @return a future which completes with true if the operation was successful, false otherwise.
*/
CompletableFuture<Boolean> requestActivation(TargetObject active);
/**
* Wait for pending transactions finish execution.
*
* <p>
* The returned future will complete when queued transactions have been executed. There are no
* guarantees regarding transactions submitted after this future is returned. Furthermore, it
* may still be necessary to wait for the trace to finish invoking its domain object change
* listeners.
*
* @return the future which completes when pending transactions finish execution.
*/
CompletableFuture<Void> flushTransactions();
}

View file

@ -1,70 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
/**
* A listener for state changes in a recorded target, or in the recorder itself
*
* <P>
* NOTE: This contains events that would not otherwise be detectable by listening to the destination
* trace. Some of these can be detected by listening to the model; however, since the recorder is
* already keeping track of most objects of interest, it makes sense for it to implement some
* conveniences for accessing and listening to those objects.
*/
@Deprecated(forRemoval = true, since = "11.3")
public interface TraceRecorderListener {
/**
* A new bank of registers has appeared, and its mapper instantiated
*
* @param recorder the recorder
*/
default void registerBankMapped(TraceRecorder recorder) {
}
/**
* Some bank of registers tracked by the given recorder has changed in accessibility
*
* @param recorder the recorder
*/
default void registerAccessibilityChanged(TraceRecorder recorder) {
}
/**
* Some portion of process memory tracked by the given recorder has changed in accessibility
*
* @param recorder the recorder
*/
default void processMemoryAccessibilityChanged(TraceRecorder recorder) {
}
/**
* The given recorder has ended its recording
*
* @param recorder the recorder that stopped
*/
default void recordingStopped(TraceRecorder recorder) {
}
/**
* The recorder has advanced a snap
*
* @param recorder the recorder that advanced
* @param snap the snap to which it advanced
*/
default void snapAdvanced(TraceRecorder recorder, long snap) {
}
}

View file

@ -1,657 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.flatapi;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import ghidra.app.services.DebuggerModelService;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
@Deprecated
public interface FlatDebuggerRecorderAPI extends FlatDebuggerAPI {
/**
* Get the model (legacy target) service
*
* @return the service
*/
default DebuggerModelService getModelService() {
return requireService(DebuggerModelService.class);
}
/**
* Get the target for a given trace
*
* <p>
* WARNING: This method will likely change or be removed in the future.
*
* @param trace the trace
* @return the target, or null if not alive
*/
default TargetObject getTarget(Trace trace) {
TraceRecorder recorder = getModelService().getRecorder(trace);
if (recorder == null) {
return null;
}
return recorder.getTarget();
}
/**
* Get the target thread for a given trace thread
*
* <p>
* WARNING: This method will likely change or be removed in the future.
*
* @param thread the trace thread
* @return the target thread, or null if not alive
*/
default TargetThread getTargetThread(TraceThread thread) {
TraceRecorder recorder = getModelService().getRecorder(thread.getTrace());
if (recorder == null) {
return null;
}
return recorder.getTargetThread(thread);
}
/**
* Get the user focus for a given trace
*
* <p>
* WARNING: This method will likely change or be removed in the future.
*
* @param trace the trace
* @return the target, or null if not alive
*/
default TargetObject getTargetFocus(Trace trace) {
TraceRecorder recorder = getModelService().getRecorder(trace);
if (recorder == null) {
return null;
}
TargetObject focus = recorder.getFocus();
return focus != null ? focus : recorder.getTarget();
}
/**
* Find the most suitable object related to the given object implementing the given interface
*
* <p>
* WARNING: This method will likely change or be removed in the future.
*
* @param <T> the interface type
* @param seed the seed object
* @param iface the interface class
* @return the related interface, or null
* @throws ClassCastException if the model violated its schema wrt. the requested interface
*/
@SuppressWarnings("unchecked")
default <T extends TargetObject> T findInterface(TargetObject seed, Class<T> iface) {
DebuggerObjectModel model = seed.getModel();
List<String> found = model
.getRootSchema()
.searchForSuitable(iface, seed.getPath());
if (found == null) {
return null;
}
try {
Object value = waitOn(model.fetchModelValue(found));
return (T) value;
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return null;
}
}
/**
* Find the most suitable object related to the given thread implementing the given interface
*
* @param <T> the interface type
* @param thread the thread
* @param iface the interface class
* @return the related interface, or null
* @throws ClassCastException if the model violated its schema wrt. the requested interface
*/
default <T extends TargetObject> T findInterface(TraceThread thread, Class<T> iface) {
TargetThread targetThread = getTargetThread(thread);
if (targetThread == null) {
return null;
}
return findInterface(targetThread, iface);
}
/**
* Find the most suitable object related to the given trace's focus implementing the given
* interface
*
* @param <T> the interface type
* @param trace the trace
* @param iface the interface class
* @return the related interface, or null
* @throws ClassCastException if the model violated its schema wrt. the requested interface
*/
default <T extends TargetObject> T findInterface(Trace trace, Class<T> iface) {
TargetObject focus = getTargetFocus(trace);
if (focus == null) {
return null;
}
return findInterface(focus, iface);
}
/**
* Find the interface related to the current thread or trace
*
* <p>
* This first attempts to find the most suitable object related to the current trace thread. If
* that fails, or if there is no current thread, it tries to find the one related to the current
* trace (or its focus). If there is no current trace, this throws an exception.
*
* @param <T> the interface type
* @param iface the interface class
* @return the related interface, or null
* @throws IllegalStateException if there is no current trace
*/
default <T extends TargetObject> T findInterface(Class<T> iface) {
TraceThread thread = getCurrentThread();
T t = thread == null ? null : findInterface(thread, iface);
if (t != null) {
return t;
}
return findInterface(requireCurrentTrace(), iface);
}
/**
* Step the given target object
*
* @param steppable the steppable target object
* @param kind the kind of step to take
* @return true if successful, false otherwise
*/
default boolean step(TargetSteppable steppable, TargetStepKind kind) {
if (steppable == null) {
return false;
}
try {
waitOn(steppable.step(kind));
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
return true;
}
/**
* Step the given thread on target according to the given kind
*
* @param thread the trace thread
* @param kind the kind of step to take
* @return true if successful, false otherwise
*/
default boolean step(TraceThread thread, TargetStepKind kind) {
if (thread == null) {
return false;
}
return step(findInterface(thread, TargetSteppable.class), kind);
}
/**
* Resume execution of the given target object
*
* @param resumable the resumable target object
* @return true if successful, false otherwise
*/
default boolean resume(TargetResumable resumable) {
if (resumable == null) {
return false;
}
try {
waitOn(resumable.resume());
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
return true;
}
/**
* Interrupt execution of the given target object
*
* @param interruptible the interruptible target object
* @return true if successful, false otherwise
*/
default boolean interrupt(TargetInterruptible interruptible) {
if (interruptible == null) {
return false;
}
try {
waitOn(interruptible.interrupt());
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
return true;
}
/**
* Terminate execution of the given target object
*
* @param interruptible the interruptible target object
* @return true if successful, false otherwise
*/
default boolean kill(TargetKillable killable) {
if (killable == null) {
return false;
}
try {
waitOn(killable.kill());
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
return true;
}
/**
* Get the current state of the given target
*
* <p>
* Any invalidated object is considered {@link TargetExecutionState#TERMINATED}. Otherwise, it's
* at least considered {@link TargetExecutionState#ALIVE}. A more specific state may be
* determined by searching the model for the conventionally-related object implementing
* {@link TargetObjectStateful}. This method applies this convention.
*
* @param target the target object
* @return the target object's execution state
*/
default TargetExecutionState getExecutionState(TargetObject target) {
if (!target.isValid()) {
return TargetExecutionState.TERMINATED;
}
TargetExecutionStateful stateful = findInterface(target, TargetExecutionStateful.class);
return stateful == null ? TargetExecutionState.ALIVE : stateful.getExecutionState();
}
/**
* Waits for the given target to exit the {@link TargetExecutionState#RUNNING} state
*
* <p>
* <b>NOTE:</b> There may be subtleties depending on the target debugger. For the most part, if
* the connection is handling a single target, things will work as expected. However, if there
* are multiple targets on one connection, it is possible for the given target to break, but for
* the target debugger to remain unresponsive to commands. This would happen, e.g., if a second
* target on the same connection is still running.
*
* @param target the target
* @param timeout the maximum amount of time to wait
* @param unit the units for time
* @throws TimeoutException if the timeout expires
*/
default void waitForBreak(TargetObject target, long timeout, TimeUnit unit)
throws TimeoutException {
TargetExecutionStateful stateful = findInterface(target, TargetExecutionStateful.class);
if (stateful == null) {
throw new IllegalArgumentException("Given target is not stateful");
}
var listener = new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
CompletableFuture<Void> future = new CompletableFuture<>();
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
private void stateChanged(TargetObject parent, TargetExecutionState state) {
if (parent == stateful && !state.isRunning()) {
future.complete(null);
}
}
};
target.getModel().addModelListener(listener);
try {
if (!stateful.getExecutionState().isRunning()) {
return;
}
listener.future.get(timeout, unit);
}
catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
finally {
target.getModel().removeModelListener(listener);
}
}
@Override
default void waitForBreak(Trace trace, long timeout, TimeUnit unit) throws TimeoutException {
TargetObject target = getTarget(trace);
if (target == null || !target.isValid()) {
return;
}
waitForBreak(target, timeout, unit);
}
/**
* Execute a command in a connection's interpreter, capturing the output
*
* <p>
* This executes a raw command in the given interpreter. The command could have arbitrary
* effects, so it may be necessary to wait for those effects to be handled by the tool's
* services and plugins before proceeding.
*
* @param interpreter the interpreter
* @param command the command
* @return the output, or null if there is no interpreter
*/
default String executeCapture(TargetInterpreter interpreter, String command) {
if (interpreter == null) {
return null;
}
try {
return waitOn(interpreter.executeCapture(command));
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return null;
}
}
/**
* Execute a command in a connection's interpreter
*
* <p>
* This executes a raw command in the given interpreter. The command could have arbitrary
* effects, so it may be necessary to wait for those effects to be handled by the tool's
* services and plugins before proceeding.
*
* @param interpreter the interpreter
* @param command the command
* @return true if successful
*/
default boolean execute(TargetInterpreter interpreter, String command) {
if (interpreter == null) {
return false;
}
try {
waitOn(interpreter.executeCapture(command));
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
return true;
}
/**
* Get the value at the given path for the given model
*
* @param model the model
* @param path the path
* @return the avlue, or null if the trace is not live or if the path does not exist
*/
default Object getModelValue(DebuggerObjectModel model, String path) {
try {
return waitOn(model.fetchModelValue(PathUtils.parse(path)));
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return null;
}
}
/**
* Get the value at the given path for the current trace's model
*
* @param path the path
* @return the value, or null if the trace is not live or if the path does not exist
*/
default Object getModelValue(String path) {
TraceRecorder recorder = getModelService().getRecorder(getCurrentTrace());
if (recorder == null) {
return null;
}
return getModelValue(recorder.getTarget().getModel(), path);
}
/**
* Refresh the given objects children (elements and attributes)
*
* @param object the object
* @return the set of children, excluding primitive-valued attributes
*/
default Set<TargetObject> refreshObjectChildren(TargetObject object) {
try {
// Refresh both children and memory/register values
waitOn(object.invalidateCaches());
waitOn(object.resync());
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return null;
}
Set<TargetObject> result = new LinkedHashSet<>();
result.addAll(object.getCachedElements().values());
for (Object v : object.getCachedAttributes().values()) {
if (v instanceof TargetObject) {
result.add((TargetObject) v);
}
}
return result;
}
/**
* Refresh the given object and its children, recursively
*
* <p>
* The objects are traversed in depth-first pre-order. Links are traversed, even if the object
* is not part of the specified subtree, but an object is skipped if it has already been
* visited.
*
* @param object the seed object
* @return true if the traversal completed successfully
*/
default boolean refreshSubtree(TargetObject object) {
var util = new Object() {
Set<TargetObject> visited = new HashSet<>();
boolean visit(TargetObject object) {
if (!visited.add(object)) {
return true;
}
for (TargetObject child : refreshObjectChildren(object)) {
if (!visit(child)) {
return false;
}
}
return true;
}
};
return util.visit(object);
}
/**
* {@inheritDoc}
*
* <p>
* This override includes flushing the recorder's event and transaction queues.
*/
@Override
default boolean flushAsyncPipelines(Trace trace) {
try {
TraceRecorder recorder = getModelService().getRecorder(trace);
if (recorder != null) {
waitOn(recorder.getTarget().getModel().flushEvents());
waitOn(recorder.flushTransactions());
}
trace.flushEvents();
waitOn(getMappingService().changesSettled());
waitOn(getBreakpointService().changesSettled());
Swing.allowSwingToProcessEvents();
return true;
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
}
/**
* Get offers for launching the given program
*
* @param program the program
* @return the offers
*/
default List<DebuggerProgramLaunchOffer> getLaunchOffers(Program program) {
return getModelService().getProgramLaunchOffers(program).collect(Collectors.toList());
}
/**
* Get offers for launching the current program
*
* @return the offers
*/
default List<DebuggerProgramLaunchOffer> getLaunchOffers() {
return getLaunchOffers(requireCurrentProgram());
}
/**
* Get the best launch offer for a program, throwing an exception if there is no offer
*
* @param program the program
* @return the offer
* @throws NoSuchElementException if there is no offer
*/
default DebuggerProgramLaunchOffer requireLaunchOffer(Program program) {
Optional<DebuggerProgramLaunchOffer> offer =
getModelService().getProgramLaunchOffers(program).findFirst();
if (offer.isEmpty()) {
throw new NoSuchElementException("No offers to launch " + program);
}
return offer.get();
}
/**
* Launch the given offer, overriding its command line
*
* <p>
* <b>NOTE:</b> Most offers take a command line, but not all do. If this is used for an offer
* that does not, it's behavior is undefined.
*
* <p>
* Launches are not always successful, and may in fact fail frequently, usually because of
* configuration errors or missing components on the target platform. This may leave stale
* connections and/or target debuggers, processes, etc., in strange states. Furthermore, even if
* launching the target is successful, starting the recorder may not succeed, typically because
* Ghidra cannot identify and map the target platform to a Sleigh language. This method makes no
* attempt at cleaning up partial pieces. Instead it returns those pieces in the launch result.
* If the result includes a recorder, the launch was successful. If not, the script can decide
* what to do with the other pieces. That choice depends on what is expected of the user. Can
* the user reasonable be expected to intervene and complete the launch manually? How many
* targets does the script intend to launch? How big is the mess if left partially completed?
*
* @param offer the offer (this includes the program given when asking for offers)
* @param commandLine the command-line override. If this doesn't refer to the same program as
* the offer, there may be unexpected results
* @param monitor the monitor for the launch stages
* @return the result, possibly partial
*/
default LaunchResult launch(DebuggerProgramLaunchOffer offer, String commandLine,
TaskMonitor monitor) {
try {
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() {
@Override
public Map<String, ValStr<?>> configureLauncher(TargetLauncher launcher,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
Map<String, ValStr<?>> adjusted = new HashMap<>(arguments);
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, ValStr.str(commandLine));
return adjusted;
}
}));
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
// TODO: This is not ideal, since it's likely partially completed
return LaunchResult.totalFailure(e);
}
}
/**
* Launch the given offer with the default/saved arguments
*
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
*/
default LaunchResult launch(DebuggerProgramLaunchOffer offer, TaskMonitor monitor) {
try {
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER));
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
// TODO: This is not ideal, since it's likely partially completed
return LaunchResult.totalFailure(e);
}
}
/**
* Launch the given program, overriding its command line
*
* <p>
* This takes the best offer for the given program. The command line should invoke the given
* program. If it does not, there may be unexpected results.
*
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
*/
default LaunchResult launch(Program program, String commandLine, TaskMonitor monitor)
throws InterruptedException, ExecutionException, TimeoutException {
return launch(requireLaunchOffer(program), commandLine, monitor);
}
/**
* Launch the given program with the default/saved arguments
*
* <p>
* This takes the best offer for the given program.
*
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
*/
default LaunchResult launch(Program program, TaskMonitor monitor)
throws InterruptedException, ExecutionException, TimeoutException {
return launch(requireLaunchOffer(program), monitor);
}
/**
* Launch the current program, overriding its command line
*
* @see #launch(Program, String, TaskMonitor)
*/
default LaunchResult launch(String commandLine, TaskMonitor monitor)
throws InterruptedException, ExecutionException, TimeoutException {
return launch(requireCurrentProgram(), commandLine, monitor);
}
/**
* Launch the current program with the default/saved arguments
*
* @see #launch(Program, TaskMonitor)
*/
default LaunchResult launch(TaskMonitor monitor)
throws InterruptedException, ExecutionException, TimeoutException {
return launch(requireCurrentProgram(), monitor);
}
}