mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-1978: Port tests to TraceRmi and delete more stuff.
This commit is contained in:
parent
dadfc465df
commit
5813548a84
110 changed files with 1762 additions and 17529 deletions
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue