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

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

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -52,9 +52,9 @@ public interface DebuggerTraceManagerService {
*/
USER,
/**
* A trace was activated because a recording was started, usually when a target is launched
* A trace was activated because a target was published or withdrawn
*/
START_RECORDING,
TARGET_UPDATED,
/**
* The change was driven by the model activation, possibly indirectly by the user
*/

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -126,7 +126,7 @@ public enum ControlMode {
return false;
}
Target target = coordinates.getTarget();
return target.isVariableExists(coordinates.getPlatform(),
return target.isVariableExists(platformFor(coordinates, address),
coordinates.getThread(), coordinates.getFrame(), address, length);
}
@ -142,8 +142,8 @@ public enum ControlMode {
return CompletableFuture
.failedFuture(new MemoryAccessException("View is not the present"));
}
return target.writeVariableAsync(coordinates.getPlatform(), coordinates.getThread(),
coordinates.getFrame(), address, data);
return target.writeVariableAsync(platformFor(coordinates, address),
coordinates.getThread(), coordinates.getFrame(), address, data);
}
@Override
@ -228,7 +228,7 @@ public enum ControlMode {
public CompletableFuture<Void> setVariable(PluginTool tool,
DebuggerCoordinates coordinates, Address guestAddress, byte[] data) {
Trace trace = coordinates.getTrace();
TracePlatform platform = coordinates.getPlatform();
TracePlatform platform = platformFor(coordinates, guestAddress);
long snap = coordinates.getViewSnap();
Address hostAddress = platform.mapGuestToHost(guestAddress);
if (hostAddress == null) {
@ -408,6 +408,14 @@ public enum ControlMode {
return coordinates;
}
protected TracePlatform platformFor(DebuggerCoordinates coordinates, Address address) {
if (address.isRegisterAddress()) {
return coordinates.getPlatform();
}
// This seems odd, but the memory UI components are displaying *host* addresses.
return coordinates.getTrace().getPlatformManager().getHostPlatform();
}
/**
* Check if (broadly speaking) the mode supports editing the given coordinates
*

View file

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

View file

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

View file

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

View file

@ -1,226 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.model;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetLauncher;
import ghidra.dbg.target.TargetObject;
import ghidra.debug.api.ValStr;
import ghidra.util.task.TaskMonitor;
/**
* An offer to launch a program with a given mechanism
*
* <p>
* Typically each offer is configured with the program it's going to launch, and knows how to work a
* specific connector and platform to obtain a target executing the program's image. The mechanisms
* may vary wildly from platform to platform.
*/
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerProgramLaunchOffer {
/**
* The result of launching a program
*
* <p>
* The launch may not always be completely successful. Instead of tearing things down, partial
* launches are left in place, in case the user wishes to repair/complete the steps manually. If
* the result includes a recorder, the launch was completed successfully. If not, then the
* caller can choose how to treat the connection and target. If the cause of failure was an
* exception, it is included. If the launch succeeded, but module mapping failed, the result
* will include a recorder and the exception.
*
* @param model the connection
* @param target the launched target
* @param recorder the recorder
* @param exception optional error, if failed
*/
public record LaunchResult(DebuggerObjectModel model, TargetObject target,
TraceRecorder recorder, Throwable exception) {
public static LaunchResult totalFailure(Throwable ex) {
return new LaunchResult(null, null, null, ex);
}
}
/**
* When programmatically customizing launch configuration, describes callback timing relative to
* prompting the user.
*/
public enum RelPrompt {
/**
* The user is not prompted for parameters. This will be the only callback.
*/
NONE,
/**
* The user will be prompted. This callback can pre-populate suggested parameters. Another
* callback will be issued if the user does not cancel.
*/
BEFORE,
/**
* The user has confirmed the parameters. This callback can validate or override the users
* parameters. Overriding the user is discouraged. This is the final callback.
*/
AFTER;
}
public enum PromptMode {
/**
* The user is always prompted for parameters.
*/
ALWAYS,
/**
* The user is never prompted for parameters.
*/
NEVER,
/**
* The user is prompted after an error.
*/
ON_ERROR;
}
/**
* Callbacks for custom configuration when launching a program
*/
public interface LaunchConfigurator {
LaunchConfigurator NOP = new LaunchConfigurator() {};
/**
* Re-configure the factory, if desired
*
* @param factory the factory that will create the connection
*/
default void configureConnector(DebuggerModelFactory factory) {
}
/**
* Re-write the launcher arguments, if desired
*
* @param launcher the launcher that will create the target
* @param arguments the arguments suggested by the offer or saved settings
* @param relPrompt describes the timing of this callback relative to prompting the user
* @return the adjusted arguments
*/
default Map<String, ValStr<?>> configureLauncher(TargetLauncher launcher,
Map<String, ValStr<?>> arguments, RelPrompt relPrompt) {
return arguments;
}
}
/**
* Launch the program using the offered mechanism
*
* @param monitor a monitor for progress and cancellation
* @param prompt if the user should be prompted to confirm launch parameters
* @param configurator the configuration callbacks
* @return a future which completes when the program is launched
*/
CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode prompt,
LaunchConfigurator configurator);
/**
* Launch the program using the offered mechanism
*
* @param monitor a monitor for progress and cancellation
* @param prompt if the user should be prompted to confirm launch parameters
* @return a future which completes when the program is launched
*/
default CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode prompt) {
return launchProgram(monitor, prompt, LaunchConfigurator.NOP);
}
/**
* A name so that this offer can be recognized later
*
* <p>
* The name is saved to configuration files, so that user preferences and priorities can be
* memorized. The opinion will generate each offer fresh each time, so it's important that the
* "same offer" have the same configuration name. Note that the name <em>cannot</em> depend on
* the program name, but can depend on the model factory and program language and/or compiler
* spec. This name cannot contain semicolons ({@ code ;}).
*
* @return the configuration name
*/
String getConfigName();
/**
* Get the icon displayed in the UI for this offer
*
* <p>
* Don't override this except for good reason. If you do override, please return a variant that
* still resembles this icon, e.g., just overlay on this one.
*
* @return the icon
*/
Icon getIcon();
/**
* Get the text display on the parent menu for this offer
*
* <p>
* Unless there's good reason, this should always be "Debug [executablePath]".
*
* @return the title
*/
String getMenuParentTitle();
/**
* Get the text displayed on the menu for this offer
*
* @return the title
*/
String getMenuTitle();
/**
* Get the text displayed if the user will not be prompted
*
* <p>
* Sometimes when "the last options" are being used without prompting, it's a good idea to
* remind the user what those options were.
*
* @return the title
*/
default String getQuickTitle() {
return getMenuTitle();
}
/**
* Get the text displayed on buttons for this offer
*
* @return the title
*/
default String getButtonTitle() {
return getMenuParentTitle() + " " + getQuickTitle();
}
/**
* Get the default priority (position in the menu) of the offer
*
* <p>
* Note that greater priorities will be listed first, with the greatest being the default "quick
* launch" offer.
*
* @return the priority
*/
default int defaultPriority() {
return 50;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -664,7 +664,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
trace = connection.waitForTrace(getTimeoutMillis());
traceManager.openTrace(trace);
traceManager.activate(traceManager.resolveTrace(trace),
ActivationCause.START_RECORDING);
ActivationCause.TARGET_UPDATED);
monitor.increment();
waitForModuleMapping(monitor, connection, trace);

View file

@ -0,0 +1,72 @@
/* ###
* 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.plugin.core.debug.service.tracermi;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Swing;
public abstract class AbstractTraceRmiConnection implements TraceRmiConnection {
protected abstract DebuggerTraceManagerService getTraceManager();
protected abstract DebuggerControlService getControlService();
protected abstract boolean ownsTrace(Trace trace);
protected boolean followsPresent(Trace trace) {
DebuggerControlService controlService = getControlService();
if (controlService == null) {
return true;
}
return controlService.getCurrentMode(trace).followsPresent();
}
protected void doActivate(TraceObject object, Trace trace, TraceSnapshot snapshot) {
DebuggerCoordinates coords = getTraceManager().getCurrent();
if (coords.getTrace() != trace) {
coords = DebuggerCoordinates.NOWHERE;
}
if (snapshot != null && followsPresent(trace)) {
coords = coords.snap(snapshot.getKey());
}
DebuggerCoordinates finalCoords = object == null ? coords : coords.object(object);
Swing.runLater(() -> {
DebuggerTraceManagerService traceManager = getTraceManager();
if (traceManager == null) {
// Can happen during tear down.
return;
}
if (!traceManager.getOpenTraces().contains(trace)) {
traceManager.openTrace(trace);
traceManager.activate(finalCoords, ActivationCause.SYNC_MODEL);
}
else {
Trace currentTrace = traceManager.getCurrentTrace();
if (currentTrace == null || ownsTrace(currentTrace)) {
traceManager.activate(finalCoords, ActivationCause.SYNC_MODEL);
}
}
});
}
}

View file

@ -35,7 +35,6 @@ import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.util.PathPattern;
@ -43,7 +42,6 @@ import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.progress.CloseableTaskMonitor;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.api.tracermi.*;
import ghidra.framework.Application;
import ghidra.framework.model.*;
@ -67,7 +65,7 @@ import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
public class TraceRmiHandler implements TraceRmiConnection {
public class TraceRmiHandler extends AbstractTraceRmiConnection {
public static final String VERSION = "11.2";
protected static class VersionMismatchError extends TraceRmiError {
@ -817,42 +815,25 @@ public class TraceRmiHandler implements TraceRmiConnection {
return makeArgument(ent.getKey(), ent.getValue());
}
protected boolean followsPresent(Trace trace) {
DebuggerControlService controlService = this.controlService;
if (controlService == null) {
return true;
}
return controlService.getCurrentMode(trace).followsPresent();
@Override
protected DebuggerTraceManagerService getTraceManager() {
return this.traceManager;
}
@Override
protected DebuggerControlService getControlService() {
return this.controlService;
}
@Override
protected boolean ownsTrace(Trace trace) {
return openTraces.getByTrace(trace) != null;
}
protected ReplyActivate handleActivate(RequestActivate req) {
OpenTrace open = requireOpenTrace(req.getOid());
TraceObject object = open.getObject(req.getObject(), false);
DebuggerCoordinates coords = traceManager.getCurrent();
if (coords.getTrace() != open.trace) {
coords = DebuggerCoordinates.NOWHERE;
}
if (open.lastSnapshot != null && followsPresent(open.trace)) {
coords = coords.snap(open.lastSnapshot.getKey());
}
DebuggerCoordinates finalCoords = object == null ? coords : coords.object(object);
Swing.runLater(() -> {
DebuggerTraceManagerService traceManager = this.traceManager;
if (traceManager == null) {
// Can happen during tear down.
return;
}
if (!traceManager.getOpenTraces().contains(open.trace)) {
traceManager.openTrace(open.trace);
traceManager.activate(finalCoords, ActivationCause.SYNC_MODEL);
}
else {
Trace currentTrace = traceManager.getCurrentTrace();
if (currentTrace == null || openTraces.getByTrace(currentTrace) != null) {
traceManager.activate(finalCoords, ActivationCause.SYNC_MODEL);
}
}
});
doActivate(object, open.trace, open.lastSnapshot);
return ReplyActivate.getDefaultInstance();
}

View file

@ -334,10 +334,12 @@ public class TraceRmiTarget extends AbstractTarget {
ActionName.STEP_OUT.equals(name) ||
ActionName.STEP_OVER.equals(name) ||
ActionName.STEP_SKIP.equals(name)) {
return () -> whenState(obj, state -> state != null && (state.isStopped() || state.isUnknown()));
return () -> whenState(obj,
state -> state != null && (state.isStopped() || state.isUnknown()));
}
else if (ActionName.INTERRUPT.equals(name)) {
return () -> whenState(obj, state -> state == null || state.isRunning() || state.isUnknown());
return () -> whenState(obj,
state -> state == null || state.isRunning() || state.isUnknown());
}
else if (ActionName.KILL.equals(name)) {
return () -> whenState(obj, state -> state == null || !state.isTerminated());
@ -1211,13 +1213,13 @@ public class TraceRmiTarget extends AbstractTarget {
.thenApply(__ -> null);
}
protected boolean isMemorySpaceValid(AddressSpace space) {
return trace.getBaseAddressFactory().getAddressSpace(space.getSpaceID()) == space;
protected boolean isMemorySpaceValid(TracePlatform platform, AddressSpace space) {
return platform.getAddressFactory().getAddressSpace(space.getSpaceID()) == space;
}
protected boolean isRegisterValid(TracePlatform platform, TraceThread thread, int frame,
Address address, int length) {
if (!isMemorySpaceValid(address.getAddressSpace())) {
if (!isMemorySpaceValid(platform, address.getAddressSpace())) {
return false;
}
Register register =
@ -1239,7 +1241,7 @@ public class TraceRmiTarget extends AbstractTarget {
public boolean isVariableExists(TracePlatform platform, TraceThread thread, int frame,
Address address, int length) {
if (address.isMemoryAddress()) {
return isMemorySpaceValid(address.getAddressSpace());
return isMemorySpaceValid(platform, address.getAddressSpace());
}
if (address.isRegisterAddress()) {
return isRegisterValid(platform, thread, frame, address, length);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -24,9 +24,9 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import db.Transaction;
import ghidra.app.services.DebuggerTargetService;
import ghidra.async.AsyncPairingQueue;
import ghidra.async.AsyncUtils;
import ghidra.async.*;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.target.ActionName;
@ -34,12 +34,14 @@ import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracermi.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.time.TraceSnapshot;
public class TestTraceRmiConnection implements TraceRmiConnection {
public abstract class TestTraceRmiConnection extends AbstractTraceRmiConnection {
protected final TestRemoteMethodRegistry registry = new TestRemoteMethodRegistry();
protected final CompletableFuture<Trace> firstTrace = new CompletableFuture<>();
protected final Map<Trace, Long> snapshots = new HashMap<>();
protected final Map<Trace, TraceSnapshot> snapshots = new HashMap<>();
protected final CompletableFuture<Void> closed = new CompletableFuture<>();
protected final Map<Trace, TraceRmiTarget> targets = new HashMap<>();
@ -88,8 +90,9 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
return result;
}
public Map<String, Object> expect() throws InterruptedException, ExecutionException {
return argQueue.take().get();
public Map<String, Object> expect()
throws InterruptedException, ExecutionException, TimeoutException {
return argQueue.take().get(AsyncTestUtils.TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void result(Object ret) {
@ -98,8 +101,7 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
public CompletableFuture<Map<String, Object>> expect(
Function<Map<String, Object>, Object> impl) {
record ArgsRet(Map<String, Object> args, Object ret) {
}
record ArgsRet(Map<String, Object> args, Object ret) {}
var result = argQueue().take().thenApply(a -> new ArgsRet(a, impl.apply(a)));
result.thenApply(ar -> ar.ret).handle(AsyncUtils.copyTo(retQueue().give()));
return result.thenApply(ar -> ar.args);
@ -149,6 +151,15 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
return target;
}
public void withdrawTarget(PluginTool tool, Trace trace) {
Target target;
synchronized (targets) {
target = targets.remove(trace);
}
DebuggerTargetService targetService = tool.getService(DebuggerTargetService.class);
targetService.withdrawTarget(target);
}
@Override
public Trace waitForTrace(long timeoutMillis) throws TimeoutException {
try {
@ -159,17 +170,21 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
}
}
public void setLastSnapshot(Trace trace, long snap) {
public TraceSnapshot setLastSnapshot(Trace trace, long snap) {
synchronized (snapshots) {
snapshots.put(trace, snap);
try (Transaction tx = trace.openTransaction("Add snapshot")) {
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, true);
snapshots.put(trace, snapshot);
return snapshot;
}
}
}
@Override
public long getLastSnapshot(Trace trace) {
synchronized (snapshots) {
Long snap = snapshots.get(trace);
return snap == null ? 0 : snap;
TraceSnapshot snap = snapshots.get(trace);
return snap == null ? 0 : snap.getKey();
}
}
@ -225,4 +240,14 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
public Collection<Target> getTargets() {
return List.copyOf(targets.values());
}
@Override
protected boolean ownsTrace(Trace trace) {
return targets.containsKey(trace);
}
public void synthActivate(TraceObject object) {
Trace trace = object.getTrace();
doActivate(object, trace, snapshots.get(trace));
}
}

View file

@ -1,60 +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.
*/
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetThread;
import ghidra.debug.flatapi.FlatDebuggerRecorderAPI;;
public class MonitorModelEventsScript extends GhidraScript implements FlatDebuggerRecorderAPI {
static DebuggerModelListener listener = new DebuggerModelListener() {
@Override
public void attributesChanged(TargetObject object, Collection<String> removed,
Map<String, ?> added) {
System.err.println("attributesChanged(%s, removed=%s, added=%s)"
.formatted(object.getJoinedPath("."), removed, added));
}
@Override
public void elementsChanged(TargetObject object, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
System.err.println("elementsChanged(%s, removed=%s, added=%s)"
.formatted(object.getJoinedPath("."), removed, added));
}
@Override
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
String description, List<Object> parameters) {
System.err.println(
"event(%s, thread=%s, type=%s, desc=%s)".formatted(object.getJoinedPath("."),
eventThread == null ? "<null>" : eventThread.getJoinedPath("."), type,
description));
}
@Override
public void invalidateCacheRequested(TargetObject object) {
System.err.println("invalidateCache(%s)".formatted(object.getJoinedPath(".")));
}
};
@Override
protected void run() throws Exception {
getModelService().getCurrentModel().addModelListener(listener);
}
}

View file

@ -4,81 +4,81 @@
* 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.
*/
import java.util.Set;
import java.util.List;
import db.Transaction;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.*;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.util.PathMatcher;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.flatapi.FlatDebuggerRmiAPI;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
public class RefreshRegistersScript extends GhidraScript implements FlatDebuggerRmiAPI {
public static final List<String> REG_NAMES = List.of("RAX", "RBX");
public class RefreshRegistersScript extends GhidraScript {
@Override
protected void run() throws Exception {
// There is no need to fish this from the ObjectUpdateService, you can get it directly
DebuggerModelService modelService = state.getTool().getService(DebuggerModelService.class);
// The current model is retrieved with one method, no need to stream or filter
DebuggerObjectModel model = modelService.getCurrentModel();
/**
* Navigating a model generically requires some introspection. Use the schema. We used to
* decouple the descriptions (RegisterContainer) from the values (RegisterBank), but we
* always realize the two interfaces on the same no it seems. Still, we need to work with
* values, so we search for all the Banks.
*/
PathMatcher allBanksMatcher =
model.getRootSchema().searchFor(TargetRegisterBank.class, true);
for (TargetObject objBank : allBanksMatcher.fetchSuccessors(model.fetchModelRoot().get())
.get()
.values()) {
// Because of a bug in our path search, this type check is still necessary :(
if (!(objBank instanceof TargetRegisterBank)) {
continue;
}
TargetRegisterBank bank = (TargetRegisterBank) objBank;
// This is equivalent to hitting the "Flush Caches" button in the Target Provider
bank.invalidateCaches().get();
// If you know the names of the registers to read
bank.readRegistersNamed("rax", "rbx").get();
// Values are coupled to elements, so we can also just refresh them to re-read all
bank.fetchElements(RefreshBehavior.REFRESH_ALWAYS).get();
DebuggerCoordinates current = getCurrentDebuggerCoordinates();
if (!current.isAliveAndPresent()) {
printerr("Target is not alive, or you're looking at the trace history.");
return;
}
/**
* Alternatively, to refresh just the bank for the current thread or frame, we need to
* determine the active frame. We'll do that by asking the TraceManagerService. Then,
* because that's in "trace land," we'll use the TraceRecorder to map that into "target
* land." Generally, you'd then need to navigate the schema as before, but relative to the
* target object. Because fetching registers is so common, this is already built in to the
* recorder.
*/
DebuggerTraceManagerService traceManager =
state.getTool().getService(DebuggerTraceManagerService.class);
// There are also getCurreentTrace(), etc., if you want just the one thing
DebuggerCoordinates current = traceManager.getCurrent();
try (Transaction tx = current.getTrace().openTransaction("Refresh Registers")) {
}
}
// Now, we need to get the relevant recorder
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
// There's a chance of an NPE here if there is no "current frame"
Set<TargetRegisterBank> banks =
recorder.getTargetRegisterBanks(current.getThread(), current.getFrame());
for (TargetRegisterBank bank : banks) {
// Now do the same to the bank as before
bank.invalidateCaches().get();
bank.fetchElements(RefreshBehavior.REFRESH_ALWAYS).get();
protected void setUnknown(TracePlatform platform, TraceThread thread, int frame, long snap,
List<Register> regs) {
TraceMemorySpace regSpace = thread
.getTrace()
.getMemoryManager()
.getMemoryRegisterSpace(thread, frame, false);
if (regSpace == null) {
return;
}
for (Register reg : regs) {
regSpace.setState(platform, snap, reg, TraceMemoryState.UNKNOWN);
}
}
protected void readCurrentFrame(DebuggerCoordinates current, boolean forceRefresh) {
long snap = current.getSnap();
TracePlatform platform = current.getPlatform();
List<Register> regs = REG_NAMES.stream().map(platform.getLanguage()::getRegister).toList();
TraceThread thread = current.getThread();
int frame = current.getFrame();
if (forceRefresh) {
setUnknown(platform, thread, frame, snap, regs);
}
readRegisters(platform, thread, frame, snap, regs);
}
protected void readAllThreadsTopFrame(DebuggerCoordinates current, boolean forceRefresh) {
Trace trace = current.getTrace();
long snap = current.getSnap();
TracePlatform platform = current.getPlatform();
List<Register> regs = REG_NAMES.stream().map(platform.getLanguage()::getRegister).toList();
for (TraceThread thread : trace.getThreadManager().getAllThreads()) {
if (forceRefresh) {
setUnknown(platform, thread, 0, snap, regs);
}
readRegisters(platform, thread, 0, snap, regs);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

@ -49,7 +49,6 @@ import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.async.AsyncUtils;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.program.database.ProgramContentHandler;
@ -415,24 +414,6 @@ public interface DebuggerResources {
Icon ICON = ICON_DEBUGGER;
String GROUP = GROUP_GENERAL;
String HELP_ANCHOR = "debug_program";
static <T> MultiStateActionBuilder<T> buttonBuilder(Plugin owner, Plugin helpOwner) {
return new MultiStateActionBuilder<T>(NAME, owner.getName())
.toolBarIcon(ICON)
.toolBarGroup(GROUP)
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
}
static ActionBuilder menuBuilder(DebuggerProgramLaunchOffer offer, Plugin owner,
Plugin helpOwner) {
return new ActionBuilder(offer.getConfigName(), owner.getName())
.description(offer.getButtonTitle())
.menuPath(DebuggerPluginPackage.NAME, offer.getMenuParentTitle(),
offer.getMenuTitle())
.menuIcon(offer.getIcon())
.menuGroup(GROUP)
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
}
}
abstract class AbstractQuickLaunchAction extends DockingAction {

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.action;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import docking.ActionContext;
@ -38,6 +39,7 @@ import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.*;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.TraceDomainObjectListener;
@ -164,6 +166,9 @@ public abstract class DebuggerReadsMemoryTrait {
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected AddressSetView visible;
protected final Object lock = new Object();
protected volatile CompletableFuture<?> lastRead;
public DebuggerReadsMemoryTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) {
this.tool = tool;
this.plugin = plugin;
@ -205,6 +210,9 @@ public abstract class DebuggerReadsMemoryTrait {
removeOldTraceListener();
}
current = coordinates;
if (actionRefreshSelected != null) {
actionRefreshSelected.updateEnabled(null);
}
if (doTraceListener) {
addNewTraceListener();
}
@ -228,14 +236,20 @@ public abstract class DebuggerReadsMemoryTrait {
return;
}
AddressSet visible = new AddressSet(this.visible);
autoSpec.getEffective(current).readMemory(tool, current, visible).thenAccept(b -> {
if (b) {
memoryWasRead(visible);
}
}).exceptionally(ex -> {
Msg.error(this, "Could not auto-read memory: " + ex);
return null;
});
synchronized (lock) {
lastRead = autoSpec.getEffective(current)
.readMemory(tool, current, visible)
.thenAccept(b -> {
if (b) {
memoryWasRead(visible);
}
})
.exceptionally(ex -> {
Msg.error(this, "Could not auto-read memory: " + ex);
return null;
});
}
}
public MultiStateDockingAction<AutoReadMemorySpec> installAutoReadAction() {
@ -296,10 +310,19 @@ public abstract class DebuggerReadsMemoryTrait {
}
/* testing */
@Internal
public AddressSetView getVisible() {
return visible;
}
/* testing */
@Internal
public CompletableFuture<?> getLastRead() {
synchronized (lock) {
return lastRead;
}
}
protected abstract AddressSetView getSelection();
protected abstract void repaintPanel();

View file

@ -24,6 +24,7 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -1201,6 +1202,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
return readsMemTrait.getAutoSpec();
}
/* testing */
CompletableFuture<?> getLastAutoRead() {
return readsMemTrait.getLastRead();
}
public void doAutoSyncCursorIntoStatic(ProgramLocation location) {
syncTrait.doAutoSyncCursorIntoStatic(location);
}

View file

@ -22,6 +22,7 @@ import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import javax.swing.*;
@ -675,4 +676,9 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
}
return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait);
}
/* testing */
CompletableFuture<?> getLastAutoRead() {
return readsMemTrait.getLastRead();
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -251,21 +251,18 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
}
private void objectRestored(DomainObjectChangeRecord rec) {
changed.add(current.getView().getMemory());
changeDebouncer.contact(null);
addChanged(current.getView().getMemory());
}
private void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range) {
if (space.getThread() == current.getThread() || space.getThread() == null) {
changed.add(range.getRange());
changeDebouncer.contact(null);
addChanged(range.getRange());
}
}
private void stateChanged(TraceAddressSpace space, TraceAddressSnapRange range) {
if (space.getThread() == current.getThread() || space.getThread() == null) {
changed.add(range.getRange());
changeDebouncer.contact(null);
addChanged(range.getRange());
}
}
}
@ -393,6 +390,28 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
changeDebouncer.addListener(__ -> doCheckDepsAndReevaluate());
}
private void addChanged(AddressSetView toAdd) {
synchronized (changed) {
changed.add(toAdd);
changeDebouncer.contact(null);
}
}
private void addChanged(AddressRange toAdd) {
synchronized (changed) {
changed.add(toAdd);
changeDebouncer.contact(null);
}
}
private AddressSetView clearChanged(boolean get) {
synchronized (changed) {
AddressSetView result = get ? new AddressSet(changed) : null;
changed.clear();
return result;
}
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (myActionContext != null) {
@ -914,6 +933,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
}
public synchronized void doCheckDepsAndReevaluate() {
AddressSetView changed = clearChanged(true);
if (asyncWatchExecutor == null) {
return;
}
@ -930,10 +950,10 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
row.reevaluate();
}
}
changed.clear();
}
public void reevaluate() {
clearChanged(false);
if (asyncWatchExecutor == null) {
return;
}
@ -941,7 +961,6 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
for (DefaultWatchRow row : watchTableModel.getModelData()) {
row.reevaluate();
}
changed.clear();
}
public void writeConfigState(SaveState saveState) {
@ -983,7 +1002,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
public void waitEvaluate(int timeoutMs) {
try {
CompletableFuture.runAsync(() -> {
changeDebouncer.stable().thenRunAsync(() -> {
}, workQueue).get(timeoutMs, TimeUnit.MILLISECONDS);
}
catch (ExecutionException | InterruptedException | TimeoutException e) {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -34,6 +34,7 @@ import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
@ -54,22 +55,24 @@ public class DefaultWatchRow implements WatchRow {
private final DebuggerWatchesProvider provider;
private final Object lock = new Object();
private String expression;
private String typePath;
private DataType dataType;
private SettingsImpl settings = new SettingsImpl();
private SavedSettings savedSettings = new SavedSettings(settings);
private PcodeExpression compiled;
private TraceMemoryState state;
private Address address;
private Symbol symbol;
private AddressSetView reads;
private byte[] value;
private byte[] prevValue; // Value at previous coordinates
private String valueString;
private Object valueObj;
private Throwable error = null;
private volatile PcodeExpression compiled;
private volatile TraceMemoryState state;
private volatile Address address;
private volatile Symbol symbol;
private volatile AddressSetView reads;
private volatile byte[] value;
private volatile byte[] prevValue; // Value at previous coordinates
private volatile String valueString;
private volatile Object valueObj;
private volatile Throwable error = null;
public DefaultWatchRow(DebuggerWatchesProvider provider, String expression) {
this.provider = provider;
@ -77,13 +80,15 @@ public class DefaultWatchRow implements WatchRow {
}
protected void blank() {
state = null;
address = null;
symbol = null;
reads = null;
value = null;
valueString = null;
valueObj = null;
synchronized (lock) {
state = null;
address = null;
symbol = null;
reads = null;
value = null;
valueString = null;
valueObj = null;
}
}
protected void recompile() {
@ -106,38 +111,52 @@ public class DefaultWatchRow implements WatchRow {
}
protected void reevaluate() {
blank();
PcodeExecutor<WatchValue> executor = provider.asyncWatchExecutor;
PcodeExecutor<byte[]> prevExec = provider.prevValueExecutor;
if (executor == null) {
provider.contextChanged();
return;
}
CompletableFuture.runAsync(() -> {
recompile();
if (compiled == null) {
PcodeExecutor<WatchValue> executor;
PcodeExecutor<byte[]> prevExec;
final String expression;
synchronized (lock) {
blank();
executor = provider.asyncWatchExecutor;
prevExec = provider.prevValueExecutor;
if (executor == null) {
provider.contextChanged();
return;
}
expression = this.expression;
}
CompletableFuture.runAsync(() -> {
synchronized (lock) {
recompile();
if (compiled == null) {
return;
}
}
// Do not accidentally hang the Swing thread on evaluation
WatchValue fullValue = compiled.evaluate(executor);
prevValue = prevExec == null ? null : compiled.evaluate(prevExec);
byte[] prevValue = prevExec == null ? null : compiled.evaluate(prevExec);
synchronized (lock) {
if (executor != provider.asyncWatchExecutor) {
return;
}
if (!Objects.equals(expression, this.expression)) {
return;
}
TracePlatform platform = provider.current.getPlatform();
this.prevValue = prevValue;
value = fullValue.bytes().bytes();
error = null;
state = fullValue.state();
// TODO: Optional column for guest address?
address = platform.mapGuestToHost(fullValue.address());
symbol = computeSymbol();
// reads piece uses trace access to translate to host/overlay already
reads = fullValue.reads();
TracePlatform platform = provider.current.getPlatform();
value = fullValue.bytes().bytes();
error = null;
state = fullValue.state();
// TODO: Optional column for guest address?
address = platform.mapGuestToHost(fullValue.address());
symbol = computeSymbol();
// reads piece uses trace access to translate to host/overlay already
reads = fullValue.reads();
valueObj = parseAsDataTypeObj();
valueString = parseAsDataTypeStr();
valueObj = parseAsDataTypeObj();
valueString = parseAsDataTypeStr();
}
}, provider.workQueue).exceptionally(e -> {
error = e;
provider.contextChanged();
return null;
}).thenRunAsync(() -> {
provider.watchTableModel.fireTableDataChanged();
@ -172,13 +191,15 @@ public class DefaultWatchRow implements WatchRow {
@Override
public void setExpression(String expression) {
if (!Objects.equals(this.expression, expression)) {
prevValue = null;
// NB. Allow fall-through so user can re-evaluate via nop edit.
synchronized (lock) {
if (!Objects.equals(this.expression, expression)) {
prevValue = null;
// NB. Allow fall-through so user can re-evaluate via nop edit.
}
this.expression = expression;
this.compiled = null;
reevaluate();
}
this.expression = expression;
this.compiled = null;
reevaluate();
}
@Override
@ -219,35 +240,39 @@ public class DefaultWatchRow implements WatchRow {
@Override
public void setDataType(DataType dataType) {
if (dataType instanceof Pointer ptrType && address != null &&
address.isRegisterAddress()) {
/**
* NOTE: This will not catch it if the expression cannot be evaluated. When it can later
* be evaluated, no check is performed.
*
* TODO: This should be for the current platform. These don't depend on the trace's code
* storage, so it should be easier to implement. Still, I'll wait to tackle that all at
* once.
*/
AddressSpace space =
provider.current.getTrace().getBaseAddressFactory().getDefaultAddressSpace();
DataTypeManager dtm = ptrType.getDataTypeManager();
dataType =
new PointerTypedef(null, ptrType.getDataType(), ptrType.getLength(), dtm, space);
if (dtm != null) {
try (Transaction tid = dtm.openTransaction("Resolve data type")) {
dataType = dtm.resolve(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
synchronized (lock) {
if (dataType instanceof Pointer ptrType && address != null &&
address.isRegisterAddress()) {
/**
* NOTE: This will not catch it if the expression cannot be evaluated. When it can
* later be evaluated, no check is performed.
*
* TODO: This should be for the current platform. These don't depend on the trace's
* code storage, so it should be easier to implement. Still, I'll wait to tackle
* that all at once.
*/
AddressSpace space =
provider.current.getTrace().getBaseAddressFactory().getDefaultAddressSpace();
DataTypeManager dtm = ptrType.getDataTypeManager();
dataType =
new PointerTypedef(null, ptrType.getDataType(), ptrType.getLength(), dtm,
space);
if (dtm != null) {
try (Transaction tid = dtm.openTransaction("Resolve data type")) {
dataType = dtm.resolve(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
}
}
this.typePath = dataType == null ? null : dataType.getPathName();
this.dataType = dataType;
settings.setDefaultSettings(dataType == null ? null : dataType.getDefaultSettings());
valueString = parseAsDataTypeStr();
valueObj = parseAsDataTypeObj();
provider.contextChanged();
if (dataType != null) {
savedSettings.read(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
this.typePath = dataType == null ? null : dataType.getPathName();
this.dataType = dataType;
settings.setDefaultSettings(dataType == null ? null : dataType.getDefaultSettings());
valueString = parseAsDataTypeStr();
valueObj = parseAsDataTypeObj();
provider.contextChanged();
if (dataType != null) {
savedSettings.read(dataType.getSettingsDefinitions(),
dataType.getDefaultSettings());
}
}
}
@ -272,52 +297,63 @@ public class DefaultWatchRow implements WatchRow {
@Override
public void settingsChanged() {
if (dataType != null) {
savedSettings.write(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
synchronized (lock) {
if (dataType != null) {
savedSettings.write(dataType.getSettingsDefinitions(),
dataType.getDefaultSettings());
}
valueString = parseAsDataTypeStr();
}
valueString = parseAsDataTypeStr();
provider.watchTableModel.fireTableDataChanged();
}
@Override
public Address getAddress() {
return address;
synchronized (lock) {
return address;
}
}
@Override
public AddressRange getRange() {
if (address == null || value == null) {
return null;
}
if (address.isConstantAddress()) {
return new AddressRangeImpl(address, address);
}
try {
return new AddressRangeImpl(address, value.length);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
synchronized (lock) {
if (address == null || value == null) {
return null;
}
if (address.isConstantAddress()) {
return new AddressRangeImpl(address, address);
}
try {
return new AddressRangeImpl(address, value.length);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
}
@Override
public String getRawValueString() {
if (value == null || provider.language == null) {
return "??";
synchronized (lock) {
byte[] value = this.value;
Language language = provider.language;
if (value == null || language == null) {
return "??";
}
if (address == null || !address.getAddressSpace().isMemorySpace()) {
BigInteger asBigInt = Utils.bytesToBigInteger(value, value.length,
provider.language.isBigEndian(), false);
return "0x" + asBigInt.toString(16);
}
if (value.length > TRUNCATE_BYTES_LENGTH) {
// TODO: I'd like this not to affect the actual value, just the display
// esp., since this will be the "value" when starting to edit.
return "{ " +
NumericUtilities.convertBytesToString(value, 0, TRUNCATE_BYTES_LENGTH, " ") +
" ... }";
}
return "{ " + NumericUtilities.convertBytesToString(value, " ") + " }";
}
if (address == null || !address.getAddressSpace().isMemorySpace()) {
BigInteger asBigInt = Utils.bytesToBigInteger(value, value.length,
provider.language.isBigEndian(), false);
return "0x" + asBigInt.toString(16);
}
if (value.length > TRUNCATE_BYTES_LENGTH) {
// TODO: I'd like this not to affect the actual value, just the display
// esp., since this will be the "value" when starting to edit.
return "{ " +
NumericUtilities.convertBytesToString(value, 0, TRUNCATE_BYTES_LENGTH, " ") +
" ... }";
}
return "{ " + NumericUtilities.convertBytesToString(value, " ") + " }";
}
/**
@ -327,46 +363,61 @@ public class DefaultWatchRow implements WatchRow {
*/
@Override
public AddressSetView getReads() {
return reads;
synchronized (lock) {
return reads;
}
}
public TraceMemoryState getState() {
return state;
synchronized (lock) {
return state;
}
}
@Override
public byte[] getValue() {
return value;
synchronized (lock) {
return value;
}
}
@Override
public String getValueString() {
return valueString;
synchronized (lock) {
return valueString;
}
}
@Override
public Object getValueObject() {
return valueObj;
synchronized (lock) {
return valueObj;
}
}
@Override
public boolean isRawValueEditable() {
if (!provider.isEditsEnabled()) {
return false;
synchronized (lock) {
if (!provider.isEditsEnabled()) {
return false;
}
if (address == null) {
return false;
}
DebuggerControlService controlService = provider.controlService;
if (controlService == null) {
return false;
}
StateEditor editor = controlService.createStateEditor(provider.current);
return editor.isVariableEditable(address, getValueLength());
}
if (address == null) {
return false;
}
DebuggerControlService controlService = provider.controlService;
if (controlService == null) {
return false;
}
StateEditor editor = controlService.createStateEditor(provider.current);
return editor.isVariableEditable(address, getValueLength());
}
@Override
public void setRawValueString(String valueString) {
if (!isRawValueEditable()) {
throw new IllegalStateException("Watch is not editable");
}
valueString = valueString.trim();
if (valueString.startsWith("{")) {
if (!valueString.endsWith("}")) {
@ -398,61 +449,70 @@ public class DefaultWatchRow implements WatchRow {
}
public void setRawValueBytes(byte[] bytes) {
if (address == null) {
throw new IllegalStateException("Cannot write to watch variable without an address");
synchronized (lock) {
if (address == null) {
throw new IllegalStateException(
"Cannot write to watch variable without an address");
}
if (bytes.length > value.length) {
throw new IllegalArgumentException("Byte arrays cannot exceed length of variable");
}
if (bytes.length < value.length) {
byte[] fillOld = Arrays.copyOf(value, value.length);
System.arraycopy(bytes, 0, fillOld, 0, bytes.length);
bytes = fillOld;
}
DebuggerControlService controlService = provider.controlService;
if (controlService == null) {
throw new AssertionError("No control service");
}
StateEditor editor = controlService.createStateEditor(provider.current);
editor.setVariable(address, bytes).exceptionally(ex -> {
Msg.showError(this, null, "Write Failed",
"Could not modify watch value (on target)", ex);
return null;
});
}
if (bytes.length > value.length) {
throw new IllegalArgumentException("Byte arrays cannot exceed length of variable");
}
if (bytes.length < value.length) {
byte[] fillOld = Arrays.copyOf(value, value.length);
System.arraycopy(bytes, 0, fillOld, 0, bytes.length);
bytes = fillOld;
}
DebuggerControlService controlService = provider.controlService;
if (controlService == null) {
throw new AssertionError("No control service");
}
StateEditor editor = controlService.createStateEditor(provider.current);
editor.setVariable(address, bytes).exceptionally(ex -> {
Msg.showError(this, null, "Write Failed",
"Could not modify watch value (on target)", ex);
return null;
});
}
@Override
public void setValueString(String valueString) {
if (dataType == null || value == null) {
// isValueEditable should have been false
provider.getTool().setStatusInfo("Watch no value or no data type", true);
return;
}
try {
byte[] encoded = dataType.encodeRepresentation(valueString,
new ByteMemBufferImpl(address, value, provider.language.isBigEndian()),
SettingsImpl.NO_SETTINGS, value.length);
setRawValueBytes(encoded);
}
catch (DataTypeEncodeException e) {
provider.getTool().setStatusInfo(e.getMessage(), true);
synchronized (lock) {
if (dataType == null || value == null) {
// isValueEditable should have been false
provider.getTool().setStatusInfo("Watch no value or no data type", true);
return;
}
try {
byte[] encoded = dataType.encodeRepresentation(valueString,
new ByteMemBufferImpl(address, value, provider.language.isBigEndian()),
SettingsImpl.NO_SETTINGS, value.length);
setRawValueBytes(encoded);
}
catch (DataTypeEncodeException e) {
provider.getTool().setStatusInfo(e.getMessage(), true);
}
}
}
@Override
public boolean isValueEditable() {
if (!isRawValueEditable()) {
return false;
synchronized (lock) {
if (!isRawValueEditable()) {
return false;
}
if (dataType == null) {
return false;
}
return dataType.isEncodable();
}
if (dataType == null) {
return false;
}
return dataType.isEncodable();
}
@Override
public int getValueLength() {
return value == null ? 0 : value.length;
synchronized (lock) {
return value == null ? 0 : value.length;
}
}
protected Symbol computeSymbol() {
@ -496,7 +556,9 @@ public class DefaultWatchRow implements WatchRow {
@Override
public Symbol getSymbol() {
return symbol;
synchronized (lock) {
return symbol;
}
}
@Override
@ -518,15 +580,19 @@ public class DefaultWatchRow implements WatchRow {
@Override
public boolean isKnown() {
return state == TraceMemoryState.KNOWN;
synchronized (lock) {
return state == TraceMemoryState.KNOWN;
}
}
@Override
public boolean isChanged() {
if (prevValue == null) {
return false;
synchronized (lock) {
if (prevValue == null) {
return false;
}
return !Arrays.equals(value, prevValue);
}
return !Arrays.equals(value, prevValue);
}
protected void writeConfigState(SaveState saveState) {

View file

@ -19,7 +19,6 @@ import java.util.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.model.DebuggerMappingOpinion;
import ghidra.program.model.lang.Endian;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;

View file

@ -1,84 +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.plugin.core.debug.mapping;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.util.MathUtilities;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
protected final AddressFactory traceAddressFactory;
protected final AddressFactory targetAddressFactory;
public DefaultDebuggerMemoryMapper(Language traceLanguage, DebuggerObjectModel targetModel) {
this.traceAddressFactory = traceLanguage.getAddressFactory();
this.targetAddressFactory = targetModel.getAddressFactory();
}
protected static boolean isInFactory(AddressSpace space, AddressFactory factory) {
return factory.getAddressSpace(space.getName()) == space;
}
protected static boolean isInFactory(Address addr, AddressFactory factory) {
return isInFactory(addr.getAddressSpace(), factory);
}
protected static Address toSameNamedSpace(Address addr, AddressFactory factory) {
if (addr.isRegisterAddress()) {
throw new IllegalArgumentException("Memory mapper cannot handle register addresses");
}
return factory.getAddressSpace(addr.getAddressSpace().getName())
.getAddress(addr.getOffset());
}
@Override
public Address traceToTarget(Address traceAddr) {
assert isInFactory(traceAddr, traceAddressFactory);
return toSameNamedSpace(traceAddr, targetAddressFactory);
}
@Override
public Address targetToTrace(Address targetAddr) {
if (targetAddr == SpecialAddress.NO_ADDRESS) {
return SpecialAddress.NO_ADDRESS;
}
assert isInFactory(targetAddr, targetAddressFactory);
return toSameNamedSpace(targetAddr, traceAddressFactory);
}
@Override
public AddressRange targetToTraceTruncated(AddressRange targetRange) {
AddressSpace traceSpace =
traceAddressFactory.getAddressSpace(targetRange.getAddressSpace().getName());
long maxTraceSpaceOffset = traceSpace.getMaxAddress().getOffset();
Address targetMin = targetRange.getMinAddress();
long minTargetOffset = targetMin.getOffset();
if (Long.compareUnsigned(maxTraceSpaceOffset, minTargetOffset) < 0) {
return null;
}
Address traceMin = traceSpace.getAddress(minTargetOffset);
Address targetMax = targetRange.getMaxAddress();
long maxTargetOffset = targetMax.getOffset();
Address traceMax =
traceSpace.getAddress(MathUtilities.unsignedMin(maxTraceSpaceOffset, maxTargetOffset));
return new AddressRangeImpl(traceMin, traceMax);
}
}

View file

@ -1,162 +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.plugin.core.debug.mapping;
import java.util.*;
import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.target.TargetRegisterContainer;
import ghidra.debug.api.model.DebuggerRegisterMapper;
import ghidra.program.model.lang.*;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
protected final Language language;
protected final CompilerSpec cspec;
//protected final TargetRegisterContainer targetRegContainer;
protected final boolean caseSensitive;
protected final Map<String, Register> languageRegs = new LinkedHashMap<>();
protected final Map<String, Register> filtLanguageRegs = new LinkedHashMap<>();
protected final Map<String, TargetRegister> targetRegs = new HashMap<>();
public DefaultDebuggerRegisterMapper(CompilerSpec cSpec,
TargetRegisterContainer targetRegContainer, boolean caseSensitive) {
this.language = cSpec.getLanguage();
this.cspec = cSpec;
//this.targetRegContainer = targetRegContainer;
this.caseSensitive = caseSensitive;
collectFilteredLanguageRegs();
}
protected boolean includeTraceRegister(Register lReg) {
return lReg.isBaseRegister();
}
protected synchronized void collectFilteredLanguageRegs() {
for (Register lReg : language.getRegisters()) {
if (!includeTraceRegister(lReg)) {
continue;
}
putLanguageRegister(filtLanguageRegs, lReg);
}
}
protected synchronized void putLanguageRegister(Map<String, Register> map, Register lReg) {
map.put(normalizeName(lReg.getName()), lReg);
for (String alias : lReg.getAliases()) {
map.put(normalizeName(alias), lReg);
}
}
protected synchronized void removeLanguageRegister(Map<String, Register> map, Register lReg) {
map.remove(normalizeName(lReg.getName()));
for (String alias : lReg.getAliases()) {
map.remove(normalizeName(alias));
}
}
protected synchronized Register considerRegister(String index) {
String name = normalizeName(index);
Register lReg = filtLanguageRegs.get(name);
if (lReg == null) {
return null;
}
putLanguageRegister(languageRegs, lReg);
return lReg;
}
protected synchronized Register considerRegister(TargetRegister tReg) {
String name = normalizeName(tReg.getIndex());
Register lReg = filtLanguageRegs.get(name);
if (lReg == null) {
return null;
}
targetRegs.put(name, tReg);
putLanguageRegister(languageRegs, lReg);
return lReg;
}
protected synchronized Register removeRegister(TargetRegister tReg) {
String name = normalizeName(tReg.getIndex());
Register lReg = filtLanguageRegs.get(name);
if (lReg == null) {
return null;
}
targetRegs.remove(name);
removeLanguageRegister(languageRegs, lReg);
return lReg;
}
protected String normalizeName(String name) {
if (caseSensitive) {
return name;
}
return name.toLowerCase();
}
@Override
public synchronized TargetRegister getTargetRegister(String name) {
return targetRegs.get(normalizeName(name));
}
@Override
public synchronized Register getTraceRegister(String name) {
return languageRegs.get(normalizeName(name));
}
@Override
public synchronized TargetRegister traceToTarget(Register lReg) {
TargetRegister tReg = targetRegs.get(normalizeName(lReg.getName()));
if (tReg != null) {
return tReg;
}
for (String alias : lReg.getAliases()) {
tReg = targetRegs.get(normalizeName(alias));
if (tReg != null) {
return tReg;
}
}
return null;
}
@Override
public synchronized Register targetToTrace(TargetRegister tReg) {
return languageRegs.get(normalizeName(tReg.getIndex()));
}
@Override
public synchronized Set<Register> getRegistersOnTarget() {
return Set.copyOf(languageRegs.values());
}
@Override
public synchronized void targetRegisterAdded(TargetRegister register) {
//if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
// return;
//}
considerRegister(register);
}
@Override
public synchronized void targetRegisterRemoved(TargetRegister register) {
//if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
// return;
//}
removeRegister(register);
}
}

View file

@ -1,104 +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.plugin.core.debug.mapping;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.DefaultTraceRecorder;
import ghidra.dbg.target.*;
import ghidra.debug.api.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.trace.model.Trace;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultDebuggerTargetTraceMapper implements DebuggerTargetTraceMapper {
protected final TargetObject target;
protected final Language language;
protected final CompilerSpec cSpec;
protected final Set<String> extraRegNames;
public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID,
CompilerSpecID csID, Collection<String> extraRegNames)
throws LanguageNotFoundException, CompilerSpecNotFoundException {
this.target = target;
LanguageService langServ = DefaultLanguageService.getLanguageService();
this.language = langServ.getLanguage(langID);
this.cSpec = language.getCompilerSpecByID(csID);
this.extraRegNames = Set.copyOf(extraRegNames);
}
/**
* Create a mapper between trace and target memory
*
* <p>
* TODO: Now that every impl just uses the model's address factory, we should probably just have
* this take a model, and create the mapper in the recorder's constructor.
*
* @param memory the target memory
* @return the mapper
*/
protected DebuggerMemoryMapper createMemoryMapper(TargetMemory memory) {
return new DefaultDebuggerMemoryMapper(language, memory.getModel());
}
/**
* Create a mapper between trace and target registers
*
* @param registers the target's register container
* @return the mapper
*/
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
}
// TODO: Make this synchronous, or remove it
public CompletableFuture<DebuggerMemoryMapper> offerMemory(TargetMemory memory) {
DebuggerMemoryMapper mm = createMemoryMapper(memory);
return CompletableFuture.completedFuture(mm);
}
// TODO: Make this synchronous, or remove it
public CompletableFuture<DebuggerRegisterMapper> offerRegisters(
TargetRegisterContainer registers) {
DebuggerRegisterMapper rm = createRegisterMapper(registers);
return CompletableFuture.completedFuture(rm);
}
public Set<String> getExtraRegNames() {
return extraRegNames;
}
@Override
public Language getTraceLanguage() {
return language;
}
@Override
public CompilerSpec getTraceCompilerSpec() {
return cSpec;
}
@Override
public TraceRecorder startRecording(PluginTool tool, Trace trace) {
return new DefaultTraceRecorder(tool, trace, target, this);
}
}

View file

@ -1,100 +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.plugin.core.debug.mapping;
import java.util.HashMap;
import java.util.Map;
import db.Transaction;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.util.exception.DuplicateNameException;
@Deprecated(forRemoval = true, since = "11.3")
public class ObjectBasedDebuggerMemoryMapper implements DebuggerMemoryMapper {
protected final Trace trace;
protected final AddressSpace base;
protected final Map<Integer, AddressSpace> targetToTraceSpaces = new HashMap<>();
protected final Map<Integer, AddressSpace> traceToTargetSpaces = new HashMap<>();
public ObjectBasedDebuggerMemoryMapper(Trace trace) {
this.trace = trace;
this.base = trace.getBaseAddressFactory().getDefaultAddressSpace();
}
@Override
public Address traceToTarget(Address traceAddr) {
AddressSpace traceSpace = traceAddr.getAddressSpace();
int traceIdHash = System.identityHashCode(traceSpace);
AddressSpace targetSpace;
synchronized (traceToTargetSpaces) {
targetSpace = traceToTargetSpaces.get(traceIdHash);
}
/**
* Can only be null if space is the default space or some non-physical space. In that case,
* the target hasn't defined a space with that name, so no mapping.
*/
if (targetSpace == null) {
return null;
}
return targetSpace.getAddress(traceAddr.getOffset());
}
@Override
public Address targetToTrace(Address targetAddr) {
AddressSpace targetSpace = targetAddr.getAddressSpace();
int targetIdHash = System.identityHashCode(targetSpace);
AddressSpace traceSpace;
synchronized (traceToTargetSpaces) {
traceSpace = targetToTraceSpaces.get(targetIdHash);
if (traceSpace == null) {
traceSpace = createSpace(targetSpace.getName());
targetToTraceSpaces.put(targetIdHash, traceSpace);
traceToTargetSpaces.put(System.identityHashCode(traceSpace),
targetSpace);
}
}
return traceSpace.getAddress(targetAddr.getOffset());
}
@Override
public AddressRange targetToTraceTruncated(AddressRange targetRange) {
// the DATA space can always accommodate all 64 bits
return targetToTrace(targetRange);
}
protected AddressSpace createSpace(String name) {
try (Transaction tx = trace.openTransaction("Create space for mapping")) {
AddressFactory factory = trace.getBaseAddressFactory();
AddressSpace space = factory.getAddressSpace(name);
if (space == null) {
return trace.getMemoryManager().createOverlayAddressSpace(name, base);
}
// Let the default space suffice for its own name
// NB. if overlay already exists, we've already issued a warning
if (space == base || space.isOverlaySpace()) {
return space;
}
// Otherwise, do not allow non-physical spaces to be used by accident.
return trace.getMemoryManager().createOverlayAddressSpace('_' + name, base);
}
catch (DuplicateNameException e) {
throw new AssertionError(e);
}
}
}

View file

@ -1,55 +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.plugin.core.debug.mapping;
import java.util.Collection;
import ghidra.app.plugin.core.debug.service.model.record.ObjectBasedTraceRecorder;
import ghidra.dbg.target.*;
import ghidra.debug.api.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
@Deprecated(forRemoval = true, since = "11.3")
public class ObjectBasedDebuggerTargetTraceMapper extends DefaultDebuggerTargetTraceMapper {
protected ObjectBasedDebuggerMemoryMapper memoryMapper;
public ObjectBasedDebuggerTargetTraceMapper(TargetObject target, LanguageID langID,
CompilerSpecID csID, Collection<String> extraRegNames)
throws LanguageNotFoundException, CompilerSpecNotFoundException {
super(target, langID, csID, extraRegNames);
}
@Override
protected DebuggerMemoryMapper createMemoryMapper(TargetMemory memory) {
// TODO: Validate regions to not overlap?
// Could probably do that in unit testing of model instead
return memoryMapper;
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
throw new UnsupportedOperationException();
}
@Override
public TraceRecorder startRecording(PluginTool tool, Trace trace) {
this.memoryMapper = new ObjectBasedDebuggerMemoryMapper(trace);
return new ObjectBasedTraceRecorder(tool, trace, target, this);
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -160,7 +160,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
protected class TrackRecordersListener implements TargetPublicationListener {
protected class TrackTargetsListener implements TargetPublicationListener {
@Override
public void targetPublished(Target target) {
processChange(c -> evtTraceTargetPublished(c, target), "targetPublished");
@ -804,7 +804,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private final ListenerSet<LogicalBreakpointsChangeListener> changeListeners =
new ListenerSet<>(LogicalBreakpointsChangeListener.class, true);
private final TrackRecordersListener targetsListener = new TrackRecordersListener();
private final TrackTargetsListener targetsListener = new TrackTargetsListener();
private final TrackMappingsListener mappingListener = new TrackMappingsListener();
private final TrackModesListener modeListener = new TrackModesListener();

View file

@ -1,498 +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.plugin.core.debug.service.model;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.beans.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.text.View;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import docking.ReusableDialogComponentProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractConnectAction;
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
import ghidra.app.services.DebuggerModelService;
import ghidra.async.AsyncUtils;
import ghidra.async.SwingExecutorService;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.Property;
import ghidra.framework.options.SaveState;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import ghidra.util.datastruct.CollectionChangeListener;
@Deprecated(forRemoval = true, since = "11.3")
public class DebuggerConnectDialog extends ReusableDialogComponentProvider
implements PropertyChangeListener {
private static final String KEY_CURRENT_FACTORY_CLASSNAME = "currentFactoryCls";
private static final String KEY_SUCCESS_FACTORY_CLASSNAME = "successFactoryCls";
private static final String HTML_BOLD_DESCRIPTION = "<html><b>Description:</b> ";
protected class FactoriesChangedListener
implements CollectionChangeListener<DebuggerModelFactory> {
@Override
public void elementAdded(DebuggerModelFactory element) {
addFactory(element);
}
@Override
public void elementModified(DebuggerModelFactory element) {
// Don't care
}
@Override
public void elementRemoved(DebuggerModelFactory element) {
removeFactory(element);
}
}
protected record FactoryEntry(DebuggerModelFactory factory) {
@Override
public String toString() {
return factory.getBrief();
}
}
protected record PrioritizedFactory(FactoryEntry entry, int priority) {
public PrioritizedFactory(FactoryEntry ent, Program program) {
this(ent, ent.factory.getPriority(program));
}
}
protected enum NameComparator implements Comparator<String> {
INSTANCE;
@Override
public int compare(String o1, String o2) {
boolean p1 = o1.startsWith("PROTOTYPE:");
boolean p2 = o2.startsWith("PROTOTYPE:");
if (p1 && !p2) {
return 1;
}
if (!p1 && p2) {
return -1;
}
return o1.toLowerCase().compareTo(o2.toLowerCase());
}
}
private DebuggerModelService modelService;
private DebuggerModelFactory currentFactory;
private DebuggerModelFactory successFactory;
private final Map<DebuggerModelFactory, FactoryEntry> factories = new HashMap<>();
private FactoriesChangedListener listener = new FactoriesChangedListener();
private JComboBox<FactoryEntry> dropdown;
protected final DefaultComboBoxModel<FactoryEntry> dropdownModel = new DefaultComboBoxModel<>();
private final BidiMap<Property<?>, PropertyEditor> propertyEditors =
new DualLinkedHashBidiMap<>();
private final Map<Property<?>, Component> components = new LinkedHashMap<>();
protected JLabel description;
protected JPanel gridPanel;
protected JButton connectButton;
protected CompletableFuture<? extends DebuggerObjectModel> futureConnect;
protected CompletableFuture<DebuggerObjectModel> result;
public DebuggerConnectDialog() {
super(AbstractConnectAction.NAME, true, true, true, false);
populateComponents();
}
protected void clearFactories() {
synchronized (factories) {
factories.clear();
SwingUtilities.invokeLater(() -> {
dropdownModel.removeAllElements();
});
}
}
protected void loadFactories() {
synchronized (factories) {
List<FactoryEntry> toAdd = new ArrayList<>();
Set<DebuggerModelFactory> current = modelService.getModelFactories();
for (DebuggerModelFactory element : current) {
FactoryEntry entry = new FactoryEntry(element);
factories.put(element, entry);
toAdd.add(entry);
}
SwingUtilities.invokeLater(() -> {
toAdd.sort(Comparator.comparing(FactoryEntry::toString, NameComparator.INSTANCE));
for (FactoryEntry entry : toAdd) {
dropdownModel.addElement(entry);
}
});
}
}
protected void addFactory(DebuggerModelFactory element) {
synchronized (factories) {
if (factories.containsKey(element)) {
return;
}
FactoryEntry entry = new FactoryEntry(element);
factories.put(element, entry);
SwingUtilities.invokeLater(() -> {
dropdownModel.addElement(entry);
});
}
}
protected void removeFactory(DebuggerModelFactory element) {
synchronized (factories) {
FactoryEntry entry = factories.remove(element);
if (entry == null) {
return;
}
SwingUtilities.invokeLater(() -> {
dropdownModel.removeElement(entry);
});
}
}
public void setModelService(DebuggerModelService modelService) {
if (this.modelService != null) {
this.modelService.removeFactoriesChangedListener(listener);
clearFactories();
}
this.modelService = modelService;
if (this.modelService != null) {
this.modelService.addFactoriesChangedListener(listener);
loadFactories();
}
}
@Override
public void dispose() {
modelService.removeFactoriesChangedListener(listener);
clearFactories();
super.dispose();
}
protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
Box topBox = Box.createVerticalBox();
dropdown = new JComboBox<>(dropdownModel);
topBox.add(dropdown);
// Avoid Swing's automatic indentation
JPanel inner = new JPanel(new BorderLayout());
description = new JLabel(HTML_BOLD_DESCRIPTION + "</html>");
description.setBorder(new EmptyBorder(10, 0, 10, 0));
description.setPreferredSize(new Dimension(400, 150));
inner.add(description);
topBox.add(inner);
panel.add(topBox, BorderLayout.NORTH);
gridPanel = new JPanel(new GridBagLayout());
JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER));
JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrolling.setPreferredSize(new Dimension(100, 200));
panel.add(scrolling, BorderLayout.CENTER);
centering.add(gridPanel);
addWorkPanel(panel);
connectButton = new JButton();
AbstractConnectAction.styleButton(connectButton);
addButton(connectButton);
addCancelButton();
dropdown.addItemListener(this::itemSelected);
connectButton.addActionListener(this::connect);
}
private void itemSelected(ItemEvent evt) {
if (evt.getStateChange() == ItemEvent.DESELECTED) {
gridPanel.removeAll();
}
else if (evt.getStateChange() == ItemEvent.SELECTED) {
FactoryEntry ent = (FactoryEntry) evt.getItem();
currentFactory = ent.factory;
populateOptions();
/**
* Don't repack here. It can shrink the dialog, which may not be what the user wants.
*/
}
}
private void connect(ActionEvent evt) {
connectButton.setEnabled(false);
for (Map.Entry<Property<?>, PropertyEditor> ent : propertyEditors.entrySet()) {
Property<?> prop = ent.getKey();
@SuppressWarnings("unchecked")
Property<Object> objProp = (Property<Object>) prop;
objProp.setValue(ent.getValue().getValue());
}
setStatusText("Connecting...");
synchronized (this) {
futureConnect = currentFactory.build();
}
futureConnect.thenCompose(m -> m.fetchModelRoot()).thenAcceptAsync(r -> {
DebuggerObjectModel m = r.getModel();
modelService.addModel(m);
setStatusText("");
close();
modelService.activateModel(m);
synchronized (this) {
/**
* NB. Errors will typically be reported, the dialog stays up, and the user is given
* an opportunity to rectify the failure. Thus, errors should not be used to
* complete the result exceptionally. Only catastrophic errors and cancellation
* should affect the result.
*/
result.completeAsync(() -> m);
result = null;
}
}, SwingExecutorService.LATER).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
if (!(e instanceof CancellationException)) {
Msg.showError(this, getComponent(), "Could not connect", e);
}
setStatusText("Could not connect: " + e.getMessage(), MessageType.ERROR);
return null;
}).whenComplete((v, e) -> {
synchronized (this) {
futureConnect = null;
}
successFactory = currentFactory;
connectButton.setEnabled(true);
});
}
@Override
protected void cancelCallback() {
if (futureConnect != null) {
futureConnect.cancel(false);
}
if (result != null) {
result.cancel(false);
}
super.cancelCallback();
}
/**
* For testing and documentation purposes only!
*/
public synchronized void setFactoryByBrief(String brief) {
synchronized (factories) {
for (FactoryEntry fe : factories.values()) {
if (Objects.equals(brief, fe.factory.getBrief())) {
dropdownModel.setSelectedItem(fe);
return;
}
}
throw new AssertionError();
}
}
protected synchronized CompletableFuture<DebuggerObjectModel> reset(
DebuggerModelFactory factory, Program program) {
if (factory != null) {
synchronized (factories) {
dropdownModel.setSelectedItem(factories.get(factory));
}
dropdown.setEnabled(false);
}
else {
selectCompatibleFactory(program);
dropdown.setEnabled(true);
}
if (result != null) {
result.cancel(false);
}
result = new CompletableFuture<>();
setStatusText("");
connectButton.setEnabled(true);
return result;
}
protected void syncOptionsEnabled() {
for (Map.Entry<Property<?>, Component> ent : components.entrySet()) {
ent.getValue().setEnabled(ent.getKey().isEnabled());
}
}
protected void populateOptions() {
description.setText(HTML_BOLD_DESCRIPTION + currentFactory.getHtmlDetails());
propertyEditors.clear();
components.clear();
Map<String, Property<?>> optsMap = currentFactory.getOptions();
gridPanel.removeAll();
GridBagConstraints constraints;
if (optsMap.isEmpty()) {
JLabel label =
new JLabel("<html>There are no configuration options for this connector.");
constraints = new GridBagConstraints();
gridPanel.add(label, constraints);
}
int i = 0;
for (Map.Entry<String, Property<?>> opt : optsMap.entrySet()) {
Property<?> property = opt.getValue();
JLabel label = new JLabel("<html>" + HTMLUtilities.escapeHTML(opt.getKey())) {
@Override
public Dimension getPreferredSize() {
View v = (View) getClientProperty("html");
if (v == null) {
return super.getPreferredSize();
}
v.setSize(200, 0);
float height = v.getPreferredSpan(View.Y_AXIS);
return new Dimension(200, (int) height);
}
};
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 0;
constraints.gridy = i;
constraints.insets = new Insets(i == 0 ? 0 : 5, 0, 0, 5);
gridPanel.add(label, constraints);
Class<?> type = property.getValueClass();
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor == null) {
throw new RuntimeException("Could not find editor for " + property.getValueClass());
}
editor.setValue(property.getValue());
editor.addPropertyChangeListener(this);
Component editorComponent = MiscellaneousUtils.getEditorComponent(editor);
if (editorComponent instanceof JTextField textField) {
textField.setColumns(13);
}
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.WEST;
constraints.gridx = 1;
constraints.gridy = i;
constraints.insets = new Insets(i == 0 ? 0 : 5, 0, 0, 0);
gridPanel.add(editorComponent, constraints);
propertyEditors.put(property, editor);
components.put(property, editorComponent);
i++;
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
PropertyEditor editor = (PropertyEditor) evt.getSource();
Property<?> prop = propertyEditors.getKey(editor);
@SuppressWarnings("unchecked")
Property<Object> objProp = (Property<Object>) prop;
objProp.setValue(editor.getValue());
syncOptionsEnabled();
}
public void writeConfigState(SaveState saveState) {
if (currentFactory != null) {
saveState.putString(KEY_CURRENT_FACTORY_CLASSNAME, currentFactory.getClass().getName());
}
if (successFactory != null) {
saveState.putString(KEY_SUCCESS_FACTORY_CLASSNAME, successFactory.getClass().getName());
}
}
protected FactoryEntry getByName(String className) {
synchronized (factories) {
for (FactoryEntry ent : factories.values()) {
String name = ent.factory.getClass().getName();
if (className.equals(name)) {
return ent;
}
}
return null;
}
}
protected Collection<PrioritizedFactory> getByPriority(Program program) {
synchronized (factories) {
return factories.values()
.stream()
.map(e -> new PrioritizedFactory(e, program))
.sorted(Comparator.comparing(pf -> -pf.priority()))
.toList();
}
}
protected PrioritizedFactory getFirstCompatibleByPriority(Program program) {
for (PrioritizedFactory pf : getByPriority(program)) {
if (pf.priority >= 0) {
return pf;
}
return null;
}
return null;
}
protected void selectCompatibleFactory(Program program) {
if (currentFactory != null && currentFactory.isCompatible(program)) {
return;
}
if (successFactory != null && successFactory.isCompatible(program)) {
currentFactory = successFactory;
synchronized (factories) {
dropdown.setSelectedItem(factories.get(successFactory));
}
return;
}
PrioritizedFactory compat = getFirstCompatibleByPriority(program);
if (compat == null) {
return;
}
currentFactory = compat.entry.factory;
dropdown.setSelectedItem(compat.entry);
}
public void readConfigState(SaveState saveState) {
String currentFactoryName = saveState.getString(KEY_CURRENT_FACTORY_CLASSNAME, null);
FactoryEntry restoreCurrent =
currentFactoryName == null ? null : getByName(currentFactoryName);
currentFactory = restoreCurrent == null ? null : restoreCurrent.factory;
dropdown.setSelectedItem(restoreCurrent);
String successFactoryName = saveState.getString(KEY_SUCCESS_FACTORY_CLASSNAME, null);
FactoryEntry restoreSuccess =
successFactoryName == null ? null : getByName(successFactoryName);
successFactory = restoreSuccess == null ? null : restoreSuccess.factory;
}
}

View file

@ -1,123 +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.plugin.core.debug.service.model;
import java.io.IOException;
import java.util.Collection;
import ghidra.app.plugin.core.debug.event.ModelActivatedPluginEvent;
import ghidra.app.services.DebuggerModelService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
import ghidra.debug.api.model.DebuggerTargetTraceMapper;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.lifecycle.Internal;
/**
* Specifies additional methods on the model service which are available for internal testing
* purposes only.
*/
@Internal
@Deprecated(forRemoval = true, since = "11.2")
public interface DebuggerModelServiceInternal extends DebuggerModelService {
/**
* Force the set of factory instances to be that given
*
* <p>
* This exists for testing the factory change listeners. A test depending on a controlled
* collection of model factories must invoke this method before said test. Conventionally, it is
* the responsibility of each test to ensure its own preconditions are met. For a test depending
* on classpath-discovered factories, see {@link #refreshFactoryInstances()}.
*
* @param factories the forced set of factories
* @see #refreshFactoryInstances()
*/
void setModelFactories(Collection<DebuggerModelFactory> factories);
/**
* Set the model factories back to those found on the classpath
*
* <p>
* This exists for testing the factory change listeners. A test depending on
* classpath-discovered factories must invoke this method. It must consider that a previous test
* may have overridden the factories using {@link #setModelFactories(Collection)}.
* Conventionally, it is the responsibility of each test to ensure its own preconditions are
* met. Tests using {@link #setModelFactories(Collection)} are <em>not</em> required to restore
* the classpath-discovered factories.
*
* @see #setModelFactories(Collection)
*/
void refreshFactoryInstances();
/**
* Start and open a new trace on the given target
*
* <p>
* Starts a new trace, and opens it in the tool
*
* @see #recordTarget(TargetObject)
* @param traceManager the manager for the tool in which to activate the trace
*/
TraceRecorder recordTargetAndActivateTrace(TargetObject target,
DebuggerTargetTraceMapper mapper, DebuggerTraceManagerService traceManager)
throws IOException;
/**
* Set the active model
*
* @param model the new active model
* @return true if changed, false otherwise (including if its already the active model)
*/
boolean doActivateModel(DebuggerObjectModel model);
/**
* Fire a model-activation event
*/
default void fireModelActivatedEvent(DebuggerObjectModel model) {
firePluginEvent(new ModelActivatedPluginEvent(getName(), model));
}
/**
* Fire an object-focus event
*
* @param focused the focused object
*/
void fireFocusEvent(TargetObject focused);
/**
* Fire a recorder-advanced event
*
* @param recorder the recorder that advanced
* @param snap the snap to which it advanced
*/
void fireSnapEvent(TraceRecorder recorder, long snap);
// Impl should inherit from Plugin
String getName();
// Impl should inherit from Plugin
void firePluginEvent(PluginEvent event);
@Override
default void activateModel(DebuggerObjectModel model) {
if (doActivateModel(model)) {
fireModelActivatedEvent(model);
}
}
}

View file

@ -1,693 +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.plugin.core.debug.service.model;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.showError;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.CharBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdom.Element;
import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
import ghidra.app.services.DebuggerModelService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncFence;
import ghidra.dbg.*;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.model.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.lifecycle.Internal;
import ghidra.program.model.listing.Program;
import ghidra.trace.database.DBTrace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.TimedMsg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.datastruct.ListenerSet;
@PluginInfo(
shortDescription = "Debugger models manager service",
description = "Manage debug sessions, connections, and trace recording",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.HIDDEN,
servicesRequired = {},
servicesProvided = {
DebuggerModelService.class
})
@Deprecated(forRemoval = true, since = "11.2")
public class DebuggerModelServicePlugin extends Plugin
implements DebuggerModelServiceInternal, ApplicationLevelOnlyPlugin {
private static final String PREFIX_FACTORY = "Factory_";
// Since used for naming, no ':' allowed.
public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd-HH.mm.ss-z");
protected class ForRemovalAndFocusListener extends AnnotatedDebuggerAttributeListener {
public ForRemovalAndFocusListener() {
super(MethodHandles.lookup());
}
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
if (!object.isRoot()) {
return;
}
DebuggerObjectModel model = object.getModel();
synchronized (models) {
models.remove(model);
}
model.removeModelListener(this);
modelListeners.invoke().elementRemoved(model);
if (currentModel == model) {
activateModel(null);
}
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
// I don't think I care which scope
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
proxy.fireFocusEvent(focused);
}
}
}
protected class ListenerOnRecorders implements TraceRecorderListener {
@Override
public void snapAdvanced(TraceRecorder recorder, long snap) {
TimedMsg.debug(this, "Got snapAdvanced callback");
fireSnapEvent(recorder, snap);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
TimedMsg.debug(this, "Firing SnapEvent on " + proxy);
proxy.fireSnapEvent(recorder, snap);
}
}
@Override
public void recordingStopped(TraceRecorder recorder) {
removeRecorder(recorder);
}
}
protected class ChangeListenerForFactoryInstances implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
refreshFactoryInstances();
}
}
protected final Set<DebuggerModelServiceProxyPlugin> proxies = new HashSet<>();
protected final Set<DebuggerModelFactory> factories = new HashSet<>();
// Keep strong references to my listeners, or they'll get torched
//protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel =
//new LinkedHashMap<>();
protected final Set<DebuggerObjectModel> models = new LinkedHashSet<>();
protected final ForRemovalAndFocusListener forRemovalAndFocusListener =
new ForRemovalAndFocusListener();
protected final Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>();
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true);
protected final ListenerSet<CollectionChangeListener<DebuggerObjectModel>> modelListeners =
new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true);
protected final ListenerSet<CollectionChangeListener<TraceRecorder>> recorderListeners =
new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true);
protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances();
protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
protected final DebuggerSelectMappingOfferDialog offerDialog;
protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog();
DockingAction actionDisconnectAll;
protected DebuggerObjectModel currentModel;
public DebuggerModelServicePlugin(PluginTool tool) {
super(tool);
offerDialog = new DebuggerSelectMappingOfferDialog(tool);
ClassSearcher.addChangeListener(classChangeListener);
refreshFactoryInstances();
connectDialog.setModelService(this);
}
@Override
protected void init() {
super.init();
createActions();
}
@Override
protected void dispose() {
super.dispose();
connectDialog.dispose();
offerDialog.dispose();
}
protected void createActions() {
actionDisconnectAll = DisconnectAllAction.builder(this, this)
.menuPath("Debugger", DisconnectAllAction.NAME)
.menuIcon(null) // our pattern is to no use icons in the main app window
.onAction(this::activatedDisconnectAll)
.buildAndInstall(tool);
}
private void activatedDisconnectAll(ActionContext context) {
closeAllModels();
}
protected void addProxy(DebuggerModelServiceProxyPlugin proxy) {
synchronized (proxies) {
proxies.add(proxy);
}
}
protected void removeProxy(DebuggerModelServiceProxyPlugin proxy) {
synchronized (proxies) {
proxies.remove(proxy);
}
}
@Override
public Set<DebuggerModelFactory> getModelFactories() {
synchronized (factories) {
return Set.copyOf(factories);
}
}
@Override
public Set<DebuggerObjectModel> getModels() {
synchronized (models) {
return Set.copyOf(models);
}
}
@Override
public CompletableFuture<Void> closeAllModels() {
AsyncFence fence = new AsyncFence();
for (DebuggerObjectModel model : getModels()) {
fence.include(model.close().exceptionally(showError(null, "Problem disconnecting")));
}
return fence.ready();
}
@Override
public Collection<TraceRecorder> getTraceRecorders() {
synchronized (recordersByTarget) {
return List.copyOf(recordersByTarget.values());
}
}
@Override
public boolean addModel(DebuggerObjectModel model) {
Objects.requireNonNull(model);
synchronized (models) {
if (!models.add(model)) {
return false;
}
model.addModelListener(forRemovalAndFocusListener);
TargetObject root = model.getModelRoot();
// root == null, probably means we're between model construction
// and root construction, but the model was not closed, so no need
// to invalidate
if (root != null && !root.isValid()) {
forRemovalAndFocusListener.invalidated(root, root,
"Invalidated before or during add to service");
}
}
modelListeners.invoke().elementAdded(model);
return true;
}
@Override
public boolean removeModel(DebuggerObjectModel model) {
model.removeModelListener(forRemovalAndFocusListener);
synchronized (models) {
if (!models.remove(model)) {
return false;
}
}
modelListeners.invoke().elementRemoved(model);
return true;
}
@Override
public void fireFocusEvent(TargetObject focused) {
// Nothing to do
}
@Override
public void fireSnapEvent(TraceRecorder recorder, long snap) {
// Nothing to do
}
@Override
public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper,
ActionSource source) throws IOException {
TraceRecorder recorder;
// Cannot use computeIfAbsent here
// Entry must be present before listeners invoked
synchronized (recordersByTarget) {
recorder = recordersByTarget.get(target);
if (recorder != null) {
Msg.warn(this, "Target is already being recorded: " + target);
return recorder;
}
recorder = doBeginRecording(target, mapper);
recorder.addListener(listenerOnRecorders);
recorder.init().exceptionally(e -> {
if (source == ActionSource.MANUAL) {
Msg.showError(this, null, "Record Trace", "Error initializing recorder", e);
}
else {
Msg.error(this, "Error initializing recorder", e);
}
return null;
});
recordersByTarget.put(target, recorder);
}
recorderListeners.invoke().elementAdded(recorder);
// NOTE: It's possible the recorder stopped recording before we installed the listener
if (!recorder.isRecording()) {
doRemoveRecorder(recorder);
}
return recorder;
}
@Override
public TraceRecorder recordTargetBestOffer(TargetObject target) {
synchronized (recordersByTarget) {
TraceRecorder recorder = recordersByTarget.get(target);
if (recorder != null) {
Msg.warn(this, "Target is already being recorded: " + target);
return recorder;
}
}
DebuggerTargetTraceMapper mapper =
DebuggerMappingOffer.first(DebuggerMappingOpinion.queryOpinions(target, false));
if (mapper == null) {
throw new NoSuchElementException("No mapper for target: " + target);
}
try {
return recordTarget(target, mapper, ActionSource.AUTOMATIC);
}
catch (IOException e) {
throw new AssertionError("Could not record target: " + target, e);
}
}
@Internal
protected TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target) {
synchronized (recordersByTarget) {
TraceRecorder recorder = recordersByTarget.get(target);
if (recorder != null) {
Msg.warn(this, "Target is already being recorded: " + target);
return recorder;
}
}
List<DebuggerMappingOffer> offers = DebuggerMappingOpinion.queryOpinions(target, true);
offerDialog.setOffers(offers);
t.showDialog(offerDialog);
if (offerDialog.isCancelled()) {
return null;
}
DebuggerMappingOffer selected = offerDialog.getSelectedOffer();
assert selected != null;
DebuggerTargetTraceMapper mapper = selected.take();
try {
return recordTarget(target, mapper, ActionSource.MANUAL);
}
catch (IOException e) {
throw new AssertionError("Could not record target: " + target, e);
// TODO: For certain errors, It may not be appropriate to close the dialog.
}
}
@Override
public TraceRecorder recordTargetPromptOffers(TargetObject target) {
return doRecordTargetPromptOffers(tool, target);
}
protected void removeRecorder(TraceRecorder recorder) {
synchronized (recordersByTarget) {
TraceRecorder old = recordersByTarget.remove(recorder.getTarget());
/**
* Possible race condition when quickly launching and stopping a recording. If it's
* already removed, that's actually fine. If we get something non-null that doesn't
* match, then yeah, something's truly gone wrong.
*/
if (old != null) {
if (old != recorder) {
throw new AssertionError("Container-recorder mix up");
}
old.removeListener(listenerOnRecorders);
}
}
recorderListeners.invoke().elementRemoved(recorder);
}
@Override
public synchronized DebuggerObjectModel getCurrentModel() {
if (!currentModel.isAlive()) {
currentModel = null;
}
return currentModel;
}
@Override
public synchronized boolean doActivateModel(DebuggerObjectModel model) {
if (model == currentModel) {
return false;
}
currentModel = model;
return true;
}
@Internal
@Override
public void refreshFactoryInstances() {
Collection<DebuggerModelFactory> newFactories =
ClassSearcher.getInstances(DebuggerModelFactory.class);
setModelFactories(newFactories);
}
@Internal
@Override
public synchronized void setModelFactories(Collection<DebuggerModelFactory> newFactories) {
Set<DebuggerModelFactory> diff = new HashSet<>();
diff.addAll(factories);
diff.removeAll(newFactories);
for (DebuggerModelFactory factory : diff) {
factories.remove(factory);
factoryListeners.invoke().elementRemoved(factory);
}
diff.clear();
diff.addAll(newFactories);
diff.removeAll(factories);
for (DebuggerModelFactory factory : diff) {
factories.add(factory);
factoryListeners.invoke().elementAdded(factory);
}
}
@Override
public void addFactoriesChangedListener(
CollectionChangeListener<DebuggerModelFactory> listener) {
factoryListeners.add(listener);
}
@Override
public void removeFactoriesChangedListener(
CollectionChangeListener<DebuggerModelFactory> listener) {
factoryListeners.remove(listener);
}
@Override
public synchronized void addModelsChangedListener(
CollectionChangeListener<DebuggerObjectModel> listener) {
modelListeners.add(listener);
}
@Override
public synchronized void removeModelsChangedListener(
CollectionChangeListener<DebuggerObjectModel> listener) {
modelListeners.remove(listener);
}
@Override
public synchronized void addTraceRecordersChangedListener(
CollectionChangeListener<TraceRecorder> listener) {
recorderListeners.add(listener);
}
@Override
public synchronized void removeTraceRecordersChangedListener(
CollectionChangeListener<TraceRecorder> listener) {
recorderListeners.remove(listener);
}
@Override
public TraceRecorder recordTargetAndActivateTrace(TargetObject target,
DebuggerTargetTraceMapper mapper, DebuggerTraceManagerService traceManager)
throws IOException {
TraceRecorder recorder = recordTarget(target, mapper, ActionSource.AUTOMATIC);
if (traceManager != null) {
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
traceManager.activate(traceManager.resolveTrace(trace),
ActivationCause.ACTIVATE_DEFAULT);
}
return recorder;
}
@Override
public TraceRecorder recordTargetAndActivateTrace(TargetObject target,
DebuggerTargetTraceMapper mapper) throws IOException {
return recordTargetAndActivateTrace(target, mapper, null);
}
protected TraceRecorder doBeginRecording(TargetObject target, DebuggerTargetTraceMapper mapper)
throws IOException {
String traceName = nameTrace(target);
Trace trace = new DBTrace(traceName, mapper.getTraceCompilerSpec(), this);
TraceRecorder recorder = mapper.startRecording(tool, trace);
trace.release(this); // The recorder now owns it (on behalf of the service)
return recorder;
}
protected static String nameTrace(TargetObject target) {
String name = target.getDisplay();
if (name == null) {
name = PathUtils.toString(target.getPath());
}
CharBuffer buf = CharBuffer.wrap(name.toCharArray());
// This duplicates makeValidName a bit, but with replacement.
// Still use it for length check, though.
for (int i = 0; i < buf.length(); i++) {
if (!LocalFileSystem.isValidNameCharacter(buf.get(i))) {
buf.put(i, '_');
}
}
return AppInfo.getActiveProject()
.getProjectData()
.makeValidName(buf + " " + DATE_FORMAT.format(new Date()));
}
public void doRemoveRecorder(TraceRecorder recorder) {
boolean removed;
synchronized (recordersByTarget) {
// TODO: If I register a listener. Here is where to remove it.
removed = recordersByTarget.remove(recorder.getTarget()) != null;
}
if (removed) {
recorderListeners.invoke().elementRemoved(recorder);
}
}
@Override
public TraceRecorder getRecorder(TargetObject target) {
synchronized (recordersByTarget) {
return recordersByTarget.get(target);
}
}
@Override
public TraceRecorder getRecorderForSuccessor(TargetObject successor) {
synchronized (recordersByTarget) {
while (successor != null) {
TraceRecorder recorder = recordersByTarget.get(successor);
if (recorder != null) {
return recorder;
}
successor = successor.getParent();
}
return null;
}
}
@Override
public TraceRecorder getRecorder(Trace trace) {
synchronized (recordersByTarget) {
// TODO: Is a map of recorders by trace worth it?
for (TraceRecorder recorder : recordersByTarget.values()) {
if (recorder.getTrace() != trace) {
continue;
}
return recorder;
}
return null;
}
}
@Override
public TargetThread getTargetThread(TraceThread thread) {
TraceRecorder recorder = getRecorder(thread.getTrace());
if (recorder == null) {
return null;
}
return recorder.getTargetThread(thread);
}
@Override
public TargetObject getTarget(Trace trace) {
TraceRecorder recorder = getRecorder(trace);
if (recorder == null) {
return null;
}
return recorder.getTarget();
}
@Override
public Trace getTrace(TargetObject target) {
TraceRecorder recorder = getRecorder(target);
if (recorder == null) {
return null;
}
return recorder.getTrace();
}
@Override
public TraceThread getTraceThread(TargetThread thread) {
synchronized (recordersByTarget) {
for (TraceRecorder recorder : recordersByTarget.values()) {
// TODO: Consider sorting schemes to find this faster
if (!PathUtils.isAncestor(recorder.getTarget().getPath(), thread.getPath())) {
continue;
}
return recorder.getTraceThread(thread);
}
}
return null;
}
@Override
public TraceThread getTraceThread(TargetObject target, TargetThread thread) {
TraceRecorder recorder = getRecorder(target);
if (recorder == null) {
return null;
}
return recorder.getTraceThread(thread);
}
@Override
public TargetObject getTargetFocus(TargetObject target) {
TraceRecorder recorder = getRecorder(target);
if (recorder == null) {
return null;
}
return recorder.getFocus();
}
@Override
public void writeConfigState(SaveState saveState) {
for (DebuggerModelFactory factory : getModelFactories()) {
String stateName = PREFIX_FACTORY + factory.getClass().getName();
SaveState factoryState = new SaveState();
factory.writeConfigState(factoryState);
saveState.putXmlElement(stateName, factoryState.saveToXml());
}
connectDialog.writeConfigState(saveState);
}
@Override
public void readConfigState(SaveState saveState) {
for (DebuggerModelFactory factory : getModelFactories()) {
String stateName = PREFIX_FACTORY + factory.getClass().getName();
Element factoryElement = saveState.getXmlElement(stateName);
if (factoryElement != null) {
SaveState factoryState = new SaveState(factoryElement);
factory.readConfigState(factoryState);
}
}
connectDialog.readConfigState(saveState);
}
protected Stream<DebuggerProgramLaunchOffer> doGetProgramLaunchOffers(PluginTool tool,
Program program) {
return ClassSearcher.getInstances(DebuggerProgramLaunchOpinion.class)
.stream()
.flatMap(opinion -> opinion.getOffers(program, tool, this).stream());
}
@Override
public Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program) {
return doGetProgramLaunchOffers(tool, program);
}
protected CompletableFuture<DebuggerObjectModel> doShowConnectDialog(PluginTool tool,
DebuggerModelFactory factory, Program program) {
CompletableFuture<DebuggerObjectModel> future = connectDialog.reset(factory, program);
tool.showDialog(connectDialog);
return future;
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog() {
return doShowConnectDialog(tool, null, null);
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(Program program) {
return doShowConnectDialog(tool, null, program);
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
return doShowConnectDialog(tool, factory, null);
}
}

View file

@ -1,724 +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.plugin.core.debug.service.model;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.Icon;
import org.apache.commons.lang3.exception.ExceptionUtils;
import db.Transaction;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.builder.MultiStateActionBuilder;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetThread;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.model.*;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.PromptMode;
import ghidra.debug.api.progress.CloseableTaskMonitor;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.ProgramUserData;
import ghidra.program.model.util.StringPropertyMap;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@PluginInfo(
shortDescription = "Debugger models manager service (proxy to front-end)",
description = """
Manage debug sessions, connections, and trace recording. \
Deprecated since 11.2 for removal in 11.3.""",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.DEPRECATED,
eventsConsumed = {
ProgramActivatedPluginEvent.class, ProgramClosedPluginEvent.class, },
servicesRequired = {
DebuggerTargetService.class,
DebuggerTraceManagerService.class, },
servicesProvided = { DebuggerModelService.class, })
@Deprecated(forRemoval = true, since = "11.2")
public class DebuggerModelServiceProxyPlugin extends Plugin
implements DebuggerModelServiceInternal {
private static final String KEY_MOST_RECENT_LAUNCHES = "mostRecentLaunches";
private static final DebuggerProgramLaunchOffer DUMMY_LAUNCH_OFFER =
new DebuggerProgramLaunchOffer() {
@Override
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor,
PromptMode prompt, LaunchConfigurator configurator) {
throw new AssertionError("Who clicked me?");
}
@Override
public String getConfigName() {
return "DUMMY";
}
@Override
public Icon getIcon() {
return DebuggerResources.ICON_DEBUGGER;
}
@Override
public String getMenuParentTitle() {
return "";
}
@Override
public String getMenuTitle() {
return "";
}
@Override
public String getQuickTitle() {
return "";
}
@Override
public String getButtonTitle() {
return "No quick launcher for the current program";
}
};
private static final ActionState<DebuggerProgramLaunchOffer> DUMMY_LAUNCH_STATE =
new ActionState<>(DUMMY_LAUNCH_OFFER.getButtonTitle(), DUMMY_LAUNCH_OFFER.getIcon(),
DUMMY_LAUNCH_OFFER);
protected static DebuggerModelServicePlugin getOrCreateFrontEndDelegate() {
FrontEndTool frontEnd = AppInfo.getFrontEndTool();
for (Plugin plugin : frontEnd.getManagedPlugins()) {
if (plugin instanceof DebuggerModelServicePlugin) {
return (DebuggerModelServicePlugin) plugin;
}
}
try {
DebuggerModelServicePlugin plugin =
PluginUtils.instantiatePlugin(DebuggerModelServicePlugin.class, frontEnd);
frontEnd.addPlugin(plugin);
return plugin;
}
catch (PluginException e) {
throw new AssertionError(e);
}
}
protected class ProxiedFactoryChangeListener
implements CollectionChangeListener<DebuggerModelFactory> {
@Override
public void elementAdded(DebuggerModelFactory element) {
factoryListeners.invoke().elementAdded(element);
}
@Override
public void elementRemoved(DebuggerModelFactory element) {
factoryListeners.invoke().elementRemoved(element);
}
@Override
public void elementModified(DebuggerModelFactory element) {
factoryListeners.invoke().elementModified(element);
}
}
protected class ProxiedModelChangeListener
implements CollectionChangeListener<DebuggerObjectModel> {
@Override
public void elementAdded(DebuggerObjectModel element) {
modelListeners.invoke().elementAdded(element);
if (currentModel == null) {
activateModel(element);
}
}
@Override
public void elementRemoved(DebuggerObjectModel element) {
if (currentModel == element) {
activateModel(null);
}
modelListeners.invoke().elementRemoved(element);
}
@Override
public void elementModified(DebuggerObjectModel element) {
modelListeners.invoke().elementModified(element);
}
}
protected class ProxiedRecorderChangeListener
implements CollectionChangeListener<TraceRecorder> {
@Override
public void elementAdded(TraceRecorder element) {
recorderListeners.invoke().elementAdded(element);
Swing.runLater(() -> {
TraceRecorderTarget target = new TraceRecorderTarget(tool, element);
if (targets.put(element, target) == target) {
return;
}
targetService.publishTarget(target);
});
}
@Override
public void elementRemoved(TraceRecorder element) {
recorderListeners.invoke().elementRemoved(element);
Swing.runLater(() -> {
TraceRecorderTarget target = targets.remove(element);
if (target == null) {
return;
}
targetService.withdrawTarget(target);
});
}
@Override
public void elementModified(TraceRecorder element) {
recorderListeners.invoke().elementModified(element);
}
}
protected DebuggerModelServicePlugin delegate;
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerTargetService targetService;
@AutoServiceConsumed
private ProgressService progressService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
// This is not delegated. Each tool can have its own active model.
protected DebuggerObjectModel currentModel;
// Focus within the model, however, is controlled by the model, so it is global
protected Program currentProgram;
protected File currentProgramPath;
protected final ProxiedFactoryChangeListener factoryChangeListener =
new ProxiedFactoryChangeListener();
protected final ProxiedModelChangeListener modelChangeListener =
new ProxiedModelChangeListener();
protected final ProxiedRecorderChangeListener recorderChangeListener =
new ProxiedRecorderChangeListener();
MultiStateDockingAction<DebuggerProgramLaunchOffer> actionDebugProgram;
Set<DockingAction> actionDebugProgramMenus = new HashSet<>();
DockingAction actionDisconnectAll;
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true);
protected final ListenerSet<CollectionChangeListener<DebuggerObjectModel>> modelListeners =
new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true);
protected final ListenerSet<CollectionChangeListener<TraceRecorder>> recorderListeners =
new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true);
protected final Map<TraceRecorder, TraceRecorderTarget> targets = new HashMap<>();
public DebuggerModelServiceProxyPlugin(PluginTool tool) {
super(tool);
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
}
@Override
protected void init() {
super.init();
delegate = getOrCreateFrontEndDelegate();
delegate.addProxy(this);
delegate.addFactoriesChangedListener(factoryChangeListener);
delegate.addModelsChangedListener(modelChangeListener);
delegate.addTraceRecordersChangedListener(recorderChangeListener);
createActions();
}
protected void createActions() {
// Note, I have to give an enabledWhen, otherwise any context change re-enables it
MultiStateActionBuilder<DebuggerProgramLaunchOffer> builderDebugProgram =
DebugProgramAction.buttonBuilder(this, delegate);
actionDebugProgram = builderDebugProgram.enabledWhen(ctx -> currentProgram != null)
.onAction(this::debugProgramButtonActivated)
.onActionStateChanged(this::debugProgramStateActivated)
.addState(DUMMY_LAUNCH_STATE)
.buildAndInstall(tool);
actionDisconnectAll = DisconnectAllAction.builder(this, delegate)
.menuPath("Debugger", DisconnectAllAction.NAME)
.onAction(this::activatedDisconnectAll)
.buildAndInstall(tool);
updateActionDebugProgram();
}
private void activatedDisconnectAll(ActionContext context) {
closeAllModels();
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog() {
return delegate.doShowConnectDialog(tool, null, null);
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(Program program) {
return delegate.doShowConnectDialog(tool, null, program);
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
return delegate.doShowConnectDialog(tool, factory, null);
}
@Override
public Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program) {
return orderOffers(delegate.doGetProgramLaunchOffers(tool, program), program);
}
protected List<String> readMostRecentLaunches(Program program) {
StringPropertyMap prop = program.getProgramUserData()
.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, false);
if (prop == null) {
return List.of();
}
Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
String str = prop.getString(min);
if (str == null) {
return List.of();
}
return List.of(str.split(";"));
}
protected void writeMostRecentLaunches(Program program, List<String> mrl) {
ProgramUserData userData = program.getProgramUserData();
try (Transaction tid = userData.openTransaction()) {
StringPropertyMap prop =
userData.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true);
Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
prop.add(min, mrl.stream().collect(Collectors.joining(";")));
}
}
static class OfferComparator implements Comparator<DebuggerProgramLaunchOffer> {
Map<String, Integer> fastIndex = new HashMap<>();
public OfferComparator(List<String> mostRecentNames) {
int i = 0;
for (String name : mostRecentNames) {
fastIndex.put(name, i++);
}
}
@Override
public int compare(DebuggerProgramLaunchOffer o1, DebuggerProgramLaunchOffer o2) {
int i1 = fastIndex.getOrDefault(o1, -1);
int i2 = fastIndex.getOrDefault(o2, -1);
int result = i1 - i2; // reversed, yes. Most recent is last in list
if (result != 0) {
return result;
}
return o1.defaultPriority() - o2.defaultPriority(); // Greater is higher priority
}
}
protected Stream<DebuggerProgramLaunchOffer> orderOffers(
Stream<DebuggerProgramLaunchOffer> offers, Program program) {
List<String> mrl = readMostRecentLaunches(program);
return offers.sorted(Comparator.comparingInt(o -> -mrl.indexOf(o.getConfigName())));
}
private void debugProgram(DebuggerProgramLaunchOffer offer, Program program,
PromptMode prompt) {
List<String> recent = new ArrayList<>(readMostRecentLaunches(program));
recent.remove(offer.getConfigName());
recent.add(offer.getConfigName());
writeMostRecentLaunches(program, recent);
updateActionDebugProgram();
if (progressService == null) {
BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> {
return offer.launchProgram(m, prompt).exceptionally(ex -> {
Throwable t = AsyncUtils.unwrapThrowable(ex);
if (t instanceof CancellationException || t instanceof CancelledException) {
return null;
}
return ExceptionUtils.rethrow(ex);
}).whenCompleteAsync((v, e) -> {
updateActionDebugProgram();
}, AsyncUtils.SWING_EXECUTOR);
});
}
else {
@SuppressWarnings("resource")
CloseableTaskMonitor monitor = progressService.publishTask();
offer.launchProgram(monitor, prompt).exceptionally(ex -> {
Throwable t = AsyncUtils.unwrapThrowable(ex);
if (t instanceof CancellationException || t instanceof CancelledException) {
return null;
}
monitor.reportError(t);
return ExceptionUtils.rethrow(ex);
}).whenCompleteAsync((v, e) -> {
monitor.close();
updateActionDebugProgram();
}, AsyncUtils.SWING_EXECUTOR);
}
}
private void debugProgramButtonActivated(ActionContext ctx) {
DebuggerProgramLaunchOffer offer = actionDebugProgram.getCurrentUserData();
Program program = currentProgram;
if (offer == null || program == null) {
return;
}
debugProgram(offer, program, PromptMode.ON_ERROR);
}
private void debugProgramStateActivated(ActionState<DebuggerProgramLaunchOffer> offer,
EventTrigger trigger) {
if (trigger == EventTrigger.GUI_ACTION) {
debugProgramButtonActivated(null);
}
}
private void debugProgramMenuActivated(DebuggerProgramLaunchOffer offer) {
Program program = currentProgram;
if (program == null) {
return;
}
debugProgram(offer, program, PromptMode.ALWAYS);
}
private void updateActionDebugProgram() {
if (actionDebugProgram == null) {
return;
}
Program program = currentProgram;
List<DebuggerProgramLaunchOffer> offers = program == null ? List.of()
: getProgramLaunchOffers(program).collect(Collectors.toList());
List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream()
.map(o -> new ActionState<>(o.getButtonTitle(), o.getIcon(), o))
.collect(Collectors.toList());
if (!states.isEmpty()) {
actionDebugProgram.setActionStates(states);
actionDebugProgram.setEnabled(true);
actionDebugProgram.setCurrentActionState(states.get(0));
}
else {
actionDebugProgram.setActionStates(List.of(DUMMY_LAUNCH_STATE));
actionDebugProgram.setEnabled(false);
actionDebugProgram.setCurrentActionState(DUMMY_LAUNCH_STATE);
}
for (Iterator<DockingAction> it = actionDebugProgramMenus.iterator(); it.hasNext();) {
DockingAction action = it.next();
it.remove();
tool.removeAction(action);
String[] path = action.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), null);
}
for (DebuggerProgramLaunchOffer offer : offers) {
DockingAction action = DebugProgramAction.menuBuilder(offer, this, delegate)
.onAction(ctx -> debugProgramMenuActivated(offer))
.build();
actionDebugProgramMenus.add(action);
String[] path = action.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), DebugProgramAction.GROUP, "zz");
tool.addAction(action);
}
}
@Override
protected void dispose() {
super.dispose();
if (delegate != null) {
delegate.removeProxy(this);
delegate.removeFactoriesChangedListener(factoryChangeListener);
delegate.removeModelsChangedListener(modelChangeListener);
delegate.removeTraceRecordersChangedListener(recorderChangeListener);
}
currentModel = null;
}
private File getProgramPath(Program program) {
if (program == null) {
return null;
}
String path = program.getExecutablePath();
if (path == null) {
return null;
}
File file = new File(path);
try {
if (!file.canExecute()) {
return null;
}
return file.getCanonicalFile();
}
catch (SecurityException | IOException e) {
Msg.error(this, "Cannot examine file " + path, e);
return null;
}
}
@Override
public void fireFocusEvent(TargetObject focused) {
if (focused == null) {
return;
}
TraceRecorder recorder = getRecorderForSuccessor(focused);
if (recorder == null) {
return;
}
Trace trace = recorder.getTrace();
if (trace == null) {
return;
}
Trace curTrace = traceManager.getCurrentTrace();
if (curTrace != null && curTrace != trace) {
return;
}
DebuggerCoordinates focusedCoords =
traceManager.getCurrent().trace(trace).path(TraceObjectKeyPath.of(focused.getPath()));
traceManager.activate(focusedCoords, ActivationCause.SYNC_MODEL);
}
@Override
public void fireSnapEvent(TraceRecorder recorder, long snap) {
Trace trace = recorder.getTrace();
DebuggerCoordinates current = traceManager.getCurrentFor(trace);
if (current.getTrace() == null) {
return;
}
DebuggerCoordinates snapCoords = current.snapNoResolve(snap);
traceManager.activate(snapCoords, ActivationCause.FOLLOW_PRESENT);
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof ProgramActivatedPluginEvent evt) {
currentProgram = evt.getActiveProgram();
currentProgramPath = getProgramPath(currentProgram);
updateActionDebugProgram();
}
if (event instanceof ProgramClosedPluginEvent evt) {
if (currentProgram == evt.getProgram()) {
currentProgram = null;
currentProgramPath = null;
updateActionDebugProgram();
}
}
}
@Override
public void refreshFactoryInstances() {
delegate.refreshFactoryInstances();
}
@Override
public void setModelFactories(Collection<DebuggerModelFactory> factories) {
delegate.setModelFactories(factories);
}
@Override
public Set<DebuggerModelFactory> getModelFactories() {
return delegate.getModelFactories();
}
@Override
public Set<DebuggerObjectModel> getModels() {
return delegate.getModels();
}
@Override
public CompletableFuture<Void> closeAllModels() {
return delegate.closeAllModels();
}
@Override
public Collection<TraceRecorder> getTraceRecorders() {
return delegate.getTraceRecorders();
}
@Override
public boolean addModel(DebuggerObjectModel model) {
return delegate.addModel(model);
}
@Override
public boolean removeModel(DebuggerObjectModel model) {
return delegate.removeModel(model);
}
@Override
public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper,
ActionSource source) throws IOException {
return delegate.recordTarget(target, mapper, source);
}
@Override
public TraceRecorder recordTargetBestOffer(TargetObject target) {
return delegate.recordTargetBestOffer(target);
}
@Override
public TraceRecorder recordTargetPromptOffers(TargetObject target) {
return delegate.doRecordTargetPromptOffers(tool, target);
}
@Override
public synchronized boolean doActivateModel(DebuggerObjectModel model) {
if (model == currentModel) {
return false;
}
currentModel = model;
return true;
}
@Override
public synchronized DebuggerObjectModel getCurrentModel() {
return currentModel;
}
@Override
public void addFactoriesChangedListener(
CollectionChangeListener<DebuggerModelFactory> listener) {
factoryListeners.add(listener);
}
@Override
public void removeFactoriesChangedListener(
CollectionChangeListener<DebuggerModelFactory> listener) {
factoryListeners.remove(listener);
}
@Override
public void addModelsChangedListener(CollectionChangeListener<DebuggerObjectModel> listener) {
modelListeners.add(listener);
}
@Override
public void removeModelsChangedListener(
CollectionChangeListener<DebuggerObjectModel> listener) {
modelListeners.remove(listener);
}
@Override
public void addTraceRecordersChangedListener(CollectionChangeListener<TraceRecorder> listener) {
recorderListeners.add(listener);
}
@Override
public void removeTraceRecordersChangedListener(
CollectionChangeListener<TraceRecorder> listener) {
recorderListeners.remove(listener);
}
@Override
public TraceRecorder recordTargetAndActivateTrace(TargetObject target,
DebuggerTargetTraceMapper mapper, DebuggerTraceManagerService altTraceManager)
throws IOException {
return delegate.recordTargetAndActivateTrace(target, mapper, altTraceManager);
}
@Override
public TraceRecorder recordTargetAndActivateTrace(TargetObject target,
DebuggerTargetTraceMapper mapper) throws IOException {
return delegate.recordTargetAndActivateTrace(target, mapper, traceManager);
}
@Override
public TraceRecorder getRecorder(TargetObject target) {
return delegate.getRecorder(target);
}
@Override
public TraceRecorder getRecorderForSuccessor(TargetObject obj) {
return delegate.getRecorderForSuccessor(obj);
}
@Override
public TraceRecorder getRecorder(Trace trace) {
return delegate.getRecorder(trace);
}
@Override
public TargetThread getTargetThread(TraceThread thread) {
return delegate.getTargetThread(thread);
}
@Override
public TargetObject getTarget(Trace trace) {
return delegate.getTarget(trace);
}
@Override
public Trace getTrace(TargetObject target) {
return delegate.getTrace(target);
}
@Override
public TraceThread getTraceThread(TargetThread thread) {
return delegate.getTraceThread(thread);
}
@Override
public TraceThread getTraceThread(TargetObject target, TargetThread thread) {
return delegate.getTraceThread(target, thread);
}
@Override
public TargetObject getTargetFocus(TargetObject target) {
return delegate.getTargetFocus(target);
}
}

View file

@ -1,343 +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.plugin.core.debug.service.model;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import javax.swing.*;
import docking.ReusableDialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.debug.api.model.DebuggerMappingOffer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@Deprecated(forRemoval = true, since = "11.3")
public class DebuggerSelectMappingOfferDialog extends ReusableDialogComponentProvider {
protected enum OfferTableColumns
implements EnumeratedTableColumn<OfferTableColumns, DebuggerMappingOffer> {
CONFIDENCE("Confidence", Integer.class, DebuggerMappingOffer::getConfidence, SortDirection.DESCENDING),
PROCESSOR("Processor", Processor.class, OfferTableColumns::getProcessor),
VARIANT("Variant", String.class, OfferTableColumns::getVariant),
SIZE("Size", Integer.class, OfferTableColumns::getSize),
ENDIAN("Endian", Endian.class, OfferTableColumns::getEndian),
COMPILER("Compiler", CompilerSpecID.class, DebuggerMappingOffer::getTraceCompilerSpecID);
private static final LanguageService LANG_SERV =
DefaultLanguageService.getLanguageService();
private static Processor getProcessor(DebuggerMappingOffer offer) {
try {
return LANG_SERV.getLanguageDescription(offer.getTraceLanguageID()).getProcessor();
}
catch (LanguageNotFoundException e) {
return Processor.findOrPossiblyCreateProcessor("Not Found");
}
}
private static String getVariant(DebuggerMappingOffer offer) {
try {
return LANG_SERV.getLanguageDescription(offer.getTraceLanguageID()).getVariant();
}
catch (LanguageNotFoundException e) {
return "???";
}
}
private static int getSize(DebuggerMappingOffer offer) {
try {
return LANG_SERV.getLanguageDescription(offer.getTraceLanguageID()).getSize();
}
catch (LanguageNotFoundException e) {
return 0;
}
}
private static Endian getEndian(DebuggerMappingOffer offer) {
try {
return LANG_SERV.getLanguageDescription(offer.getTraceLanguageID()).getEndian();
}
catch (LanguageNotFoundException e) {
return null;
}
}
private final String header;
private final Class<?> cls;
private final Function<DebuggerMappingOffer, ?> getter;
private final SortDirection sortDir;
<T> OfferTableColumns(String header, Class<T> cls,
Function<DebuggerMappingOffer, T> getter, SortDirection sortDir) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.sortDir = sortDir;
}
<T> OfferTableColumns(String header, Class<T> cls,
Function<DebuggerMappingOffer, T> getter) {
this(header, cls, getter, SortDirection.ASCENDING);
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(DebuggerMappingOffer row) {
return getter.apply(row);
}
@Override
public String getHeader() {
return header;
}
@Override
public SortDirection defaultSortDirection() {
return sortDir;
}
}
public static class OfferTableModel
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerMappingOffer> {
public OfferTableModel(PluginTool tool) {
super(tool, "Offers", OfferTableColumns.class);
}
@Override
public List<OfferTableColumns> defaultSortOrder() {
return List.of(OfferTableColumns.CONFIDENCE, OfferTableColumns.PROCESSOR,
OfferTableColumns.VARIANT, OfferTableColumns.COMPILER);
}
}
public static class OfferPanel extends JPanel {
private final OfferTableModel offerTableModel;
private final GhidraTable offerTable;
private final GhidraTableFilterPanel<DebuggerMappingOffer> offerTableFilterPanel;
private final JLabel descLabel = new JLabel();
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
private final JScrollPane scrollPane = new JScrollPane() {
@Override
public Dimension getPreferredSize() {
Dimension pref = super.getPreferredSize();
if (pref.width != 0) {
pref.height = 150;
}
return pref;
}
};
private final TableFilter<DebuggerMappingOffer> filterRecommended = new TableFilter<>() {
@Override
public boolean acceptsRow(DebuggerMappingOffer offer) {
return !offer.isOverride();
}
@Override
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
return false;
}
};
private LanguageID preferredLangID;
private CompilerSpecID preferredCsID;
protected OfferPanel(PluginTool tool) {
offerTableModel = new OfferTableModel(tool);
offerTable = new GhidraTable(offerTableModel);
offerTableFilterPanel = new GhidraTableFilterPanel<>(offerTable, offerTableModel);
scrollPane.setViewportView(offerTable);
JPanel descPanel = new JPanel(new BorderLayout());
descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
descPanel.add(descLabel, BorderLayout.CENTER);
JPanel nested1 = new JPanel(new BorderLayout());
nested1.add(scrollPane, BorderLayout.CENTER);
nested1.add(offerTableFilterPanel, BorderLayout.SOUTH);
JPanel nested2 = new JPanel(new BorderLayout());
nested2.add(nested1, BorderLayout.CENTER);
nested2.add(descPanel, BorderLayout.SOUTH);
setLayout(new BorderLayout());
add(nested2, BorderLayout.CENTER);
add(overrideCheckBox, BorderLayout.SOUTH);
setFilterRecommended(true);
offerTable.getSelectionModel().addListSelectionListener(e -> {
DebuggerMappingOffer offer = getSelectedOffer();
descLabel.setText(offer == null ? "" : offer.getDescription());
});
overrideCheckBox.addActionListener(evt -> {
setFilterRecommended(overrideCheckBox.isSelected());
});
}
public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
this.preferredLangID = langID;
this.preferredCsID = csID;
}
public void setOffers(Collection<DebuggerMappingOffer> offers) {
offerTableModel.clear();
offerTableModel.addAll(offers);
selectPreferred();
}
private void selectPreferred() {
// As sorted and filtered, pick the first matching offer
// NB. It should never be one or the other. Always both or none.
RowObjectFilterModel<DebuggerMappingOffer> model =
offerTableFilterPanel.getTableFilterModel();
int count = model.getRowCount();
if (preferredLangID != null && preferredCsID != null) {
for (int i = 0; i < count; i++) {
DebuggerMappingOffer offer = model.getRowObject(i);
if (offer.getTraceLanguageID().equals(preferredLangID) &&
offer.getTraceCompilerSpecID().equals(preferredCsID)) {
offerTable.getSelectionModel().setSelectionInterval(i, i);
return;
}
}
}
// Fall back to first offer; disregard preference
if (model.getRowCount() > 0) {
offerTable.getSelectionModel().setSelectionInterval(0, 0);
}
}
public void setFilterRecommended(boolean recommendedOnly) {
boolean hasSelection = offerTableFilterPanel.getSelectedItem() != null;
overrideCheckBox.setSelected(recommendedOnly);
offerTableFilterPanel.setSecondaryFilter(recommendedOnly ? filterRecommended : null);
if (!hasSelection) {
selectPreferred();
}
}
public void setSelectedOffer(DebuggerMappingOffer offer) {
offerTableFilterPanel.setSelectedItem(offer);
}
public DebuggerMappingOffer getSelectedOffer() {
return offerTableFilterPanel.getSelectedItem();
}
// For tests
public List<DebuggerMappingOffer> getDisplayedOffers() {
return List.copyOf(offerTableFilterPanel.getTableFilterModel().getModelData());
}
}
private final OfferPanel offerPanel;
private boolean isCancelled = false;
protected DebuggerSelectMappingOfferDialog(PluginTool tool) {
super(DebuggerResources.AbstractRecordAction.NAME, true, false, true, false);
offerPanel = new OfferPanel(tool);
populateComponents();
}
protected void populateComponents() {
offerPanel.setBorder(BorderFactory.createTitledBorder(" Select Target Recorder Mapper "));
addWorkPanel(offerPanel);
addOKButton();
addCancelButton();
setDefaultButton(okButton);
setOkEnabled(false);
// TODO: Separate this a bit
offerPanel.offerTable.getSelectionModel().addListSelectionListener(e -> {
setOkEnabled(getSelectedOffer() != null);
});
}
/**
* Set the preferred language and compiler spec IDs, typically from the current program.
*
* <p>
* This must be called before {@link #setOffers(Collection)}.
*
* @param langID the preferred language
* @param csID the preferred compiler spec (ABI)
*/
public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
offerPanel.setPreferredIDs(langID, csID);
}
public void setOffers(Collection<DebuggerMappingOffer> offers) {
offerPanel.setOffers(offers);
}
public boolean isCancelled() {
return isCancelled;
}
public void setSelectedOffer(DebuggerMappingOffer offer) {
offerPanel.setSelectedOffer(offer);
}
public DebuggerMappingOffer getSelectedOffer() {
return offerPanel.getSelectedOffer();
}
// For tests
protected List<DebuggerMappingOffer> getDisplayedOffers() {
return offerPanel.getDisplayedOffers();
}
protected void setFilterRecommended(boolean recommendedOnly) {
offerPanel.setFilterRecommended(recommendedOnly);
}
@Override
protected void cancelCallback() {
isCancelled = true;
super.cancelCallback();
}
@Override
protected void okCallback() {
if (getSelectedOffer() != null) {
isCancelled = false;
close();
}
// Do nothing. Should be disabled anyway
}
}

View file

@ -1,219 +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.plugin.core.debug.service.model;
import java.util.Collection;
import java.util.Set;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedBreakpointRecorder;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder {
protected static String nameBreakpoint(TargetBreakpointLocation bpt) {
if (bpt instanceof TargetBreakpointSpec) {
return bpt.getIndex();
}
return bpt.getSpecification().getIndex() + "." + bpt.getIndex();
}
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceBreakpointManager breakpointManager;
protected TargetBreakpointSpecContainer breakpointContainer;
public DefaultBreakpointRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.breakpointManager = trace.getBreakpointManager();
}
@Override
public void offerBreakpointContainer(TargetBreakpointSpecContainer bc) {
if (breakpointContainer != null) {
Msg.warn(this, "Already have a breakpoint container for this process");
}
breakpointContainer = bc;
}
@Override
public void offerBreakpointLocation(TargetObject containerParent,
TargetBreakpointLocation bpt) {
synchronized (this) {
if (recorder.getMemoryMapper() == null) {
return;
}
}
RecorderBreakpointLocationResolver resolver =
new RecorderBreakpointLocationResolver(recorder, bpt);
resolver.updateBreakpoint(containerParent, bpt);
}
protected void doRecordBreakpoint(long snap, TargetBreakpointLocation loc,
Set<TraceThread> traceThreads) {
synchronized (this) {
if (recorder.getMemoryMapper() == null) {
throw new IllegalStateException(
"No memory mapper! Have not recorded a region, yet.");
}
}
String path = PathUtils.toString(loc.getPath());
String name = nameBreakpoint(loc);
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(loc.getRange());
try {
TargetBreakpointSpec spec = loc.getSpecification();
boolean enabled = spec.isEnabled();
Set<TraceBreakpointKind> traceKinds =
TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds());
TraceBreakpoint traceBpt =
breakpointManager.placeBreakpoint(path, snap,
traceRange, traceThreads, traceKinds, enabled, spec.getExpression());
traceBpt.setName(name);
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not record placed breakpoint: " + e);
}
}
@Override
public void recordBreakpoint(TargetBreakpointLocation loc,
Set<TraceThread> traceThreads) {
String path = loc.getJoinedPath(".");
long snap = recorder.getSnap();
recorder.parTx.execute("Breakpoint " + path + " placed", () -> {
doRecordBreakpoint(snap, loc, traceThreads);
}, path);
}
protected void doRemoveBreakpointLocation(long snap, TargetBreakpointLocation loc) {
String path = loc.getJoinedPath(".");
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
try {
if (traceBpt.getPlacedSnap() > snap) {
Msg.error(this,
"Tracked, now removed breakpoint was placed in the future? " + path);
}
else if (traceBpt.getPlacedSnap() == snap) {
// TODO: I forget if this is allowed for DBTrace iteration
traceBpt.delete();
}
else {
traceBpt.setClearedSnap(snap - 1);
}
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not record breakpoint removal: " + e);
}
}
}
@Override
public void removeBreakpointLocation(TargetBreakpointLocation loc) {
String path = loc.getJoinedPath(".");
long snap = recorder.getSnap();
recorder.parTx.execute("Breakpoint " + path + " deleted", () -> {
doRemoveBreakpointLocation(snap, loc);
}, path);
}
protected void doBreakpointLocationChanged(long snap, AddressRange traceRng, String path) {
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
if (traceBpt.getRange().equals(traceRng)) {
continue; // Nothing to change
}
// TODO: Verify all other attributes match?
try {
if (traceBpt.getPlacedSnap() == snap) {
traceBpt.delete();
}
else {
traceBpt.setClearedSnap(snap - 1);
}
TraceBreakpoint newtraceBpt =
breakpointManager.placeBreakpoint(path, snap, traceRng,
traceBpt.getThreads(), traceBpt.getKinds(), traceBpt.isEnabled(snap),
traceBpt.getComment());
// placeBreakpoint resets the name - maybe pass name in?
newtraceBpt.setName(traceBpt.getName());
}
catch (DuplicateNameException e) {
// Split, and length matters not
Msg.error(this, "Could not record breakpoint length change: " + e);
}
}
}
@Override
public void breakpointLocationChanged(AddressRange traceRng, String path)
throws AssertionError {
long snap = recorder.getSnap();
recorder.parTx.execute("Breakpoint length changed", () -> {
doBreakpointLocationChanged(snap, traceRng, path);
}, path);
}
protected void doBreakpointSpecChanged(long snap,
Collection<? extends TargetBreakpointLocation> bpts, boolean enabled,
Collection<TraceBreakpointKind> kinds) {
for (TargetBreakpointLocation bl : bpts) {
String path = PathUtils.toString(bl.getPath());
recorder.parTx.execute("Breakpoint " + path + " changed", () -> {
TraceBreakpoint traceBpt = recorder.getTraceBreakpoint(bl);
if (traceBpt == null) {
Msg.warn(this, "Cannot find toggled trace breakpoint for " + path);
return;
}
// Verify attributes match? Eh. If they don't, someone has fiddled with it.
traceBpt.splitAndSet(snap, enabled, kinds);
}, path);
}
}
@Override
public void breakpointSpecChanged(TargetBreakpointSpec spec, boolean enabled,
Collection<TraceBreakpointKind> kinds) {
long snap = recorder.getSnap();
spec.getLocations().thenAccept(bpts -> {
doBreakpointSpecChanged(snap, bpts, enabled, kinds);
}).exceptionally(ex -> {
Msg.error(this, "Error recording changed breakpoint spec: " + spec.getJoinedPath("."),
ex);
return null;
});
}
@Override
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
String path = PathUtils.toString(bpt.getPath());
return breakpointManager.getPlacedBreakpointByPath(recorder.getSnap(), path);
}
@Override
public TargetBreakpointSpecContainer getBreakpointContainer() {
return breakpointContainer;
}
}

View file

@ -1,162 +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.plugin.core.debug.service.model;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder;
import ghidra.app.plugin.core.debug.service.model.record.RecorderUtils;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
// For large memory captures
private static final int BLOCK_BITS = 12; // 4096 bytes
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceMemoryManager memoryManager;
public DefaultMemoryRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.memoryManager = trace.getMemoryManager();
}
public CompletableFuture<Void> captureProcessMemory(AddressSetView set,
TaskMonitor monitor) {
return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor);
}
@Override
public void offerProcessMemory(TargetMemory memory) {
recorder.getProcessMemory().addMemory(memory);
}
@Override
public void offerProcessRegion(TargetMemoryRegion region) {
TargetMemory mem = region.getMemory();
recorder.getProcessMemory().addRegion(region, mem);
//recorder.objectManager.addMemory(mem);
String path = region.getJoinedPath(".");
long snap = recorder.getSnap();
recorder.parTx.execute("Memory region " + path + " added", () -> {
try {
TraceMemoryRegion traceRegion =
memoryManager.getLiveRegionByPath(snap, path);
if (traceRegion != null) {
Msg.warn(this, "Region " + path + " already recorded");
return;
}
AddressRange traceRange =
recorder.getMemoryMapper().targetToTraceTruncated(region.getRange());
if (traceRange == null) {
Msg.warn(this, "Dropped unmappable region: " + region);
return;
}
if (region.getRange().getLength() != traceRange.getLength()) {
Msg.warn(this, "Truncated region: " + region);
}
traceRegion = memoryManager.addRegion(path, Lifespan.nowOn(snap), traceRange,
getTraceFlags(region));
traceRegion.setName(region.getDisplay());
}
catch (TraceOverlappedRegionException e) {
Msg.error(this, "Failed to create region due to overlap: " + e);
}
catch (DuplicateNameException e) {
Msg.error(this, "Failed to create region due to duplicate: " + e);
}
}, path);
}
@Override
public void removeProcessMemory(TargetMemory memory) {
recorder.getProcessMemory().removeMemory(memory);
}
@Override
public void removeProcessRegion(TargetMemoryRegion region) {
// Already removed from processMemory. That's how we knew to go here.
String path = region.getJoinedPath(".");
long snap = recorder.getSnap();
recorder.parTx.execute("Memory region " + path + " removed", () -> {
try {
TraceMemoryRegion traceRegion = memoryManager.getLiveRegionByPath(snap, path);
if (traceRegion == null) {
Msg.warn(this, "Could not find region " + path + " in trace to remove");
return;
}
if (traceRegion.getCreationSnap() >= snap) {
traceRegion.delete();
}
else {
traceRegion.setDestructionSnap(snap - 1);
}
}
catch (DuplicateNameException | TraceOverlappedRegionException e) {
// Region is shrinking in time
Msg.error(this, "Failed to record region removal: " + e);
}
}, path);
}
@Override
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
String path = region.getJoinedPath(".");
return memoryManager.getLiveRegionByPath(recorder.getSnap(), path);
}
public Collection<TraceMemoryFlag> getTraceFlags(TargetMemoryRegion region) {
Collection<TraceMemoryFlag> flags = new HashSet<>();
if (region.isReadable()) {
flags.add(TraceMemoryFlag.READ);
}
if (region.isWritable()) {
flags.add(TraceMemoryFlag.WRITE);
}
if (region.isExecutable()) {
flags.add(TraceMemoryFlag.EXECUTE);
}
// TODO: Volatile? Can any debugger report that?
return flags;
}
public void regionChanged(TargetMemoryRegion region, String display) {
String path = region.getJoinedPath(".");
long snap = recorder.getSnap();
recorder.parTx.execute("Memory region " + path + " changed display", () -> {
TraceMemoryRegion traceRegion = memoryManager.getLiveRegionByPath(snap, path);
if (traceRegion == null) {
Msg.warn(this, "Could not find region " + path + " in trace to rename");
return;
}
traceRegion.setName(display);
}, path);
}
}

View file

@ -1,168 +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.plugin.core.debug.service.model;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedModuleRecorder;
import ghidra.dbg.target.TargetModule;
import ghidra.dbg.target.TargetSection;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultModuleRecorder implements ManagedModuleRecorder {
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceModuleManager moduleManager;
public DefaultModuleRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.moduleManager = trace.getModuleManager();
}
protected TraceModule doRecordProcessModule(long snap, TargetModule module) {
String path = module.getJoinedPath(".");
if (recorder.getMemoryMapper() == null) {
Msg.error(this, "Got module before memory mapper: " + path);
return null;
}
// Short-circuit the DuplicateNameException for efficiency?
TraceModule exists = moduleManager.getLoadedModuleByPath(snap, path);
if (exists != null) {
return exists;
}
try {
AddressRange targetRange = module.getRange();
if (targetRange == null) {
Msg.error(this, "Range not found for " + module);
return null;
}
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(targetRange);
return moduleManager.addLoadedModule(path, module.getModuleName(), traceRange, snap);
}
catch (DuplicateNameException e) {
// This resolves the race condition, since DB access is synchronized
return moduleManager.getLoadedModuleByPath(snap, path);
}
}
@Override
public void offerProcessModule(TargetModule module) {
long snap = recorder.getSnap();
String path = module.getJoinedPath(".");
recorder.parTx.execute("Module " + path + " loaded", () -> {
doRecordProcessModule(snap, module);
}, path);
}
protected TraceSection doRecordProcessModuleSection(long snap, TargetSection section) {
String path = section.getJoinedPath(".");
if (recorder.getMemoryMapper() == null) {
Msg.error(this, "Got module section before memory mapper: " + path);
return null;
}
TraceModule traceModule = doRecordProcessModule(snap, section.getModule());
if (traceModule == null) {
return null; // Failure should already be logged
}
try {
AddressRange targetRange = section.getRange();
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(targetRange);
return traceModule.addSection(path, section.getIndex(), traceRange);
}
catch (DuplicateNameException e) {
// Msg.warn(this, path + " already recorded");
return moduleManager.getLoadedSectionByPath(snap, path);
}
}
@Override
public void offerProcessModuleSection(TargetSection section) {
long snap = recorder.getSnap();
String path = section.getJoinedPath(".");
recorder.parTx.execute("Section " + path + " added", () -> {
doRecordProcessModuleSection(snap, section);
}, section.getModule().getJoinedPath("."));
}
protected void doRemoveProcessModule(long snap, TargetModule module) {
String path = module.getJoinedPath(".");
//TraceThread eventThread = recorder.getSnapshot().getEventThread();
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snap, path);
if (traceModule == null) {
Msg.warn(this, "unloaded " + path + " is not in the trace");
return;
}
try {
if (traceModule.getLoadedSnap() == snap) {
Msg.warn(this, "Observed module unload in the same snap as its load");
//recorder.createSnapshot("WARN: Module removed", eventThread, tid);
}
traceModule.setUnloadedSnap(snap);
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not record process module removed: " + e);
}
}
public void moduleChanged(TargetModule module, AddressRange traceRng) {
long snap = recorder.getSnap();
String path = module.getJoinedPath(".");
recorder.parTx.execute("Module " + path + " range updated", () -> {
doModuleChanged(snap, path, traceRng);
}, path);
}
protected void doModuleChanged(long snap, String path, AddressRange traceRng) {
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snap, path);
if (traceModule == null) {
Msg.warn(this, "changed " + path + " is not in the trace");
return;
}
/**
* Yes, this will modify the module's previous history, which technically could be
* incorrect. The occasion should be rare, and the OBTR will handle it correctly.
*/
traceModule.setRange(traceRng);
}
@Override
public void removeProcessModule(TargetModule module) {
long snap = recorder.getSnap();
String path = module.getJoinedPath(".");
recorder.parTx.execute("Module " + path + " unloaded", () -> {
doRemoveProcessModule(snap, module);
}, path);
}
@Override
public TraceModule getTraceModule(TargetModule module) {
String path = module.getJoinedPath(".");
return moduleManager.getLoadedModuleByPath(recorder.getSnap(), path);
}
@Override
public TraceSection getTraceSection(TargetSection section) {
String path = section.getJoinedPath(".");
return moduleManager.getLoadedSectionByPath(recorder.getSnap(), path);
}
}

View file

@ -1,94 +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.plugin.core.debug.service.model;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.util.TriConsumer;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultProcessRecorder implements ManagedProcessRecorder {
private final AbstractRecorderMemory processMemory;
protected final TriConsumer<Boolean, Boolean, Void> listenerProcMemAccChanged =
this::processMemoryAccessibilityChanged;
private DefaultBreakpointRecorder breakpointRecorder;
private DefaultTraceRecorder recorder;
public DefaultProcessRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.processMemory = new RecorderSimpleMemory();
//this.processMemory = new RecorderComposedMemory(this.getProcessMemory());
//getProcessMemory().getMemAccListeners().add(listenerProcMemAccChanged);
this.breakpointRecorder = new DefaultBreakpointRecorder(recorder);
}
protected void processMemoryAccessibilityChanged(boolean old,
boolean acc, Void __) {
recorder.getListeners().invoke().processMemoryAccessibilityChanged(recorder);
}
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {
Address tStart = recorder.getMemoryMapper().traceToTarget(start);
return getProcessMemory().readMemory(tStart, length);
}
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) {
Address tStart = recorder.getMemoryMapper().traceToTarget(start);
return getProcessMemory().writeMemory(tStart, data);
}
public AddressSetView getAccessibleProcessMemory() {
// TODO: Efficiently distinguish which memory is process vs. thread
///TODO Is this correct?
return getProcessMemory().getAccessibleMemory(mem -> true, recorder.getMemoryMapper());
}
@Override
public AbstractRecorderMemory getProcessMemory() {
return processMemory;
}
@Override
public ManagedBreakpointRecorder getBreakpointRecorder() {
return breakpointRecorder;
}
@Override
public Trace getTrace() {
return recorder.trace;
}
@Override
public long getSnap() {
return recorder.getSnap();
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return recorder.getMemoryMapper();
}
}

View file

@ -1,176 +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.plugin.core.debug.service.model;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.*;
import ghidra.trace.model.thread.TraceThread;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultStackRecorder implements ManagedStackRecorder {
protected static int getFrameLevel(TargetStackFrame frame) {
// TODO: A fair assumption? frames are elements with numeric base-10 indices
return Integer.decode(frame.getIndex());
}
private NavigableMap<Integer, TargetStackFrame> stack =
Collections.synchronizedNavigableMap(new TreeMap<>());
private final TraceThread thread;
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceStackManager stackManager;
public DefaultStackRecorder(TraceThread thread, DefaultTraceRecorder recorder) {
this.thread = thread;
this.recorder = recorder;
this.trace = recorder.getTrace();
this.stackManager = trace.getStackManager();
}
@Override
public void offerStackFrame(TargetStackFrame frame) {
recordFrame(frame);
}
@Override
public void recordStack() {
doRecordStack(recorder.getSnap(), getPcsByLevel());
}
public void popStack() {
long snap = recorder.getSnap();
recorder.parTx.execute("Stack popped", () -> {
TraceStack traceStack = stackManager.getStack(thread, snap, true);
traceStack.setDepth(stackDepth(), false);
}, thread.getPath());
}
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
traceFrame.setProgramCounter(null, pc); // Not object-based, so span=null
}
protected Map<Integer, Address> getPcsByLevel() {
DebuggerMemoryMapper mm = recorder.getMemoryMapper();
synchronized (stack) {
return stack.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> {
return mm.targetToTrace(e.getValue().getProgramCounter());
}));
}
}
protected void doRecordStack(long snap, Map<Integer, Address> pcsByLevel) {
recorder.parTx.execute("Stack changed", () -> {
TraceStack traceStack = stackManager.getStack(thread, snap, true);
traceStack.setDepth(stackDepth(), false);
for (Map.Entry<Integer, Address> ent : pcsByLevel.entrySet()) {
doRecordFrame(traceStack, ent.getKey(), ent.getValue());
}
}, thread.getPath());
}
public void recordFrame(TargetStackFrame frame) {
long snap = recorder.getSnap();
Map<Integer, Address> pcsByLevel;
synchronized (stack) {
stack.put(getFrameLevel(frame), frame);
pcsByLevel = getPcsByLevel();
}
doRecordStack(snap, pcsByLevel);
}
protected int stackDepth() {
synchronized (stack) {
return stack.isEmpty() ? 0 : stack.lastKey() + 1;
}
}
@Override
public int getSuccessorFrameLevel(TargetObject successor) {
for (TargetObject p = successor; p != null; p = p.getParent()) {
if (p instanceof TargetStackFrame) {
if (!PathUtils.isIndex(p.getPath())) {
throw new AssertionError("Invalid path index " + p.getPath());
}
int index = Integer.decode(p.getIndex());
TargetStackFrame frame;
synchronized (stack) {
frame = stack.get(index);
}
if (!Objects.equals(p, frame)) {
// NB: This really ought to be an error but the dead frames ask for updates
// after they're dead - until we can figure that out...
//throw new AssertionError("Recorder stack has lost synchronization");
return -1;
}
return index;
}
}
return 0;
}
protected boolean checkStackFrameRemoved(TargetObject invalid) {
boolean removed;
synchronized (stack) {
removed = stack.values().remove(invalid);
}
if (removed) {
popStack();
}
return removed;
}
public Address pcFromStack() {
TargetStackFrame frame;
synchronized (stack) {
frame = stack.get(0);
}
if (frame == null) {
return null;
}
return frame.getProgramCounter();
}
@Override
public TraceStackFrame getTraceStackFrame(TraceThread thread, int level) {
TraceStack latest = stackManager.getLatestStack(thread, recorder.getSnap());
if (latest == null) {
return null;
}
return latest.getFrame(level, false);
}
@Override
public TargetStackFrame getTargetStackFrame(int frameLevel) {
synchronized (stack) {
return stack.get(frameLevel);
}
}
}

View file

@ -1,592 +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.plugin.core.debug.service.model;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.DefaultDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.debug.api.model.DebuggerRegisterMapper;
import ghidra.program.model.address.*;
import ghidra.program.model.data.Pointer;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.TimedMsg;
import ghidra.util.exception.DuplicateNameException;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultThreadRecorder implements ManagedThreadRecorder {
//private static final boolean LOG_STACK_TRACE = false;
private final TargetThread targetThread;
private final TraceThread traceThread;
protected final AbstractRecorderMemory threadMemory;
//private AbstractRecorderRegisterSet threadRegisters;
protected TargetBreakpointSpecContainer threadBreakpointContainer;
protected Map<Integer, Set<TargetRegisterBank>> regs = new HashMap<>();
protected Collection<TargetRegister> extraRegs;
protected TargetExecutionState state = TargetExecutionState.ALIVE;
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceObjectManager objectManager;
private final TraceMemoryManager memoryManager;
private DebuggerRegisterMapper regMapper;
private final DefaultDebuggerTargetTraceMapper mapper;
private final DefaultStackRecorder stackRecorder;
private final DefaultBreakpointRecorder breakpointRecorder;
protected static int getFrameLevel(TargetStackFrame frame) {
// TODO: A fair assumption? frames are elements with numeric base-10 indices
return Integer.decode(frame.getIndex());
}
public DefaultThreadRecorder(DefaultTraceRecorder recorder,
DefaultDebuggerTargetTraceMapper mapper, TargetThread targetThread,
TraceThread traceThread) {
this.recorder = recorder;
this.mapper = mapper;
this.trace = recorder.getTrace();
this.objectManager = recorder.objectManager;
this.targetThread = targetThread;
this.traceThread = traceThread;
this.memoryManager = trace.getMemoryManager();
//this.threadMemory = new RecorderComposedMemory(recorder.getProcessMemory());
this.threadMemory = recorder.getProcessMemory();
//this.threadRegisters = recorder.getThreadRegisters();
if (targetThread instanceof TargetExecutionStateful) {
TargetExecutionStateful stateful = (TargetExecutionStateful) targetThread;
state = stateful.getExecutionState();
}
this.stackRecorder = new DefaultStackRecorder(traceThread, recorder);
this.breakpointRecorder = new DefaultBreakpointRecorder(recorder);
}
protected synchronized CompletableFuture<Void> initRegMapper(
TargetRegisterContainer registers) {
/**
* TODO: At the moment, this assumes the recorded thread has one register container, or at
* least that all register banks in the thread use the same register container
* (descriptors). If this becomes a problem, then we'll need to keep a separate register
* mapper per register container. This would likely also require some notion of multiple
* languages in the mapper (seems an unlikely design choice). NOTE: In cases where a single
* process may (at least appear to) execute multiple languages, the model should strive to
* present the registers of the physical machine, as they are most likely uniform across the
* process, not those being emulated in the moment. In cases where an abstract machine is
* involved, it is probably more fitting to present separate containers (likely provided by
* separate models) than to present both the physical and abstract machine in the same
* target.
*
* <p>
* TODO: Should I formalize that only one register container is present in a recorded
* thread? This seems counter to the model's flexibility. Traces allow polyglot disassembly,
* but not polyglot register spaces.
*/
return objectManager.getRegMappers().get(registers).thenAccept(rm -> {
synchronized (this) {
regMapper = rm;
Language language = trace.getBaseLanguage();
extraRegs = new LinkedHashSet<>();
for (String rn : mapper.getExtraRegNames()) {
Register traceReg = language.getRegister(rn);
if (traceReg == null) {
Msg.error(this,
"Mapper's extra register '" + rn + "' is not in the language!");
continue;
}
TargetRegister targetReg = regMapper.traceToTarget(traceReg);
if (targetReg == null) {
Msg.error(this,
"Mapper's extra register '" + traceReg + "' is not mappable!");
continue;
}
extraRegs.add(targetReg);
}
}
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex);
return null;
});
}
@Override
public CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank bank) {
TargetRegisterContainer descs = bank.getDescriptions();
if (descs == null) {
Msg.error(this, "Cannot create mapper, yet: Descriptions is null.");
return AsyncUtils.nil();
}
return initRegMapper(descs).thenAccept(__ -> {
recorder.getListeners().invoke().registerBankMapped(recorder);
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex);
return null;
});
}
public CompletableFuture<Map<Register, RegisterValue>> captureThreadRegisters(
TraceThread thread, int frameLevel, Set<Register> registers) {
if (regMapper == null) {
return CompletableFuture.failedFuture(
new IllegalStateException("Have not found register descriptions for " + thread));
}
List<TargetRegister> tRegs = registers.stream()
.map(regMapper::traceToTarget)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (tRegs.size() < registers.size()) {
Msg.warn(this,
"All requested registers must be recognized by the model as registers");
}
if (registers.isEmpty()) {
return CompletableFuture.completedFuture(Map.of());
}
Set<TargetRegisterBank> banks = getTargetRegisterBank(thread, frameLevel);
if (banks == null) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Given thread and frame level does not have a live register bank"));
}
// NOTE: Cache update, if applicable, will cause recorder to write values to trace
AsyncFence fence = new AsyncFence();
Map<Register, RegisterValue> result = new HashMap<>();
for (TargetRegisterBank bank : banks) {
try {
fence.include(bank.readRegisters(tRegs)
.thenApply(regMapper::targetToTrace)
.thenAccept(br -> {
synchronized (result) {
result.putAll(br);
}
}));
}
catch (Exception e) {
fence.include(CompletableFuture.failedFuture(e));
}
}
return fence.ready().thenApply(__ -> result);
}
public Set<TargetRegisterBank> getTargetRegisterBank(TraceThread thread, int frameLevel) {
return regs.get(frameLevel);
}
@Override
public void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg, boolean removed) {
String name = reg.getIndex();
synchronized (this) {
if (regMapper != rm) {
return;
}
if (mapper.getExtraRegNames().contains(name)) {
if (removed) {
extraRegs.remove(reg);
}
else {
extraRegs.add(reg);
}
}
}
}
@Override
public void offerRegisters(TargetRegisterBank bank) {
if (regMapper == null) {
doFetchAndInitRegMapper(bank);
}
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
Set<TargetRegisterBank> set = regs.get(frameLevel);
if (set == null) {
set = new HashSet<>();
regs.put(frameLevel, set);
}
if (set.contains(bank)) {
Msg.warn(this, "Unexpected register bank replacement");
}
set.add(bank);
}
@Override
public void removeRegisters(TargetRegisterBank bank) {
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
Set<TargetRegisterBank> set = regs.get(frameLevel);
boolean remove = set.remove(bank);
if (!remove) {
Msg.warn(this, "Unexpected register bank upon removal");
}
}
@Override
public void offerThreadRegion(TargetMemoryRegion region) {
TargetMemory mem = region.getMemory();
threadMemory.addRegion(region, mem);
}
@Override
public void stateChanged(final TargetExecutionState newState) {
state = newState;
}
public void threadDestroyed() {
String path = getTargetThread().getJoinedPath(".");
long snap = recorder.getSnap();
recorder.parTx.execute("Thread " + path + " destroyed", () -> {
// TODO: Should it be key - 1
// Perhaps, since the thread should not exist
// But it could imply earlier destruction than actually observed
try {
getTraceThread().setDestructionSnap(snap);
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be shrinking
}
}, path);
}
@Override
public void recordRegisterValues(TargetRegisterBank bank, Map<String, byte[]> updates) {
synchronized (recorder) {
if (regMapper == null) {
doFetchAndInitRegMapper(bank);
}
}
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
if (frameLevel < 0) {
return;
}
long snap = recorder.getSnap();
String path = bank.getJoinedPath(".");
TimedMsg.debug(this, "Reg values changed: " + updates.keySet());
recorder.parTx.execute("Registers " + path + " changed", () -> {
TraceCodeManager codeManager = trace.getCodeManager();
TraceCodeSpace codeRegisterSpace =
codeManager.getCodeRegisterSpace(traceThread, false);
TraceDefinedDataView definedData =
codeRegisterSpace == null ? null : codeRegisterSpace.definedData();
TraceMemorySpace regSpace =
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, true);
for (Entry<String, byte[]> ent : updates.entrySet()) {
RegisterValue rv = regMapper.targetToTrace(ent.getKey(), ent.getValue());
if (rv == null) {
continue; // mapper does not know this register....
}
regSpace.setValue(snap, rv);
Register register = rv.getRegister();
if (definedData != null) {
TraceData td = definedData.getForRegister(snap, register);
if (td != null && td.getDataType() instanceof Pointer) {
Address addr = registerValueToTargetAddress(rv, ent.getValue());
readAlignedConditionally(ent.getKey(), addr); // NB: Reports errors
}
}
}
}, getTargetThread().getJoinedPath("."));
}
@Override
public void recordRegisterValue(TargetRegister targetRegister, byte[] value) {
TargetRegisterBank bank = (TargetRegisterBank) targetRegister.getParent();
synchronized (recorder) {
if (regMapper == null) {
doFetchAndInitRegMapper(bank);
}
}
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
long snap = recorder.getSnap();
String path = targetRegister.getJoinedPath(".");
//TimedMsg.info(this, "Register value changed: " + targetRegister);
recorder.parTx.execute("Register " + path + " changed", () -> {
TraceCodeManager codeManager = trace.getCodeManager();
TraceCodeSpace codeRegisterSpace =
codeManager.getCodeRegisterSpace(traceThread, false);
TraceDefinedDataView definedData =
codeRegisterSpace == null ? null : codeRegisterSpace.definedData();
TraceMemorySpace regSpace =
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, true);
String key = targetRegister.getName();
if (PathUtils.isIndex(key)) {
key = key.substring(1, key.length() - 1);
}
RegisterValue rv = regMapper.targetToTrace(key, value);
if (rv == null) {
return; // mapper does not know this register....
}
regSpace.setValue(snap, rv);
Register register = rv.getRegister();
if (definedData != null) {
TraceData td = definedData.getForRegister(snap, register);
if (td != null && td.getDataType() instanceof Pointer) {
Address addr = registerValueToTargetAddress(rv, value);
readAlignedConditionally(key, addr); // NB: Reports errors
}
}
}, getTargetThread().getJoinedPath("."));
}
@Override
public void invalidateRegisterValues(TargetRegisterBank bank) {
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
long snap = recorder.getSnap();
String path = bank.getJoinedPath(".");
recorder.parTx.execute("Registers invalidated: " + path, () -> {
TraceMemorySpace regSpace =
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, false);
if (regSpace == null) {
return;
}
AddressSpace as = regSpace.getAddressSpace();
regSpace.setState(snap, as.getMinAddress(), as.getMaxAddress(),
TraceMemoryState.UNKNOWN);
}, path);
}
public CompletableFuture<Void> writeThreadRegisters(int frameLevel,
Map<Register, RegisterValue> values) {
if (!regMapper.getRegistersOnTarget().containsAll(values.keySet())) {
throw new IllegalArgumentException(
"All given registers must be recognized by the target");
}
if (values.isEmpty()) {
return AsyncUtils.nil();
}
Map<String, byte[]> tVals = values.entrySet().stream().map(ent -> {
if (ent.getKey() != ent.getValue().getRegister()) {
throw new IllegalArgumentException("register name mismatch in value");
}
return regMapper.traceToTarget(ent.getValue());
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
Set<TargetRegisterBank> banks = getTargetRegisterBank(traceThread, frameLevel);
if (banks == null) {
throw new IllegalArgumentException(
"Given thread and frame level does not have a live register bank");
}
// NOTE: Model + recorder will cause applicable trace updates
AsyncFence fence = new AsyncFence();
for (TargetRegisterBank bank : banks) {
fence.include(bank.writeRegistersNamed(tVals).thenApply(__ -> null));
}
return fence.ready();
}
Address registerValueToTargetAddress(RegisterValue rv, byte[] value) {
Address traceAddress =
trace.getBaseLanguage().getDefaultSpace().getAddress(rv.getUnsignedValue().longValue());
return objectManager.getMemoryMapper().traceToTarget(traceAddress);
}
protected CompletableFuture<?> readAlignedConditionally(String name, Address targetAddress) {
if (targetAddress == null) {
return AsyncUtils.nil();
}
Address traceAddress = objectManager.getMemoryMapper().targetToTrace(targetAddress);
if (traceAddress == null) {
return AsyncUtils.nil();
}
if (!checkReadCondition(traceAddress)) {
return AsyncUtils.nil();
}
AddressRange targetRange = threadMemory.alignAndLimitToFloor(targetAddress, 1);
if (targetRange == null) {
return AsyncUtils.nil();
}
TimedMsg.debug(this,
" Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")");
// NOTE: Recorder takes data via memoryUpdated callback
// TODO: In that callback, sort out process memory from thread memory?
return threadMemory.readMemory(targetRange.getMinAddress(), (int) targetRange.getLength())
.exceptionally(ex -> {
Msg.error(this, "Could not read memory at " + name, ex);
return null;
});
}
protected boolean checkReadCondition(Address traceAddress) {
/**
* TODO: This heuristic doesn't really belong here, but I have to implement it here so that
* it doesn't "override" the listing's implementation. Once watches are implemented, we
* should be able to drop this garbage.
*/
TraceMemoryRegion region =
memoryManager.getRegionContaining(recorder.getSnap(), traceAddress);
if (region == null) {
return false;
}
if (region.isWrite()) {
return true;
}
Entry<TraceAddressSnapRange, TraceMemoryState> ent =
memoryManager.getMostRecentStateEntry(recorder.getSnap(), traceAddress);
if (ent == null) {
return true;
}
if (ent.getValue() == TraceMemoryState.KNOWN) {
return false;
}
return true;
}
@Override
public TargetThread getTargetThread() {
return targetThread;
}
@Override
public TraceThread getTraceThread() {
return traceThread;
}
@Override
public long getSnap() {
return recorder.getSnap();
}
@Override
public Trace getTrace() {
return recorder.getTrace();
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return recorder.objectManager.getMemoryMapper();
}
@Override
public ManagedStackRecorder getStackRecorder() {
return stackRecorder;
}
@Override
public ManagedBreakpointRecorder getBreakpointRecorder() {
return breakpointRecorder;
}
/**
* Inform the recorder the given object is no longer valid
*
* @param invalid the invalidated object
* @return true if this recorder should be invalidated, too
*/
// UNUSED?
@Override
public synchronized boolean objectRemoved(TargetObject invalid) {
if (checkThreadRemoved(invalid)) {
return true;
}
if (stackRecorder.checkStackFrameRemoved(invalid)) {
return false;
}
if (threadMemory.removeRegion(invalid)) {
return false;
}
Msg.trace(this, "Ignored removed object: " + invalid);
return false;
}
protected boolean checkThreadRemoved(TargetObject invalid) {
if (getTargetThread() == invalid) {
threadDestroyed();
return true;
}
return false;
}
public DebuggerRegisterMapper getRegisterMapper() {
return regMapper;
}
/*
public CompletableFuture<Void> updateRegsMem(TargetMemoryRegion limit) {
TargetRegisterBank bank;
TargetRegister pc;
TargetRegister sp;
Set<TargetRegister> toRead = new LinkedHashSet<>();
synchronized (recorder) {
if (regMapper == null) {
return AsyncUtils.nil();
}
bank = regs.get(0);
pc = pcReg;
sp = spReg;
toRead.addAll(extraRegs);
toRead.add(sp);
toRead.add(pc);
}
if (bank == null || pc == null || sp == null) {
return AsyncUtils.nil();
}
System.err.println("URM:" + getTargetThread());
TimedMsg.info(this, "Reading " + toRead + " of " + getTargetThread());
return bank.readRegisters(toRead).thenCompose(vals -> {
synchronized (recorder) {
if (memoryManager == null) {
return AsyncUtils.nil();
}
}
if (threadMemory == null) {
return AsyncUtils.nil();
}
AsyncFence fence = new AsyncFence();
Address pcTargetAddr = stackRecorder.pcFromStack();
if (pcTargetAddr == null) {
pcTargetAddr = registerValueToTargetAddress(pcReg, vals.get(pcReg.getIndex()));
}
fence.include(readAlignedConditionally("PC", pcTargetAddr, limit));
Address spTargetAddr = registerValueToTargetAddress(spReg, vals.get(spReg.getIndex()));
fence.include(readAlignedConditionally("SP", spTargetAddr, limit));
return fence.ready();
}).exceptionally(ex -> {
if (LOG_STACK_TRACE) {
Msg.error(this, "Could not read registers", ex);
}
else {
Msg.error(this, "Could not read registers");
}
return null;
});
}
*/
}

View file

@ -1,66 +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.plugin.core.debug.service.model;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultTimeRecorder {
private DefaultTraceRecorder recorder;
private Trace trace;
private TraceSnapshot snapshot = null;
public DefaultTimeRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
}
public TraceSnapshot getSnapshot() {
return snapshot;
}
public long getSnap() {
return snapshot.getKey();
}
protected synchronized void doAdvanceSnap(String description, TraceThread eventThread) {
snapshot = trace.getTimeManager().createSnapshot(description);
snapshot.setEventThread(eventThread);
}
public TraceSnapshot forceSnapshot() {
createSnapshot("User-forced snapshot", null, null);
return snapshot;
}
public void createSnapshot(String description, TraceThread eventThread,
RecorderPermanentTransaction tid) {
if (tid != null) {
doAdvanceSnap(description, eventThread);
recorder.getListeners().invoke().snapAdvanced(recorder, getSnap());
return;
}
// NB. The also serves as the snap counter, so it must be on the service thread
try (RecorderPermanentTransaction tid2 =
RecorderPermanentTransaction.start(trace, description)) {
doAdvanceSnap(description, eventThread);
}
recorder.getListeners().invoke().snapAdvanced(recorder, getSnap());
}
}

View file

@ -1,648 +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.plugin.core.debug.service.model;
import java.util.*;
import java.util.concurrent.*;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.mapping.DefaultDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.app.plugin.core.debug.service.model.record.DataTypeRecorder;
import ghidra.app.plugin.core.debug.service.model.record.SymbolRecorder;
import ghidra.async.AsyncLazyValue;
import ghidra.async.AsyncUtils;
import ghidra.dbg.agent.AbstractDebuggerObjectModel;
import ghidra.dbg.error.DebuggerModelAccessException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.model.*;
import ghidra.framework.plugintool.PluginTool;
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.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public class DefaultTraceRecorder implements TraceRecorder {
static final int POOL_SIZE = Math.min(16, Runtime.getRuntime().availableProcessors());
protected final PluginTool tool;
protected final TargetObject target;
protected final Trace trace;
final RecorderThreadMap threadMap = new RecorderThreadMap();
TraceObjectManager objectManager;
DefaultBreakpointRecorder breakpointRecorder;
DataTypeRecorder datatypeRecorder;
DefaultMemoryRecorder memoryRecorder;
DefaultModuleRecorder moduleRecorder;
DefaultProcessRecorder processRecorder;
SymbolRecorder symbolRecorder;
DefaultTimeRecorder timeRecorder;
//protected final PermanentTransactionExecutor seqTx;
protected final PermanentTransactionExecutor parTx;
protected final Executor privateQueue = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder().namingPattern("DTR-EventQueue-%d").build());
protected final AsyncLazyValue<Void> lazyInit = new AsyncLazyValue<>(this::doInit);
private boolean valid = true;
public DefaultTraceRecorder(PluginTool tool, Trace trace, TargetObject target,
DefaultDebuggerTargetTraceMapper mapper) {
trace.addConsumer(this);
this.tool = tool;
this.trace = trace;
this.target = target;
//seqTx = new PermanentTransactionExecutor(
// trace, "TraceRecorder(seq): " + target.getJoinedPath("."), 1, 100);
parTx = new PermanentTransactionExecutor(
trace, "TraceRecorder(par): " + target.getJoinedPath("."), POOL_SIZE, 100);
this.processRecorder = new DefaultProcessRecorder(this);
this.breakpointRecorder = new DefaultBreakpointRecorder(this);
this.datatypeRecorder = new DataTypeRecorder(this);
this.memoryRecorder = new DefaultMemoryRecorder(this);
this.moduleRecorder = new DefaultModuleRecorder(this);
this.symbolRecorder = new SymbolRecorder(this);
this.timeRecorder = new DefaultTimeRecorder(this);
this.objectManager = new TraceObjectManager(target, mapper, this);
}
/*---------------- OBJECT MANAGER METHODS -------------------*/
@Override
public TargetObject getTargetObject(TraceObject obj) {
return null;
}
@Override
public TargetObject getTargetObject(TraceObjectKeyPath path) {
return target.getModel().getModelObject(path.getKeyList());
}
@Override
public TraceObject getTraceObject(TargetObject obj) {
return null;
}
@Override
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
return objectManager.getTargetBreakpoint(bpt);
}
@Override
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
return objectManager.getTargetMemoryRegion(region);
}
@Override
public TargetModule getTargetModule(TraceModule module) {
return objectManager.getTargetModule(module);
}
@Override
public TargetSection getTargetSection(TraceSection section) {
return objectManager.getTargetSection(section);
}
@Override
public List<TargetBreakpointSpecContainer> collectBreakpointContainers(TargetThread thread) {
List<TargetBreakpointSpecContainer> result = new ArrayList<>();
objectManager.onBreakpointContainers(thread, result::add);
return result;
}
@Override
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
return objectManager.collectBreakpoints(thread);
}
@Override
public Set<TraceBreakpointKind> getSupportedBreakpointKinds() {
Set<TargetBreakpointKind> tKinds = new HashSet<>();
objectManager.onBreakpointContainers(null, cont -> {
tKinds.addAll(cont.getSupportedBreakpointKinds());
});
return TraceRecorder.targetToTraceBreakpointKinds(tKinds);
}
/*---------------- RECORDER ACCESS METHODS -------------------*/
@Override
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
return breakpointRecorder.getTraceBreakpoint(bpt);
}
@Override
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
return memoryRecorder.getTraceMemoryRegion(region);
}
@Override
public TraceModule getTraceModule(TargetModule module) {
return moduleRecorder.getTraceModule(module);
}
@Override
public TraceSection getTraceSection(TargetSection section) {
return moduleRecorder.getTraceSection(section);
}
/*---------------- BY-THREAD OBJECT MANAGER METHODS -------------------*/
public ManagedThreadRecorder computeIfAbsent(TargetThread thread) {
AbstractDebuggerObjectModel model = (AbstractDebuggerObjectModel) thread.getModel();
synchronized (model.lock) {
if (!threadMap.byTargetThread.containsKey(thread)) {
if (objectManager.hasObject(thread)) {
createTraceThread(thread);
}
}
return threadMap.get(thread);
}
}
public TraceThread createTraceThread(TargetThread thread) {
//System.err.println("createTraceThread " + thread);
String path = PathUtils.toString(thread.getPath());
// NB. Keep this on service thread, since thread creation must precede any dependent
try (RecorderPermanentTransaction tid =
RecorderPermanentTransaction.start(trace, path + " created")) {
// Note, if THREAD_CREATED is emitted, it will adjust the creation snap
TraceThread tthread =
trace.getThreadManager().createThread(path, thread.getShortDisplay(), getSnap());
threadMap.put(
new DefaultThreadRecorder(this, objectManager.getMapper(), thread, tthread));
return tthread;
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be a new thread in model
}
}
@Override
public TargetThread getTargetThread(TraceThread thread) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
return rec == null ? null : rec.getTargetThread();
}
@Override
public TargetExecutionState getTargetThreadState(TargetThread thread) {
DefaultThreadRecorder rec = (DefaultThreadRecorder) getThreadRecorder(thread);
return rec == null ? null : rec.state;
}
@Override
public TargetExecutionState getTargetThreadState(TraceThread thread) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
return rec == null ? null : rec.state;
}
@Override
public Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
return rec.getTargetRegisterBank(thread, frameLevel);
}
@Override
public TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
if (rec == null) {
return null;
}
return rec.getStackRecorder().getTargetStackFrame(frameLevel);
}
/*---------------- BY-THREAD RECORDER ACCESS METHODS -------------------*/
@Override
public TraceThread getTraceThread(TargetThread thread) {
ManagedThreadRecorder rec = getThreadRecorder(thread);
return rec == null ? null : rec.getTraceThread();
}
@Override
public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
ManagedThreadRecorder rec = getThreadRecorderForSuccessor(successor);
return rec == null ? null : rec.getTraceThread();
}
@Override
public TraceStackFrame getTraceStackFrame(TargetStackFrame frame) {
// Not the most efficient, but only used in testing.
return getTraceStackFrameForSuccessor(frame);
}
@Override
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
ManagedThreadRecorder rec = getThreadRecorderForSuccessor(successor);
if (rec == null) {
return null;
}
ManagedStackRecorder stackRecorder = rec.getStackRecorder();
int level = stackRecorder.getSuccessorFrameLevel(successor);
return stackRecorder.getTraceStackFrame(rec.getTraceThread(), level);
}
/*---------------- CAPTURE METHODS -------------------*/
@Override
public CompletableFuture<Void> readMemoryBlocks(AddressSetView set, TaskMonitor monitor) {
if (set.isEmpty()) {
return AsyncUtils.nil();
}
return memoryRecorder.captureProcessMemory(set, monitor);
}
@Override
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor) {
if (!valid) {
return AsyncUtils.nil();
}
return datatypeRecorder.captureDataTypes(namespace, monitor);
}
@Override
public CompletableFuture<Void> captureDataTypes(TraceModule module, TaskMonitor monitor) {
TargetModule targetModule = getTargetModule(module);
if (targetModule == null) {
Msg.error(this, "Module " + module + " is not loaded");
return AsyncUtils.nil();
}
return datatypeRecorder.captureDataTypes(targetModule, monitor);
}
@Override
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
TaskMonitor monitor) {
if (!valid) {
return AsyncUtils.nil();
}
return symbolRecorder.captureSymbols(namespace, monitor);
}
@Override
public CompletableFuture<Void> captureSymbols(TraceModule module, TaskMonitor monitor) {
TargetModule targetModule = getTargetModule(module);
if (targetModule == null) {
Msg.error(this, "Module " + module + " is not loaded");
return AsyncUtils.nil();
}
return symbolRecorder.captureSymbols(targetModule, monitor);
}
@Override
public CompletableFuture<Void> captureThreadRegisters(
TracePlatform platform, TraceThread thread, int frameLevel, Set<Register> registers) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
return rec.captureThreadRegisters(thread, frameLevel, registers).thenApply(__ -> null);
}
/*---------------- SNAPSHOT METHODS -------------------*/
@Override
public CompletableFuture<Void> init() {
return lazyInit.request();
}
protected CompletableFuture<Void> doInit() {
timeRecorder.createSnapshot(
"Started recording" + PathUtils.toString(target.getPath()) + " in " + target.getModel(),
null, null);
return objectManager.init();
}
@Override
public long getSnap() {
return timeRecorder.getSnap();
}
@Override
public TraceSnapshot forceSnapshot() {
return timeRecorder.forceSnapshot();
}
@Override
public boolean isRecording() {
return valid;
}
@Override
public void stopRecording() {
invalidate();
getListeners().invoke().recordingStopped(this);
}
protected void invalidate() {
objectManager.disposeModelListeners();
synchronized (this) {
if (!valid) {
return;
}
valid = false;
trace.release(this);
}
}
/*---------------- FOCUS-SUPPORT METHODS -------------------*/
protected TargetObject curFocus;
@Override
public boolean isSupportsFocus() {
return findFocusScope() != null;
}
@Override
public boolean isSupportsActivation() {
return findActiveScope() != null;
}
// NOTE: This may require the scope to be an ancestor of the target
// That should be fine
protected TargetFocusScope findFocusScope() {
List<String> path = target.getModel()
.getRootSchema()
.searchForSuitable(TargetFocusScope.class, target.getPath());
if (path == null) {
return null;
}
return (TargetFocusScope) target.getModel().getModelObject(path);
}
protected TargetActiveScope findActiveScope() {
List<String> path = target.getModel()
.getRootSchema()
.searchForSuitable(TargetActiveScope.class, target.getPath());
if (path == null) {
return null;
}
return (TargetActiveScope) target.getModel().getModelObject(path);
}
@Override
public TargetObject getFocus() {
if (curFocus == null) {
TargetFocusScope focusScope = findFocusScope();
if (focusScope == null) {
return null;
}
TargetObject focus = focusScope.getFocus();
if (focus == null || !PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
return null;
}
curFocus = focus;
}
return curFocus;
}
public void setCurrentFocus(TargetObject focused) {
curFocus = focused;
}
@Override
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
if (!isSupportsFocus()) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("Target does not support focus"));
}
if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested focus path is not a successor of the target"));
}
TargetFocusScope focusScope = findFocusScope();
if (!PathUtils.isAncestor(focusScope.getPath(), focus.getPath())) {
// This should be rare, if not forbidden
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested focus path is not a successor of the focus scope"));
}
return focusScope.requestFocus(focus).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not focus " + focus + ": " + ex.getMessage();
tool.setStatusInfo(msg);
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, "Could not focus " + focus, ex);
}
return false;
});
}
@Override
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
if (!isSupportsActivation()) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("Target does not support activation"));
}
if (!PathUtils.isAncestor(getTarget().getPath(), active.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested activation path is not a successor of the target"));
}
TargetActiveScope activeScope = findActiveScope();
if (!PathUtils.isAncestor(activeScope.getPath(), active.getPath())) {
// This should be rare, if not forbidden
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested activation path is not a successor of the focus scope"));
}
return activeScope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not activate " + active + ": " + ex.getMessage();
tool.setStatusInfo(msg);
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, "Could not activate " + active, ex);
}
return false;
});
}
/*---------------- ACCESSOR METHODS -------------------*/
@Override
public TargetObject getTarget() {
return target;
}
@Override
public Trace getTrace() {
return trace;
}
public RecorderThreadMap getThreadMap() {
return threadMap;
}
public Set<TargetThread> getThreadsView() {
return getThreadMap().byTargetThread.keySet();
}
// UNUSED?
@Override
public Set<TargetThread> getLiveTargetThreads() {
return getThreadsView();
}
public DefaultThreadRecorder getThreadRecorder(TraceThread thread) {
return (DefaultThreadRecorder) getThreadMap().get(thread);
}
public ManagedThreadRecorder getThreadRecorder(TargetThread thread) {
return computeIfAbsent(thread);
}
public ManagedThreadRecorder getThreadRecorderForSuccessor(TargetObject successor) {
TargetObject obj = successor;
while (obj != null && !(obj instanceof TargetThread)) {
obj = obj.getParent();
}
if (obj == null) {
return null;
}
return computeIfAbsent((TargetThread) obj);
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return objectManager.getMemoryMapper();
}
@Override
public DebuggerRegisterMapper getRegisterMapper(TraceThread thread) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
if (rec == null) {
return null;
}
return rec.getRegisterMapper();
}
//public AbstractRecorderRegisterSet getThreadRegisters() {
// return objectManager.getThreadRegisters();
//}
/*---------------- LISTENER METHODS -------------------*/
public ListenerSet<TraceRecorderListener> getListeners() {
return objectManager.getListeners();
}
@Override
public void addListener(TraceRecorderListener l) {
getListeners().add(l);
}
@Override
public void removeListener(TraceRecorderListener l) {
getListeners().remove(l);
}
/*---------------- DELEGATED METHODS -------------------*/
public AbstractRecorderMemory getProcessMemory() {
return processRecorder.getProcessMemory();
}
@Override
public AddressSetView getAccessibleMemory() {
return processRecorder.getAccessibleProcessMemory();
}
@Override
public CompletableFuture<byte[]> readMemory(Address start, int length) {
return processRecorder.readProcessMemory(start, length);
}
@Override
public CompletableFuture<Void> writeMemory(Address start, byte[] data) {
return processRecorder.writeProcessMemory(start, data);
}
@Override
public Register isRegisterOnTarget(TracePlatform platform, TraceThread thread, int frameLevel,
Register register) {
// NOTE: This pays no heed to frameLevel, but caller does require level==0 for now.
Collection<Register> onTarget = getRegisterMapper(thread).getRegistersOnTarget();
for (; register != null; register = register.getParentRegister()) {
if (onTarget.contains(register)) {
return register;
}
}
return null;
}
@Override
public CompletableFuture<Void> writeThreadRegisters(TracePlatform platform, TraceThread thread,
int frameLevel, Map<Register, RegisterValue> values) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
return (rec == null) ? null : rec.writeThreadRegisters(frameLevel, values);
}
public TraceSnapshot getSnapshot() {
return timeRecorder.getSnapshot();
}
public void createSnapshot(String description, TraceThread eventThread,
RecorderPermanentTransaction tid) {
timeRecorder.createSnapshot(description, eventThread, tid);
}
@Override
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
return true;
}
@Override
public boolean isRegisterBankAccessible(TraceThread thread, int frameLevel) {
return true;
}
@Override
public CompletableFuture<Void> flushTransactions() {
return CompletableFuture.runAsync(() -> {
}, privateQueue).thenCompose(__ -> {
return objectManager.flushEvents();
}).thenCompose(__ -> {
return parTx.flush();
});
}
}

View file

@ -1,101 +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.plugin.core.debug.service.model;
import java.util.HashMap;
import java.util.concurrent.*;
import java.util.stream.Stream;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectException;
import ghidra.util.Msg;
import ghidra.util.exception.ClosedException;
@Deprecated(forRemoval = true, since = "11.3")
public class PermanentTransactionExecutor {
private final TransactionCoalescer txc;
private final ExecutorService[] threads;
private final DomainObject obj;
public PermanentTransactionExecutor(DomainObject obj, String name, int threadCount,
int delayMs) {
this.obj = obj;
txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs);
this.threads = new ExecutorService[threadCount];
for (int i = 0; i < threadCount; i++) {
ThreadFactory factory =
new BasicThreadFactory.Builder().namingPattern(name + "thread-" + i + "-%d")
.build();
threads[i] = Executors.newSingleThreadExecutor(factory);
}
}
public void shutdownNow() {
for (ExecutorService t : threads) {
t.shutdownNow();
}
}
/**
* This hash is borrowed from {@link HashMap}, except for the power-of-two masking, since I
* don't want to force the thread count to be a power of two (though it probably is). In the
* grand scheme of things, one division operation is small per transaction.
*
* @param sel the basis for selecting a thread
* @return the selected executor
*/
protected Executor selectThread(Object sel) {
if (sel == null) {
return threads[0];
}
int h = sel.hashCode();
return threads[Integer.remainderUnsigned(h ^ (h >>> 16), threads.length)];
}
public CompletableFuture<Void> execute(String description, Runnable runnable, Object sel) {
return CompletableFuture.runAsync(() -> {
if (obj.isClosed()) {
return;
}
try (CoalescedTx tx = txc.start(description)) {
runnable.run();
}
catch (DomainObjectException e) {
if (e.getCause() instanceof ClosedException) {
Msg.info(this, obj + " is closed. Shutting down transaction executor.");
shutdownNow();
}
}
}, selectThread(sel)).exceptionally(e -> {
Msg.error(this, "Trouble recording " + description, e);
return null;
});
}
public CompletableFuture<Void> flush() {
Runnable nop = () -> {
};
return CompletableFuture.allOf(Stream.of(threads)
.map(t -> CompletableFuture.runAsync(nop, t))
.toArray(CompletableFuture[]::new));
}
}

View file

@ -1,108 +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.plugin.core.debug.service.model;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
import ghidra.async.AsyncFence;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
@Deprecated(forRemoval = true, since = "11.3")
public class RecorderBreakpointLocationResolver {
// TODO: I'm not sure this class really offers anything anymore
private DefaultTraceRecorder recorder;
private final TargetBreakpointLocation bpt;
private final TargetBreakpointSpec spec;
private boolean affectsProcess = false;
private final Set<TraceThread> threadsAffected = new LinkedHashSet<>();
public RecorderBreakpointLocationResolver(DefaultTraceRecorder recorder,
TargetBreakpointLocation bpt) {
this.recorder = recorder;
this.bpt = bpt;
this.spec = bpt.getSpecification();
}
// TODO: This is a stopgap, since Location.getAffects is removed
// Do we really need to worry about per-thread breakpoints?
static Collection<TargetObject> getAffects(TargetBreakpointLocation bpt) {
TargetObject findProc = bpt;
while (!(findProc instanceof TargetProcess)) {
findProc = findProc.getParent();
}
return List.of(findProc);
}
private CompletableFuture<Void> resolve(TargetObject obj) {
AsyncFence fence = new AsyncFence();
if (obj.equals(recorder.getTarget())) {
affectsProcess = true;
}
else {
fence.include(resolveThread(obj));
}
return fence.ready();
}
// TODO: If affects is empty/null, also try to default to the containing process
private CompletableFuture<Void> resolveThread(TargetObject ref) {
return DebugModelConventions.findThread(ref).thenAccept(thread -> {
if (thread == null) {
Msg.error(this,
"Could not find process or thread from breakpoint-affected object: " + ref);
return;
}
if (!ref.equals(thread)) {
Msg.warn(this, "Effective breakpoint should apply to process or threads. Got " +
ref + ". Resolved to " + thread);
return;
}
if (!PathUtils.isAncestor(recorder.getTarget().getPath(), thread.getPath())) {
/**
* Perfectly normal if the breakpoint container is outside the process container.
* Don't record such in this trace, though.
*/
return;
}
ManagedThreadRecorder rec = recorder.getThreadRecorder(thread); //listenerForRecord.getOrCreateThreadRecorder(thread);
synchronized (threadsAffected) {
threadsAffected.add(rec.getTraceThread());
}
}).exceptionally(ex -> {
Msg.error(this, "Error resolving thread from breakpoint-affected object: " + ref);
return null;
});
}
public void updateBreakpoint(TargetObject containerParent, TargetBreakpointLocation loc) {
resolve(containerParent).thenAccept(__ -> {
if (affectsProcess || !threadsAffected.isEmpty()) {
recorder.breakpointRecorder.recordBreakpoint(loc, threadsAffected);
}
}).exceptionally(ex -> {
Msg.error(this, "Could record target breakpoint: " + loc, ex);
return null;
});
}
}

View file

@ -1,42 +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.plugin.core.debug.service.model;
import db.Transaction;
import ghidra.framework.model.DomainObject;
@Deprecated(forRemoval = true, since = "11.3")
public class RecorderPermanentTransaction implements AutoCloseable {
public static RecorderPermanentTransaction start(DomainObject obj, String description) {
Transaction tx = obj.openTransaction(description);
return new RecorderPermanentTransaction(obj, tx);
}
private final DomainObject obj;
private final Transaction tx;
public RecorderPermanentTransaction(DomainObject obj, Transaction tx) {
this.obj = obj;
this.tx = tx;
}
@Override
public void close() {
tx.close();
obj.clearUndo();
}
}

View file

@ -1,142 +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.plugin.core.debug.service.model;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
import ghidra.dbg.target.*;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.program.model.address.*;
@Deprecated(forRemoval = true, since = "11.3")
public class RecorderSimpleMemory implements AbstractRecorderMemory {
private static final int BLOCK_SIZE = 4096;
private static final long BLOCK_MASK = -1L << 12;
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
protected TargetMemory memory;
public RecorderSimpleMemory() {
}
@Override
public void addMemory(TargetMemory memory) {
synchronized (this) {
if (this.memory == null) {
this.memory = memory;
}
}
}
@Override
public void addRegion(TargetMemoryRegion region, TargetMemory memory) {
synchronized (this) {
if (this.memory == null) {
this.memory = memory;
}
byMin.put(region.getRange().getMinAddress(), region);
}
}
@Override
public void removeMemory(TargetMemory invalid) {
synchronized (this) {
if (this.memory == invalid) {
this.memory = null;
}
}
}
@Override
public boolean removeRegion(TargetObject invalid) {
if (!(invalid instanceof TargetMemoryRegion)) {
return false;
}
synchronized (this) {
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
byMin.remove(invRegion.getRange().getMinAddress());
return true;
}
}
@Override
public CompletableFuture<byte[]> readMemory(Address address, int length) {
synchronized (this) {
if (memory != null) {
return memory.readMemory(address, length);
}
return CompletableFuture.completedFuture(new byte[0]);
}
}
@Override
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
synchronized (this) {
if (memory != null) {
return memory.writeMemory(address, data);
}
throw new IllegalArgumentException("read starts outside any address space");
}
}
@Override
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) {
synchronized (this) {
// TODO: Might accomplish by using listeners and tracking the accessible set
AddressSet accessible = new AddressSet();
if (memMapper != null) {
for (Entry<Address, TargetMemoryRegion> ent : byMin.entrySet()) {
AddressRange traceRange =
memMapper.targetToTraceTruncated(ent.getValue().getRange());
if (traceRange != null) {
accessible.add(traceRange);
}
}
}
return accessible;
}
}
@Override
public AddressRange alignAndLimitToFloor(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
return null;
}
return align(address, length).intersect(floor.getValue().getRange());
}
protected Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
synchronized (this) {
return byMin.floorEntry(address);
}
}
protected AddressRange align(Address address, int length) {
AddressSpace space = address.getAddressSpace();
long offset = address.getOffset();
Address start = space.getAddress(offset & BLOCK_MASK);
Address end = space.getAddress(((offset + length - 1) & BLOCK_MASK) + BLOCK_SIZE - 1);
return new AddressRangeImpl(start, end);
}
}

View file

@ -1,73 +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.plugin.core.debug.service.model;
import java.util.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetThread;
import ghidra.trace.model.thread.TraceThread;
@Deprecated(forRemoval = true, since = "11.3")
public class RecorderThreadMap {
protected final NavigableSet<Integer> observedThreadPathLengths = new TreeSet<>();
protected final Map<TargetThread, ManagedThreadRecorder> byTargetThread = new HashMap<>();
protected final Map<TraceThread, ManagedThreadRecorder> byTraceThread = new HashMap<>();
public void put(ManagedThreadRecorder rec) {
observedThreadPathLengths.add(rec.getTargetThread().getPath().size());
byTargetThread.put(rec.getTargetThread(), rec);
byTraceThread.put(rec.getTraceThread(), rec);
}
/*
public ManagedThreadRecorder getForSuccessor(TargetObject successor) {
while (successor != null) {
ManagedThreadRecorder rec = byTargetThread.get(successor);
if (rec != null) {
return rec;
}
successor = successor.getParent();
}
return null;
}
*/
public ManagedThreadRecorder get(TargetThread thread) {
return byTargetThread.get(thread);
}
public ManagedThreadRecorder get(TargetObject maybeThread) {
return byTargetThread.get(maybeThread);
}
public ManagedThreadRecorder get(TraceThread thread) {
return byTraceThread.get(thread);
}
public void remove(ManagedThreadRecorder rec) {
ManagedThreadRecorder rByTarget = byTargetThread.remove(rec.getTargetThread());
ManagedThreadRecorder rByTrace = byTraceThread.remove(rec.getTraceThread());
assert rec == rByTarget;
assert rec == rByTrace;
}
public Collection<ManagedThreadRecorder> recorders() {
return byTargetThread.values();
}
}

View file

@ -1,269 +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.plugin.core.debug.service.model;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
import ghidra.async.AsyncUtils;
import ghidra.dbg.*;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.util.Msg;
import ghidra.util.TimedMsg;
import ghidra.util.datastruct.PrivatelyQueuedListener;
@Deprecated(forRemoval = true, since = "11.3")
public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
private final DefaultTraceRecorder recorder;
private final TargetObject target;
private final Trace trace;
private final TraceMemoryManager memoryManager;
private boolean valid = true;
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
protected final PrivatelyQueuedListener<DebuggerModelListener> queue;
private boolean ignoreInvalidation = false;
public TraceEventListener(TraceObjectManager collection) {
super(MethodHandles.lookup());
this.recorder = collection.getRecorder();
this.queue = new PrivatelyQueuedListener<>(DebuggerModelListener.class,
recorder.privateQueue, reorderer);
this.target = recorder.getTarget();
this.trace = recorder.getTrace();
this.memoryManager = trace.getMemoryManager();
}
public CompletableFuture<Void> init() {
DebuggerObjectModel model = target.getModel();
model.addModelListener(queue.in, true);
return AsyncUtils.nil();
}
private boolean successor(TargetObject ref) {
return PathUtils.isAncestor(target.getPath(), ref.getPath());
}
private boolean anyRef(Collection<Object> parameters) {
for (Object p : parameters) {
if (!(p instanceof TargetObject)) {
continue;
}
return true;
}
return false;
}
private boolean anySuccessor(Collection<Object> parameters) {
for (Object p : parameters) {
if (!(p instanceof TargetObject)) {
continue;
}
TargetObject ref = (TargetObject) p;
if (!successor(ref)) {
continue;
}
return true;
}
return false;
}
private boolean eventApplies(TargetObject eventThread, TargetEventType type,
List<Object> parameters) {
if (eventThread != null) {
return successor(eventThread);
}
if (anyRef(parameters)) {
return anySuccessor(parameters);
}
return true; // Some session-wide event, I suppose
}
@Override
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
String description, List<Object> parameters) {
if (!valid) {
return;
}
TimedMsg.debug(this, "Event: " + type + " thread=" + eventThread + " description=" +
description + " params=" + parameters);
// Just use this to step the snaps. Creation/destruction still handled in add/remove
if (eventThread == null) {
if (!type.equals(TargetEventType.PROCESS_CREATED)) {
Msg.error(this, "Null eventThread for " + type);
}
return;
}
if (!eventApplies(eventThread, type, parameters)) {
return;
}
if (type == TargetEventType.RUNNING) {
/**
* Do not permit the current snapshot to be invalidated on account of the target
* running. When the STOP occurs, a new (completely UNKNOWN) snapshot is generated.
*/
ignoreInvalidation = true;
return;
/**
* TODO: Perhaps some configuration for this later. It's kind of interesting to record
* the RUNNING event time, but it gets pedantic when these exist between steps.
*/
}
ManagedThreadRecorder rec = recorder.getThreadRecorder(eventThread);
recorder.createSnapshot(description, rec == null ? null : rec.getTraceThread(), null);
ignoreInvalidation = false;
}
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
public void executionStateChanged(TargetObject stateful, TargetExecutionState state) {
if (!valid) {
return;
}
TimedMsg.debug(this, "State " + state + " for " + stateful);
TargetObject x = recorder.objectManager.findThreadOrProcess(stateful);
if (x != null) {
if (x == target && state == TargetExecutionState.TERMINATED) {
recorder.stopRecording();
return;
}
ManagedThreadRecorder rec = null;
if (x instanceof TargetThread) {
rec = recorder.getThreadRecorder((TargetThread) x);
}
if (rec != null) {
rec.stateChanged(state);
}
// Else we'll discover it and sync state later
}
}
@Override
public void invalidateCacheRequested(TargetObject object) {
if (!valid) {
return;
}
if (ignoreInvalidation) {
return;
}
if (object instanceof TargetRegisterBank) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(object);
if (rec != null) {
rec.invalidateRegisterValues((TargetRegisterBank) object);
}
}
if (object instanceof TargetMemory) {
long snap = recorder.getSnap();
String path = object.getJoinedPath(".");
recorder.parTx.execute("Memory invalidated: " + path, () -> {
AddressSet set = trace.getBaseAddressFactory().getAddressSet();
memoryManager.setState(snap, set, TraceMemoryState.UNKNOWN);
}, path);
}
}
@Override
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
if (!valid) {
return;
}
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(bank);
if (rec != null) {
rec.recordRegisterValues((TargetRegisterBank) bank, updates);
}
}
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
if (!valid) {
return;
}
synchronized (recorder) {
if (recorder.getMemoryMapper() == null) {
Msg.warn(this, "Received memory write before a region has been added");
return;
}
}
Address traceAddr = recorder.getMemoryMapper().targetToTrace(address);
long snap = recorder.getSnap();
TimedMsg.debug(this, "Memory updated: " + address + " (" + data.length + ")");
String path = memory.getJoinedPath(".");
recorder.parTx.execute("Memory observed: " + path, () -> {
memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data));
}, path); // sel could be rand()...
}
@Override
public void memoryReadError(TargetObject memory, AddressRange range,
DebuggerMemoryAccessException e) {
if (!valid) {
return;
}
Msg.error(this, "Error reading range " + range, e);
Address traceMin = recorder.getMemoryMapper().targetToTrace(range.getMinAddress());
long snap = recorder.getSnap();
String path = memory.getJoinedPath(".");
recorder.parTx.execute("Memory read error: " + path, () -> {
memoryManager.setState(snap, traceMin, TraceMemoryState.ERROR);
// TODO: Bookmark to describe error?
}, path); // sel could be rand()...
}
protected void stackUpdated(TargetStack stack) {
ManagedStackRecorder rec = recorder.getThreadRecorderForSuccessor(stack).getStackRecorder();
rec.recordStack();
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject scope, TargetObject focused) {
if (!valid) {
return;
}
if (PathUtils.isAncestor(target.getPath(), focused.getPath())) {
recorder.setCurrentFocus(focused);
}
}
public RecorderThreadMap getThreadMap() {
return recorder.getThreadMap();
}
public void dispose() {
target.getModel().removeModelListener(reorderer);
reorderer.dispose();
}
public CompletableFuture<Void> flushEvents() {
return reorderer.flushEvents();
}
}

View file

@ -1,279 +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.plugin.core.debug.service.model;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import ghidra.async.AsyncFence;
import ghidra.dbg.*;
import ghidra.dbg.target.*;
import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.util.Msg;
import ghidra.util.datastruct.PrivatelyQueuedListener;
@Deprecated(forRemoval = true, since = "11.3")
public class TraceObjectListener implements DebuggerModelListener {
private TraceObjectManager objectManager;
private TargetObject target;
protected boolean disposed = false;
protected final NavigableMap<List<String>, TargetObject> initialized =
new TreeMap<>(PathComparator.KEYED);
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
protected final PrivatelyQueuedListener<DebuggerModelListener> queue;
public TraceObjectListener(TraceObjectManager manager) {
this.objectManager = manager;
this.target = objectManager.getTarget();
DefaultTraceRecorder recorder = objectManager.getRecorder();
this.queue = new PrivatelyQueuedListener<>(DebuggerModelListener.class,
recorder.privateQueue, reorderer);
}
public CompletableFuture<Void> init() {
return findInitialObjects(target).thenAccept(adds -> {
for (TargetObject added : adds) {
processInit(added);
}
DebuggerObjectModel model = target.getModel();
model.addModelListener(queue.in, true);
});
}
boolean matchesTarget(TargetObject object) {
TargetObject proc = object;
while (proc != null) {
if (proc == target)
return true;
if (proc.getClass().equals(target.getClass()))
return false;
proc = proc.getParent();
}
return true;
}
protected void processCreate(TargetObject added) {
if (!objectManager.hasObject(added) && matchesTarget(added)) {
objectManager.addObject(added);
objectManager.createObject(added);
}
/*
else {
Msg.info(this, "processCreate dropped " + added);
}
*/
}
protected void processInit(TargetObject added) {
if (objectManager.hasObject(added)) {
if (!initialized.containsKey(added.getPath())) {
initialized.put(added.getPath(), added);
objectManager.initObject(added);
}
}
}
protected void processRemove(TargetObject removed) {
if (objectManager.hasObject(removed)) {
objectManager.removeObject(removed);
objectManager.removeObject(removed.getPath());
}
}
protected void processAttributesChanged(TargetObject changed, Map<String, ?> added) {
if (objectManager.hasObject(changed)) {
objectManager.attributesChanged(changed, added);
}
}
protected void processElementsChanged(TargetObject changed, Map<String, ?> added) {
if (objectManager.hasObject(changed)) {
objectManager.elementsChanged(changed, added);
}
}
@Override
public void created(TargetObject object) {
//System.err.println("CR:" + object);
processCreate(object);
}
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
processRemove(object);
}
@Override
public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
//System.err.println("AC:" + added + ":" + parent);
if (parent.isValid()) {
processInit(parent);
processAttributesChanged(parent, added);
}
}
@Override
public void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
//System.err.println("EC:" + added + ":" + parent);
if (parent.isValid()) {
processElementsChanged(parent, added);
}
}
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
synchronized (objectManager.objects) {
return objectManager.collectBreakpoints(thread);
}
}
protected void onProcessBreakpointContainers(
Consumer<? super TargetBreakpointSpecContainer> action) {
synchronized (objectManager.objects) {
objectManager.onProcessBreakpointContainers(action);
}
}
protected void onThreadBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointSpecContainer> action) {
synchronized (objectManager.objects) {
objectManager.onThreadBreakpointContainers(thread, action);
}
}
/*
public boolean addListener(TargetObject obj) {
if (obj == null) {
return false;
}
obj.addListener(this);
synchronized (objects) {
if (objects.put(obj.getPath(), obj) == obj) {
return false;
}
}
return true;
}
public void dispose() {
synchronized (objects) {
disposed = true;
for (Iterator<TargetObject> it = objects.values().iterator(); it.hasNext();) {
TargetObject obj = it.next();
obj.removeListener(this);
it.remove();
}
}
}
*/
private CompletableFuture<List<TargetObject>> findInitialObjects(TargetObject target) {
List<TargetObject> result = new ArrayList<>();
result.add(target);
AsyncFence fence = new AsyncFence();
CompletableFuture<? extends TargetEventScope> futureEvents =
DebugModelConventions.findSuitable(TargetEventScope.class, target);
fence.include(futureEvents.thenAccept(events -> {
if (events != null) {
result.add(events);
}
}).exceptionally(e -> {
Msg.warn(this, "Could not search for event scope", e);
return null;
}));
CompletableFuture<? extends TargetFocusScope> futureFocus =
DebugModelConventions.findSuitable(TargetFocusScope.class, target);
fence.include(futureFocus.thenAccept(focus -> {
if (focus != null) {
// Don't descend. Scope may be the entire session.
result.add(focus);
}
}).exceptionally(e -> {
Msg.error(this, "Could not search for focus scope", e);
return null;
}));
return fence.ready().thenApply(__ -> {
return result;
});
}
public void dispose() {
target.getModel().removeModelListener(reorderer);
reorderer.dispose();
}
public CompletableFuture<Void> flushEvents() {
return reorderer.flushEvents();
}
/*
private CompletableFuture<List<TargetObject>> findDependenciesTop(TargetObject added) {
List<TargetObject> result = new ArrayList<>();
result.add(added);
return findDependencies(added, result);
}
private CompletableFuture<List<TargetObject>> findDependencies(TargetObject added,
List<TargetObject> result) {
//System.err.println("findDependencies " + added);
AsyncFence fence = new AsyncFence();
fence.include(added.fetchAttributes(false).thenCompose(attrs -> {
AsyncFence af = new AsyncFence();
for (String key : attrs.keySet()) { //requiredObjKeys) {
Object object = attrs.get(key);
if (!(object instanceof TargetObject)) {
continue;
}
TargetObject ref = (TargetObject) object;
if (PathUtils.isLink(added.getPath(), key, ref.getPath())) {
continue;
}
af.include(ref.fetch().thenCompose(obj -> {
if (!objectManager.isRequired(obj)) {
return CompletableFuture.completedFuture(result);
}
synchronized (result) {
result.add(obj);
}
return findDependencies(obj, result);
}));
}
return af.ready();
}));
fence.include(added.fetchElements(false).thenCompose(elems -> {
AsyncFence ef = new AsyncFence();
for (TargetObject ref : elems.values()) {
ef.include(ref.fetch().thenCompose(obj -> {
synchronized (result) {
result.add(obj);
}
return findDependencies(obj, result);
}));
}
return ef.ready();
}));
return fence.ready().thenApply(__ -> {
return result;
});
}
*/
}

View file

@ -1,721 +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.plugin.core.debug.service.model;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.function.*;
import java.util.stream.Collectors;
import db.Transaction;
import ghidra.app.plugin.core.debug.mapping.DefaultDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.async.AsyncLazyMap;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.debug.api.model.*;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.DuplicateNameException;
@Deprecated(forRemoval = true, since = "11.3")
public class TraceObjectManager {
private final TargetObject target;
private final TraceEventListener eventListener;
final TraceObjectListener objectListener;
protected final NavigableMap<List<String>, TargetObject> objects =
new TreeMap<>(PathComparator.KEYED);
private DefaultTraceRecorder recorder;
private DefaultDebuggerTargetTraceMapper mapper;
protected DebuggerMemoryMapper memMapper;
protected AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> regMappers;
//private AbstractRecorderRegisterSet threadRegisters;
private final ListenerSet<TraceRecorderListener> listeners =
new ListenerSet<>(TraceRecorderListener.class, true);
protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>();
// NB: We add the objects in top-down order and initialize them bottom-up
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapCreate =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapInit =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapRemove =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMapElements =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMapAttributes =
new LinkedHashMap<>();
public TraceObjectManager(TargetObject target, DefaultDebuggerTargetTraceMapper mapper,
DefaultTraceRecorder recorder) {
this.target = target;
this.mapper = mapper;
this.recorder = recorder;
this.regMappers = new AsyncLazyMap<>(new HashMap<>(), ref -> mapper.offerRegisters(ref));
//this.threadRegisters = new RecorderComposedRegisterSet(recorder);
defaultHandlers();
this.eventListener = new TraceEventListener(this);
this.objectListener = new TraceObjectListener(this);
//objectListener.addListenerAndConsiderSuccessors(target);
}
public CompletableFuture<Void> init() {
return objectListener.init().thenCombine(eventListener.init(), (v1, v2) -> null);
}
private void defaultHandlers() {
putCreateHandler(TargetThread.class, this::createThread);
putCreateHandler(TargetMemory.class, this::createMemory);
putCreateHandler(TargetRegister.class, this::createRegister);
putInitHandler(TargetStack.class, this::addStack);
putInitHandler(TargetStackFrame.class, this::addStackFrame);
putInitHandler(TargetRegisterBank.class, this::addRegisterBank);
putInitHandler(TargetRegisterContainer.class, this::addRegisterContainer);
//putInitHandler(TargetMemoryRegion.class, this::addMemoryRegion);
putInitHandler(TargetModule.class, this::addModule);
//putInitHandler(TargetSection.class, this::addSection); // This is brutally expensive
putInitHandler(TargetBreakpointSpecContainer.class, this::addBreakpointContainer);
putInitHandler(TargetBreakpointSpec.class, this::addBreakpointSpec);
putInitHandler(TargetBreakpointLocation.class, this::addBreakpointLocation);
putElementsHandler(TargetBreakpointLocationContainer.class,
this::elementsChangedBreakpointLocationContainer);
putElementsHandler(TargetMemory.class, this::elementsChangedMemory);
putElementsHandler(TargetSectionContainer.class, this::elementsChangedSectionContainer);
putElementsHandler(TargetStack.class, this::elementsChangedStack);
putAttributesHandler(TargetBreakpointSpec.class, this::attributesChangedBreakpointSpec);
putAttributesHandler(TargetBreakpointLocation.class,
this::attributesChangedBreakpointLocation);
putAttributesHandler(TargetMemoryRegion.class, this::attributesChangedMemoryRegion);
putAttributesHandler(TargetModule.class, this::attributesChangedModule);
putAttributesHandler(TargetRegister.class, this::attributesChangedRegister);
putAttributesHandler(TargetStackFrame.class, this::attributesChangedStackFrame);
putAttributesHandler(TargetThread.class, this::attributesChangedThread);
putRemHandler(TargetProcess.class, this::removeProcess);
putRemHandler(TargetThread.class, this::removeThread);
putRemHandler(TargetStack.class, this::removeStack);
putRemHandler(TargetStackFrame.class, this::removeStackFrame);
putRemHandler(TargetStack.class, this::removeRegisterBank);
putRemHandler(TargetRegisterContainer.class, this::removeRegisterContainer);
putRemHandler(TargetRegister.class, this::removeRegister);
putRemHandler(TargetMemory.class, this::removeMemory);
putRemHandler(TargetMemoryRegion.class, this::removeMemoryRegion);
putRemHandler(TargetModule.class, this::removeModule);
putRemHandler(TargetSection.class, this::removeSection);
putRemHandler(TargetBreakpointSpecContainer.class, this::removeBreakpointContainer);
putRemHandler(TargetBreakpointSpec.class, this::removeBreakpointSpec);
putRemHandler(TargetBreakpointLocation.class, this::removeBreakpointLocation);
}
private <U extends TargetObject> Function<TargetObject, Void> putHandler(Class<?> key,
Consumer<TargetObject> handler,
LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMap) {
return handlerMap.put(key, (u) -> {
handler.accept(u);
return null;
});
}
@SuppressWarnings("unchecked")
private <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putHandler(
Class<?> key, BiConsumer<U, Map<String, ?>> handler,
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
return handlerMap.put(key, (u, v) -> {
handler.accept((U) u, v);
return null;
});
}
public <U extends TargetObject> Function<TargetObject, Void> putCreateHandler(Class<?> key,
Consumer<TargetObject> handler) {
return putHandler(key, handler, handlerMapCreate);
}
public <U extends TargetObject> Function<TargetObject, Void> putInitHandler(Class<?> key,
Consumer<TargetObject> handler) {
return putHandler(key, handler, handlerMapInit);
}
public <U extends TargetObject> Function<TargetObject, Void> putRemHandler(Class<?> key,
Consumer<TargetObject> handler) {
return putHandler(key, handler, handlerMapRemove);
}
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putAttributesHandler(
Class<U> key, BiConsumer<U, Map<String, ?>> handler) {
return putHandler(key, handler, handlerMapAttributes);
}
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putElementsHandler(
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler) {
return putHandler(key, handler, handlerMapElements);
}
private void processObject(TargetObject targetObject,
LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMap) {
Set<Class<? extends TargetObject>> interfaces = targetObject.getSchema().getInterfaces();
for (Class<? extends TargetObject> ifc : interfaces) {
Function<TargetObject, ? extends Void> function = handlerMap.get(ifc);
if (function != null) {
function.apply(targetObject);
}
}
}
private void processObject(TargetObject targetObject, Map<String, ?> map,
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
Set<Class<? extends TargetObject>> interfaces = targetObject.getSchema().getInterfaces();
for (Class<? extends TargetObject> ifc : interfaces) {
BiFunction<TargetObject, Map<String, ?>, ? extends Void> function = handlerMap.get(ifc);
if (function != null) {
function.apply(targetObject, map);
}
}
}
public void createObject(TargetObject toInit) {
processObject(toInit, handlerMapCreate);
}
public void initObject(TargetObject added) {
//System.err.println("initObject " + added);
processObject(added, handlerMapInit);
}
public void removeObject(TargetObject removed) {
processObject(removed, handlerMapRemove);
}
public void attributesChanged(TargetObject changed, Map<String, ?> added) {
processObject(changed, added, handlerMapAttributes);
}
public void elementsChanged(TargetObject changed, Map<String, ?> added) {
processObject(changed, added, handlerMapElements);
}
public boolean isRequired(TargetObject obj) {
if (obj.getName().equals("Debug"))
return true;
if (obj.getName().equals("Stack"))
return true;
Set<Class<? extends TargetObject>> interfaces = obj.getSchema().getInterfaces();
for (Class<? extends TargetObject> ifc : interfaces) {
if (handlerMapInit.keySet().contains(ifc)) {
return true;
}
}
return false;
}
public void addProcess(TargetObject added) {
// Create a new processRecorder
recorder.init();
}
public void removeProcess(TargetObject removed) {
recorder.stopRecording();
}
public void createThread(TargetObject added) {
//System.err.println("createThread " + added + ":" + this);
synchronized (recorder.threadMap) {
ManagedThreadRecorder threadRecorder = recorder.getThreadRecorder((TargetThread) added);
TraceThread traceThread = threadRecorder.getTraceThread();
recorder.createSnapshot(traceThread + " started", traceThread, null);
try (Transaction tx =
recorder.getTrace().openTransaction("Adjust thread creation")) {
long existing = traceThread.getCreationSnap();
if (existing == Long.MIN_VALUE) {
traceThread.setCreationSnap(recorder.getSnap());
}
else {
traceThread.setDestructionSnap(Long.MAX_VALUE);
}
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be shrinking
}
catch (IllegalArgumentException e) {
Msg.warn(this, "Unable to set creation snap for " + traceThread);
}
}
}
public void removeThread(TargetObject removed) {
synchronized (recorder.threadMap) {
ManagedThreadRecorder threadRecorder =
recorder.getThreadRecorder((TargetThread) removed);
threadRecorder.objectRemoved(removed);
}
}
public void addStack(TargetObject added) {
//addEventListener(added);
}
public void removeStack(TargetObject removed) {
// Nothing for now
}
public void addStackFrame(TargetObject added) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
if (rec == null) {
Msg.error(this, "Frame without thread?: " + added);
}
else {
rec.getStackRecorder().offerStackFrame((TargetStackFrame) added);
}
}
public void removeStackFrame(TargetObject removed) {
synchronized (recorder.threadMap) {
ManagedThreadRecorder threadRecorder = recorder.getThreadRecorderForSuccessor(removed);
threadRecorder.objectRemoved(removed);
}
}
public void addRegisterBank(TargetObject added) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
if (added instanceof TargetStackFrame) {
rec.getStackRecorder().offerStackFrame((TargetStackFrame) added);
}
rec.offerRegisters((TargetRegisterBank) added);
}
public void removeRegisterBank(TargetObject removed) {
//ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(removed);
//rec.removeRegisters((TargetRegisterBank) removed);
}
public void addRegisterContainer(TargetObject added) {
// These are picked up when a bank is added with these descriptions
}
public void removeRegisterContainer(TargetObject removed) {
regMappers.remove((TargetRegisterContainer) removed);
}
public void createRegister(TargetObject added) {
if (added.getCachedAttribute(TargetRegister.CONTAINER_ATTRIBUTE_NAME) != null) {
TargetRegister register = (TargetRegister) added;
regMappers.get(register.getContainer()).thenAccept(rm -> {
if (rm != null) {
rm.targetRegisterAdded(register);
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, register, false);
}
}
});
}
}
public void removeRegister(TargetObject removed) {
TargetRegister register = (TargetRegister) removed;
TargetRegisterContainer cont = register.getContainer();
DebuggerRegisterMapper rm = regMappers.getCompletedMap().get(cont);
if (rm == null) {
return;
}
rm.targetRegisterRemoved(register);
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, register, true);
}
}
public void createMemory(TargetObject added) {
if (memMapper != null) {
return;
}
recorder.memoryRecorder.offerProcessMemory((TargetMemory) added);
mapper.offerMemory((TargetMemory) added).thenAccept(mm -> {
synchronized (this) {
memMapper = mm;
//addEventListener(added);
}
//listenerForRecord.retroOfferMemMapperDependents();
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize memory mapper", ex);
return null;
});
}
public void removeMemory(TargetObject removed) {
recorder.memoryRecorder.removeProcessMemory((TargetMemory) removed);
}
public void addMemoryRegion(TargetObject added) {
/*
TargetMemoryRegion region = (TargetMemoryRegion) added;
findThreadOrProcess(added).thenAccept(obj -> {
if (obj == target) {
recorder.memoryRecorder.offerProcessRegion(region);
return;
}
if (obj instanceof TargetThread) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
rec.offerThreadRegion(region);
}
}).exceptionally(ex -> {
Msg.error(this, "Error recording memory region", ex);
return null;
});
*/
}
public void removeMemoryRegion(TargetObject removed) {
recorder.memoryRecorder.removeProcessRegion((TargetMemoryRegion) removed);
}
public void addModule(TargetObject added) {
recorder.moduleRecorder.offerProcessModule((TargetModule) added);
}
public void removeModule(TargetObject removed) {
recorder.moduleRecorder.removeProcessModule((TargetModule) removed);
}
public void addSection(TargetObject added) {
/*
TargetSection section = (TargetSection) added;
TargetModule module = section.getModule();
recorder.moduleRecorder.offerProcessModuleSection(module, section);
// I hope this should never be a per-thread thing
*/
}
public void removeSection(TargetObject removed) {
// Nothing for now
}
public void addBreakpointContainer(TargetObject added) {
TargetObject obj = findThreadOrProcess(added);
// NB. obj can be null
ManagedBreakpointRecorder breakpointRecorder = recorder.breakpointRecorder;
if (obj instanceof TargetThread) {
ManagedBreakpointRecorder rec =
recorder.getThreadRecorderForSuccessor(added).getBreakpointRecorder();
rec.offerBreakpointContainer((TargetBreakpointSpecContainer) added);
return;
}
breakpointRecorder.offerBreakpointContainer((TargetBreakpointSpecContainer) added);
}
public void removeBreakpointContainer(TargetObject removed) {
// Nothing for now
}
public void addBreakpointSpec(TargetObject added) {
// Nothing for now
}
public void removeBreakpointSpec(TargetObject removed) {
// Nothing for now
}
public void addBreakpointLocation(TargetObject added) {
// Nothing for now
//breakpoints.add((TargetBreakpointLocation) added);
//recorder.breakpointRecorder.offerEffectiveBreakpoint((TargetBreakpointLocation) added);
}
public void removeBreakpointLocation(TargetObject removed) {
breakpoints.remove(removed);
recorder.breakpointRecorder.removeBreakpointLocation((TargetBreakpointLocation) removed);
}
protected TargetObject findThreadOrProcess(TargetObject successor) {
TargetObject object = successor;
while (object != null) {
if (object instanceof TargetProcess)
return object;
if (object instanceof TargetThread)
return object;
object = object.getParent();
}
return object;
}
public DefaultDebuggerTargetTraceMapper getMapper() {
return mapper;
}
public DebuggerMemoryMapper getMemoryMapper() {
return memMapper;
}
public AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> getRegMappers() {
return regMappers;
}
public Set<TargetBreakpointLocation> getBreakpoints() {
return breakpoints;
}
public void attributesChangedBreakpointSpec(TargetObject bpt, Map<String, ?> added) {
if (added.containsKey(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME) ||
added.containsKey(TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME)) {
TargetBreakpointSpec spec = (TargetBreakpointSpec) bpt;
boolean enabled = spec.isEnabled();
Set<TraceBreakpointKind> traceKinds =
TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds());
recorder.breakpointRecorder.breakpointSpecChanged(spec, enabled, traceKinds);
}
}
public void attributesChangedBreakpointLocation(TargetObject obj, Map<String, ?> added) {
TargetBreakpointLocation loc = (TargetBreakpointLocation) obj;
if (added.containsKey(TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME)) {
AddressRange traceRng = recorder.getMemoryMapper().targetToTrace(loc.getRange());
String path = loc.getJoinedPath(".");
recorder.breakpointRecorder.breakpointLocationChanged(traceRng, path);
}
}
public void attributesChangedMemoryRegion(TargetObject region, Map<String, ?> added) {
if (added.containsKey(TargetObject.DISPLAY_ATTRIBUTE_NAME)) {
recorder.memoryRecorder.regionChanged((TargetMemoryRegion) region, region.getDisplay());
}
}
public void attributesChangedModule(TargetModule module, Map<String, ?> added) {
if (added.containsKey(TargetModule.RANGE_ATTRIBUTE_NAME)) {
AddressRange traceRng = recorder.getMemoryMapper().targetToTrace(module.getRange());
recorder.moduleRecorder.moduleChanged(module, traceRng);
}
}
public void attributesChangedRegister(TargetObject parent, Map<String, ?> added) {
if (added.containsKey(TargetRegister.CONTAINER_ATTRIBUTE_NAME)) {
TargetRegister register = (TargetRegister) parent;
regMappers.get(register.getContainer()).thenAccept(rm -> {
rm.targetRegisterAdded(register);
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, register, false);
}
});
}
if (added.containsKey(TargetObject.VALUE_ATTRIBUTE_NAME)) {
TargetRegister register = (TargetRegister) parent;
Object val = added.get(TargetObject.VALUE_ATTRIBUTE_NAME);
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(register);
if (val instanceof String valstr) {
rec.recordRegisterValue(register, new BigInteger(valstr, 16).toByteArray());
}
else if (val instanceof byte[] valarr) {
rec.recordRegisterValue(register, valarr);
}
}
}
public void attributesChangedStackFrame(TargetObject frame, Map<String, ?> added) {
if (added.containsKey(TargetStackFrame.PC_ATTRIBUTE_NAME)) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(frame);
if (rec != null) {
rec.getStackRecorder().offerStackFrame((TargetStackFrame) frame);
}
}
}
public void attributesChangedThread(TargetObject thread, Map<String, ?> added) {
if (added.containsKey(TargetObject.DISPLAY_ATTRIBUTE_NAME)) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(thread);
if (rec != null) {
String name = (String) added.get(TargetObject.DISPLAY_ATTRIBUTE_NAME);
try (Transaction tx = rec.getTrace().openTransaction("Rename thread")) {
rec.getTraceThread().setName(name);
}
}
}
}
public void elementsChangedBreakpointLocationContainer(TargetObject locationContainer,
Map<String, ?> added) {
TargetObject x = findThreadOrProcess(locationContainer);
if (x != null) {
for (Entry<String, ?> entry : added.entrySet()) {
TargetBreakpointLocation loc = (TargetBreakpointLocation) entry.getValue();
if (loc.isValid()) {
breakpoints.add(loc);
recorder.breakpointRecorder.offerBreakpointLocation(x, loc);
}
}
}
}
public void elementsChangedMemory(TargetObject memory, Map<String, ?> added) {
// TODO: This should probably only ever be a process
TargetObject threadOrProcess = findThreadOrProcess(memory);
if (threadOrProcess != null) {
for (Object object : added.values()) {
TargetMemoryRegion region = (TargetMemoryRegion) object;
if (!region.isValid()) {
continue;
}
if (threadOrProcess == target) {
recorder.memoryRecorder.offerProcessRegion(region);
}
else if (threadOrProcess instanceof TargetThread) {
ManagedThreadRecorder rec =
recorder.getThreadRecorderForSuccessor(threadOrProcess);
rec.offerThreadRegion(region);
}
}
}
else {
Msg.error(this, "Could not find process/thread for " + memory);
}
}
public void elementsChangedSectionContainer(TargetObject sectionContainer,
Map<String, ?> added) {
for (Object object : added.values()) {
TargetSection section = (TargetSection) object;
if (!section.isValid()) {
continue;
}
recorder.moduleRecorder.offerProcessModuleSection(section);
}
}
public void elementsChangedStack(TargetObject stack, Map<String, ?> added) {
ManagedStackRecorder rec = recorder.getThreadRecorderForSuccessor(stack).getStackRecorder();
rec.recordStack();
}
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
synchronized (objects) {
return (TargetMemoryRegion) objects.get(PathUtils.parse(region.getPath()));
}
}
public TargetModule getTargetModule(TraceModule module) {
synchronized (objects) {
return (TargetModule) objects.get(PathUtils.parse(module.getPath()));
}
}
public TargetSection getTargetSection(TraceSection section) {
synchronized (objects) {
return (TargetSection) objects.get(PathUtils.parse(section.getPath()));
}
}
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
synchronized (objects) {
return (TargetBreakpointLocation) objects.get(PathUtils.parse(bpt.getPath()));
}
}
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
return getBreakpoints().stream().collect(Collectors.toList());
}
public void onBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointSpecContainer> action) {
if (thread == null) {
objectListener.onProcessBreakpointContainers(action);
}
else {
objectListener.onThreadBreakpointContainers(thread, action);
}
}
public void onProcessBreakpointContainers(
Consumer<? super TargetBreakpointSpecContainer> action) {
TargetBreakpointSpecContainer breakpointContainer =
recorder.breakpointRecorder.getBreakpointContainer();
if (breakpointContainer == null) {
for (TargetThread thread : recorder.getThreadsView()) {
objectListener.onThreadBreakpointContainers(thread, action);
}
}
else {
action.accept(breakpointContainer);
}
}
public void onThreadBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointSpecContainer> action) {
TargetBreakpointSpecContainer breakpointContainer =
recorder.getThreadRecorder(thread).getBreakpointRecorder().getBreakpointContainer();
if (breakpointContainer == null) {
return;
}
action.accept(breakpointContainer);
}
// Needed by TraceRecorder
public TraceEventListener getEventListener() {
return eventListener;
}
public ListenerSet<TraceRecorderListener> getListeners() {
return listeners;
}
public TargetObject getTarget() {
return target;
}
public DefaultTraceRecorder getRecorder() {
return recorder;
}
public boolean hasObject(TargetObject object) {
return objects.containsKey(object.getPath());
}
public void addObject(TargetObject added) {
objects.put(added.getPath(), added);
}
public void removeObject(List<String> path) {
objects.remove(path);
}
public void disposeModelListeners() {
eventListener.dispose();
objectListener.dispose();
}
public CompletableFuture<Void> flushEvents() {
return eventListener.flushEvents().thenCompose(__ -> {
return objectListener.flushEvents();
});
}
}

View file

@ -1,628 +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.plugin.core.debug.service.model;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import docking.ActionContext;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
import ghidra.app.plugin.core.debug.service.target.AbstractTarget;
import ghidra.app.services.DebuggerConsoleService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.*;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
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.stack.TraceStackFrame;
import ghidra.trace.model.target.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public class TraceRecorderTarget extends AbstractTarget {
private final TraceRecorder recorder;
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
return false;
}
if (!Objects.equals(prev.getFrame(), resolved.getFrame())) {
return false;
}
if (!Objects.equals(prev.getThread(), resolved.getThread())) {
return false;
}
if (!Objects.equals(prev.getTrace(), resolved.getTrace())) {
return false;
}
return true;
}
protected static boolean checkTargetActivation(DebuggerCoordinates prev,
DebuggerCoordinates resolved) {
if (!resolved.isAlive()) {
return false;
}
if (isSameFocus(prev, resolved)) {
return false;
}
return true;
}
public TraceRecorderTarget(PluginTool tool, TraceRecorder recorder) {
super(tool);
this.recorder = recorder;
}
@Override
public String describe() {
return "%s in %s (recorder)".formatted(getTrace().getDomainFile().getName(),
recorder.getTarget().getModel().getBrief());
}
@Override
public boolean isValid() {
return recorder.isRecording();
}
protected <T extends TargetObject> T findObjectInContext(ActionContext context,
Class<T> iface) {
if (context instanceof DebuggerObjectActionContext ctx) {
List<TraceObjectValue> values = ctx.getObjectValues();
if (values.size() != 1) {
return null;
}
TraceObjectValue single = values.get(0);
if (!single.isObject()) {
return null;
}
TraceObject suitable = single.getChild().querySuitableTargetInterface(iface);
if (suitable == null) {
return null;
}
return iface.cast(recorder.getTargetObject(suitable));
}
else if (context instanceof DebuggerSingleObjectPathActionContext ctx) {
TargetObject targetObject = recorder.getTargetObject(ctx.getPath());
if (targetObject == null) {
return null;
}
return targetObject.getCachedSuitable(iface);
}
return null;
}
protected <T extends TargetObject> T findObjectInTrace(ActionContext context, Class<T> iface) {
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
TraceObject focus = traceManager.getCurrentObject();
if (focus == null) {
return null;
}
TraceObject suitable = focus.querySuitableTargetInterface(iface);
if (suitable == null) {
return null;
}
return iface.cast(recorder.getTargetObject(suitable));
}
protected <T extends TargetObject> T findObjectInRecorder(ActionContext context,
Class<T> iface) {
if (!isValid()) {
return null;
}
TargetObject focus = recorder.getFocus();
if (focus == null) {
return null;
}
return focus.getCachedSuitable(iface);
}
protected <T extends TargetObject> T findObject(ActionContext context, Class<T> iface) {
T object = findObjectInContext(context, iface);
if (object != null) {
return object;
}
object = findObjectInTrace(context, iface);
if (object != null) {
return object;
}
return findObjectInRecorder(context, iface);
}
private Map<String, Object> collectArgumentsReqAddr(TargetParameterMap params,
Address address) {
// The only required non-defaulted argument allowed must be an Address
// There must be an Address parameter
ParameterDescription<?> addrParam = null;
for (ParameterDescription<?> p : params.values()) {
if (p.type == Address.class) {
if (addrParam != null) {
return null;
}
addrParam = p;
}
else if (p.required && p.defaultValue == null) {
return null;
}
}
if (addrParam == null) {
return null;
}
return Map.of(addrParam.name, address);
}
private record MethodWithArgs(TargetMethod method, Map<String, Object> arguments) {
public boolean requiresPrompt() {
for (ParameterDescription<?> param : method.getParameters().values()) {
if (param.required && !arguments.containsKey(param.name)) {
return true;
}
}
return false;
}
}
private List<MethodWithArgs> findAddressMethods(ProgramLocationActionContext context) {
Address address = findAddress(context);
if (address == null) {
return List.of();
}
TargetObject object = findObject(context, TargetObject.class);
if (object == null) {
return List.of();
}
List<MethodWithArgs> result = new ArrayList<>();
PathPredicates matcher = object.getModel()
.getRootSchema()
.matcherForSuitable(TargetMethod.class, object.getPath());
for (TargetObject obj : matcher.getCachedSuccessors(object.getModel().getModelRoot())
.values()) {
if (!(obj instanceof TargetMethod method)) {
continue;
}
Map<String, Object> arguments =
collectArgumentsReqAddr(method.getParameters(), address);
if (arguments == null) {
continue;
}
result.add(new MethodWithArgs(method, arguments));
}
return result;
}
private static String getDisplay(TargetMethod method) {
String display = method.getDisplay();
if (display != null) {
return display;
}
return method.getName();
}
private Map<String, ?> promptArgs(TargetMethod method, Map<String, ?> defaults) {
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
method.getDisplay(), method.getDisplay(), null);
Map<String, ValStr<?>> args = dialog.promptArguments(method.getParameters(), defs, defs);
return args == null ? null : ValStr.toPlainMap(args);
}
private CompletableFuture<?> invokeMethod(boolean prompt, TargetMethod method,
Map<String, ?> arguments) {
Map<String, ?> chosenArgs;
if (prompt) {
chosenArgs = promptArgs(method, arguments);
}
else {
chosenArgs = arguments;
}
return method.invoke(chosenArgs).thenAccept(result -> {
DebuggerConsoleService consoleService =
tool.getService(DebuggerConsoleService.class);
if (consoleService != null && method.getReturnType() != Void.class) {
consoleService.log(null, getDisplay(method) + " returned " + result);
}
});
}
private ActionEntry makeEntry(boolean requiresPrompt, TargetMethod method,
Map<String, ?> arguments) {
return new ActionEntry(method.getDisplay(), null, null, requiresPrompt,
method.getPath().size(), () -> true, prompt -> invokeMethod(prompt, method, arguments));
}
@Override
public Map<String, ActionEntry> collectAddressActions(ProgramLocationActionContext context) {
Map<String, ActionEntry> result = new HashMap<>();
for (MethodWithArgs mwa : findAddressMethods(context)) {
result.put(mwa.method.getJoinedPath("."),
makeEntry(mwa.requiresPrompt(), mwa.method, mwa.arguments));
}
return result;
}
protected <T extends TargetObject> Map<String, ActionEntry> collectIfaceActions(
ActionContext context, Class<T> iface, String display, ActionName name,
String description, Predicate<T> enabled, Function<T, CompletableFuture<?>> action) {
T object = findObject(context, iface);
if (object == null) {
return Map.of();
}
return Map.of(display, new ActionEntry(display, name, description, false,
object.getPath().size(), () -> enabled.test(object), prompt -> action.apply(object)));
}
private TargetExecutionState getStateOf(TargetObject object) {
TargetExecutionStateful stateful = object.getCachedSuitable(TargetExecutionStateful.class);
return stateful == null ? null : stateful.getExecutionState();
}
private <T extends TargetObject> Predicate<T> stateNullOr(
Predicate<TargetExecutionState> predicate) {
return object -> {
TargetExecutionState state = getStateOf(object);
return state == null || predicate.test(state);
};
}
@Override
protected Map<String, ActionEntry> collectResumeActions(ActionContext context) {
return collectIfaceActions(context, TargetResumable.class, "Resume",
ActionName.RESUME, "Resume, i.e., go or continue execution of the target",
stateNullOr(TargetExecutionState::isStopped), TargetResumable::resume);
}
@Override
protected Map<String, ActionEntry> collectInterruptActions(ActionContext context) {
return collectIfaceActions(context, TargetInterruptible.class, "Interrupt",
ActionName.INTERRUPT, "Interrupt, i.e., suspend, the target",
stateNullOr(TargetExecutionState::isRunning), TargetInterruptible::interrupt);
}
@Override
protected Map<String, ActionEntry> collectKillActions(ActionContext context) {
return collectIfaceActions(context, TargetKillable.class, "Kill",
ActionName.KILL, "Kill, i.e., forcibly terminate the target",
stateNullOr(TargetExecutionState::isAlive), TargetKillable::kill);
}
@Override
protected Map<String, ActionEntry> collectStepIntoActions(ActionContext context) {
return collectIfaceActions(context, TargetSteppable.class, "Step Into",
ActionName.STEP_INTO, "Step the target a single instruction, descending into calls",
stateNullOr(TargetExecutionState::isStopped),
steppable -> steppable.step(TargetStepKind.INTO));
}
@Override
protected Map<String, ActionEntry> collectStepOverActions(ActionContext context) {
return collectIfaceActions(context, TargetSteppable.class, "Step Over",
ActionName.STEP_OVER, "Step the target a single instruction, without following calls",
stateNullOr(TargetExecutionState::isStopped),
steppable -> steppable.step(TargetStepKind.OVER));
}
@Override
protected Map<String, ActionEntry> collectStepOutActions(ActionContext context) {
return collectIfaceActions(context, TargetSteppable.class, "Step Out",
ActionName.STEP_OUT, "Step the target until it completes the current frame",
stateNullOr(TargetExecutionState::isStopped),
steppable -> steppable.step(TargetStepKind.FINISH));
}
@Override
protected Map<String, ActionEntry> collectStepExtActions(ActionContext context) {
return collectIfaceActions(context, TargetSteppable.class, "Step Last",
ActionName.STEP_EXT, "Step the target in a target-defined way",
stateNullOr(TargetExecutionState::isStopped),
steppable -> steppable.step(TargetStepKind.EXTENDED));
}
@Override
protected Map<String, ActionEntry> collectRefreshActions(ActionContext context) {
// Not necessary to support this here
return Map.of();
}
@Override
protected Map<String, ActionEntry> collectToggleActions(ActionContext context) {
return collectIfaceActions(context, TargetTogglable.class, "Toggle",
ActionName.TOGGLE, "Toggle the object",
togglable -> true,
togglable -> togglable.toggle(!togglable.isEnabled()));
}
@Override
public Trace getTrace() {
return recorder.getTrace();
}
@Override
public long getSnap() {
return recorder.getSnap();
}
@Override
public TargetExecutionState getThreadExecutionState(TraceThread thread) {
return recorder.getTargetThreadState(thread);
}
@Override
public boolean isSupportsFocus() {
return recorder.isSupportsFocus();
}
@Override
public TraceObjectKeyPath getFocus() {
TargetObject object = recorder.getFocus();
if (object == null) {
return null;
}
return TraceObjectKeyPath.of(object.getPath());
}
protected TargetObject toTargetObject(DebuggerCoordinates coords) {
TraceObject obj = coords.getObject();
if (obj != null) {
TargetObject object =
recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList());
if (object != null) {
return object;
}
}
TargetStackFrame frame =
recorder.getTargetStackFrame(coords.getThread(), coords.getFrame());
if (frame != null) {
return frame;
}
TargetThread thread = recorder.getTargetThread(coords.getThread());
if (thread != null) {
return thread;
}
return recorder.getTarget();
}
@Override
public CompletableFuture<Void> activateAsync(DebuggerCoordinates prev,
DebuggerCoordinates coords) {
if (!checkTargetActivation(prev, coords)) {
return AsyncUtils.nil();
}
if (!recorder.isRecording() || recorder.getSnap() != coords.getSnap() ||
!coords.getTime().isSnapOnly()) {
return AsyncUtils.nil();
}
TargetObject obj = toTargetObject(coords);
if (obj == null) {
return AsyncUtils.nil();
}
return recorder.requestActivation(obj).thenApply(__ -> null);
}
@Override
public TraceThread getThreadForSuccessor(TraceObjectKeyPath path) {
if (path == null) {
return null;
}
TargetObject object = recorder.getTargetObject(path);
if (object == null) {
return null;
}
return recorder.getTraceThreadForSuccessor(object);
}
@Override
public TraceStackFrame getStackFrameForSuccessor(TraceObjectKeyPath path) {
if (path == null) {
return null;
}
TargetObject object = recorder.getTargetObject(path);
if (object == null) {
return null;
}
return recorder.getTraceStackFrameForSuccessor(object);
}
@Override
public CompletableFuture<Void> invalidateMemoryCachesAsync() {
TargetObject target = recorder.getTarget();
DebuggerObjectModel model = target.getModel();
model.invalidateAllLocalCaches();
PathMatcher memMatcher = target.getSchema().searchFor(TargetMemory.class, true);
Collection<TargetObject> memories = memMatcher.getCachedSuccessors(target).values();
CompletableFuture<?>[] requests = memories.stream()
.map(TargetObject::invalidateCaches)
.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(requests);
}
@Override
public CompletableFuture<String> executeAsync(String command, boolean toString) {
TargetInterpreter interpreter = findObjectInRecorder(null, TargetInterpreter.class);
if (interpreter == null) {
return AsyncUtils.nil();
}
if (toString) {
return interpreter.executeCapture(command);
}
return interpreter.execute(command).thenApply(r -> null);
}
@Override
public CompletableFuture<Void> readMemoryAsync(AddressSetView set, TaskMonitor monitor) {
return recorder.readMemoryBlocks(set, monitor)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> recorder.getTrace().flushEvents());
}
@Override
public CompletableFuture<Void> readRegistersAsync(TracePlatform platform, TraceThread thread,
int frame, Set<Register> registers) {
if (registers.isEmpty()) {
return AsyncUtils.nil();
}
return recorder.captureThreadRegisters(platform, thread, frame, registers)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> platform.getTrace().flushEvents());
}
@Override
public boolean isVariableExists(TracePlatform platform, TraceThread thread, int frame,
Address address, int length) {
return recorder.isVariableOnTarget(platform, thread, frame, address, length);
}
@Override
public CompletableFuture<Void> writeMemoryAsync(Address address, byte[] data) {
return recorder.writeMemory(address, data)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> recorder.getTrace().flushEvents());
}
@Override
public CompletableFuture<Void> writeRegisterAsync(TracePlatform platform, TraceThread thread,
int frame, RegisterValue value) {
return recorder.writeThreadRegisters(platform, thread, frame,
Map.of(value.getRegister(), value))
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> recorder.getTrace().flushEvents());
}
@Override
public CompletableFuture<Void> writeRegisterAsync(TracePlatform platform, TraceThread thread,
int frame, Address address, byte[] data) {
return recorder.writeRegister(platform, thread, frame, address, data)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> recorder.getTrace().flushEvents());
}
@Override
public CompletableFuture<Void> writeVariableAsync(TracePlatform platform, TraceThread thread,
int frame, Address address, byte[] data) {
return recorder.writeVariable(platform, thread, frame, address, data);
}
@Override
public CompletableFuture<Void> placeBreakpointAsync(AddressRange range,
Set<TraceBreakpointKind> kinds, String condition, String commands) {
if (condition != null && !condition.isBlank()) {
Msg.warn(this, "breakpoint condition not supported by recorder-based targets");
}
if (commands != null && !commands.isBlank()) {
Msg.warn(this, "breakpoint commands not supported by recorder-based targets");
}
Set<TargetBreakpointKind> tKinds =
TraceRecorder.traceToTargetBreakpointKinds(kinds);
AddressRange targetRange = recorder.getMemoryMapper().traceToTarget(range);
AsyncFence fence = new AsyncFence();
for (TargetBreakpointSpecContainer cont : recorder.collectBreakpointContainers(null)) {
Set<TargetBreakpointKind> stKinds = new LinkedHashSet<>(tKinds);
stKinds.retainAll(cont.getSupportedBreakpointKinds());
fence.include(cont.placeBreakpoint(targetRange, stKinds));
}
return fence.ready();
}
@Override
public Set<TraceBreakpointKind> getSupportedBreakpointKinds() {
return recorder.getSupportedBreakpointKinds();
}
@Override
public boolean isBreakpointValid(TraceBreakpoint breakpoint) {
return recorder.getTargetBreakpoint(breakpoint) != null;
}
@Override
public CompletableFuture<Void> deleteBreakpointAsync(TraceBreakpoint breakpoint) {
TargetBreakpointLocation loc = recorder.getTargetBreakpoint(breakpoint);
if (loc == null) {
Msg.warn(this, "Breakpoint not valid on target: " + loc);
return AsyncUtils.nil();
}
if (loc instanceof TargetDeletable del) {
return del.delete();
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetDeletable del) {
return del.delete();
}
Msg.warn(this, "Neither location nor specification for breakpoint is deletable: " + loc);
return AsyncUtils.nil();
}
@Override
public CompletableFuture<Void> toggleBreakpointAsync(TraceBreakpoint breakpoint,
boolean enabled) {
TargetBreakpointLocation loc = recorder.getTargetBreakpoint(breakpoint);
if (loc == null) {
Msg.warn(this, "Breakpoint not valid on target: " + loc);
return AsyncUtils.nil();
}
if (loc instanceof TargetTogglable tog) {
return tog.toggle(enabled);
}
TargetBreakpointSpec spec = loc.getSpecification();
return spec.toggle(enabled);
}
@Override
public CompletableFuture<Void> forceTerminateAsync() {
recorder.stopRecording();
return AsyncUtils.nil();
}
@Override
public CompletableFuture<Void> disconnectAsync() {
return recorder.getTarget()
.getModel()
.close()
.orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
}

View file

@ -1,51 +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.plugin.core.debug.service.model.interfaces;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import ghidra.dbg.target.*;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.program.model.address.*;
@Deprecated(forRemoval = true, since = "11.3")
public interface AbstractRecorderMemory {
public void addMemory(TargetMemory memory);
public void addRegion(TargetMemoryRegion region, TargetMemory memory);
public void removeMemory(TargetMemory invalid);
public boolean removeRegion(TargetObject invalid);
public CompletableFuture<byte[]> readMemory(Address address, int length);
public CompletableFuture<Void> writeMemory(Address address, byte[] data);
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper);
public AddressRange alignAndLimitToFloor(Address targetAddress, int length);
}

View file

@ -1,32 +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.plugin.core.debug.service.model.interfaces;
import ghidra.debug.api.model.DebuggerMemoryMapper;
import ghidra.trace.model.Trace;
@Deprecated(forRemoval = true, since = "11.3")
public interface AbstractTraceRecorder {
public Trace getTrace();
public long getSnap();
public DebuggerMemoryMapper getMemoryMapper();
public ManagedBreakpointRecorder getBreakpointRecorder();
}

View file

@ -1,59 +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.plugin.core.debug.service.model.interfaces;
import java.util.Collection;
import java.util.Set;
import ghidra.dbg.target.*;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.thread.TraceThread;
@Deprecated(forRemoval = true, since = "11.3")
public interface ManagedBreakpointRecorder {
void offerBreakpointContainer(TargetBreakpointSpecContainer added);
void offerBreakpointLocation(TargetObject target, TargetBreakpointLocation added);
void recordBreakpoint(TargetBreakpointLocation loc, Set<TraceThread> traceThreads);
void removeBreakpointLocation(TargetBreakpointLocation removed);
TargetBreakpointSpecContainer getBreakpointContainer();
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
/**
* The range of a breakpoint location has changed
*
* @param traceRng the address range of the location in the trace
* @param path the dot-separated path of the breakpoint location in the model
*/
void breakpointLocationChanged(AddressRange traceRng, String path);
/**
* A breakpoint specification has changed (typically, toggled)
*
* @param spec the specification that changed
* @param enabled whether or not the spec is enabled
* @param kinds the kinds of the spec
*/
void breakpointSpecChanged(TargetBreakpointSpec spec, boolean enabled,
Collection<TraceBreakpointKind> kinds);
}

View file

@ -1,33 +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.plugin.core.debug.service.model.interfaces;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryRegion;
@Deprecated(forRemoval = true, since = "11.3")
public interface ManagedMemoryRecorder {
void offerProcessMemory(TargetMemory memory);
void offerProcessRegion(TargetMemoryRegion region);
void removeProcessMemory(TargetMemory memory);
void removeProcessRegion(TargetMemoryRegion region);
TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region);
}

View file

@ -1,36 +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.plugin.core.debug.service.model.interfaces;
import ghidra.dbg.target.TargetModule;
import ghidra.dbg.target.TargetSection;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
@Deprecated(forRemoval = true, since = "11.3")
public interface ManagedModuleRecorder {
void offerProcessModule(TargetModule module);
void offerProcessModuleSection(TargetSection section);
void removeProcessModule(TargetModule module);
TraceModule getTraceModule(TargetModule module);
TraceSection getTraceSection(TargetSection section);
}

View file

@ -1,23 +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.plugin.core.debug.service.model.interfaces;
@Deprecated(forRemoval = true, since = "11.3")
public interface ManagedProcessRecorder extends AbstractTraceRecorder {
public AbstractRecorderMemory getProcessMemory();
}

View file

@ -1,36 +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.plugin.core.debug.service.model.interfaces;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
@Deprecated(forRemoval = true, since = "11.3")
public interface ManagedStackRecorder {
void offerStackFrame(TargetStackFrame added);
void recordStack();
int getSuccessorFrameLevel(TargetObject successor);
TraceStackFrame getTraceStackFrame(TraceThread traceThread, int level);
TargetStackFrame getTargetStackFrame(int frameLevel);
}

View file

@ -1,55 +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.plugin.core.debug.service.model.interfaces;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.debug.api.model.DebuggerRegisterMapper;
import ghidra.trace.model.thread.TraceThread;
@Deprecated(forRemoval = true, since = "11.3")
public interface ManagedThreadRecorder extends AbstractTraceRecorder {
public TargetThread getTargetThread();
public TraceThread getTraceThread();
public void offerRegisters(TargetRegisterBank added);
public void removeRegisters(TargetRegisterBank removed);
public void offerThreadRegion(TargetMemoryRegion region);
public void recordRegisterValue(TargetRegister targetRegister, byte[] value);
public void recordRegisterValues(TargetRegisterBank bank, Map<String, byte[]> updates);
public void invalidateRegisterValues(TargetRegisterBank bank);
public boolean objectRemoved(TargetObject removed);
public void stateChanged(TargetExecutionState state);
public void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg, boolean b);
public CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank parent);
public ManagedStackRecorder getStackRecorder();
}

View file

@ -1,30 +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.plugin.core.debug.service.model.launch;
import java.util.Collection;
import ghidra.app.services.DebuggerModelService;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.classfinder.ExtensionPoint;
@Deprecated(forRemoval = true, since = "11.3")
public interface DebuggerProgramLaunchOpinion extends ExtensionPoint {
Collection<DebuggerProgramLaunchOffer> getOffers(Program program, PluginTool tool,
DebuggerModelService service);
}

View file

@ -1,91 +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.plugin.core.debug.service.model.record;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.TargetDataTypeConverter;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.data.*;
import ghidra.trace.model.Trace;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public class DataTypeRecorder {
//private TraceRecorder recorder;
private final Trace trace;
private final TargetDataTypeConverter typeConverter;
public DataTypeRecorder(TraceRecorder recorder) {
//this.recorder = recorder;
this.trace = recorder.getTrace();
this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager());
}
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor) {
String path = PathUtils.toString(namespace.getPath());
monitor.setMessage("Capturing data types for " + path);
return namespace.getTypes().thenCompose(types -> {
monitor.initialize(types.size());
AsyncFence fence = new AsyncFence();
List<DataType> converted = new ArrayList<>();
for (TargetNamedDataType type : types) {
if (monitor.isCancelled()) {
fence.ready().cancel(false);
return AsyncUtils.nil();
}
monitor.incrementProgress(1);
fence.include(typeConverter.convertTargetDataType(type).thenAccept(converted::add));
}
return fence.ready().thenApply(__ -> converted);
}).thenAccept(converted -> {
if (converted == null) {
return;
}
try (RecorderPermanentTransaction tid =
RecorderPermanentTransaction.start(trace, "Capture data types for " + path)) {
// NOTE: createCategory is actually getOrCreate
Category category =
trace.getDataTypeManager().createCategory(new CategoryPath("/" + path));
for (DataType dataType : converted) {
category.addDataType(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
});
}
public CompletableFuture<Void> captureDataTypes(TargetModule targetModule,
TaskMonitor monitor) {
CompletableFuture<? extends Map<String, ? extends TargetDataTypeNamespace>> future =
targetModule.fetchChildrenSupporting(TargetDataTypeNamespace.class);
// NOTE: I should expect exactly one namespace...
return future.thenCompose(namespaces -> {
AsyncFence fence = new AsyncFence();
for (TargetDataTypeNamespace ns : namespaces.values()) {
fence.include(captureDataTypes(ns, monitor));
}
return fence.ready();
});
}
}

View file

@ -1,60 +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.plugin.core.debug.service.model.record;
import java.util.Set;
import ghidra.dbg.target.TargetRegister;
import ghidra.debug.api.model.DebuggerRegisterMapper;
import ghidra.program.model.lang.Register;
@Deprecated(forRemoval = true, since = "11.3")
public class EmptyDebuggerRegisterMapper implements DebuggerRegisterMapper {
@Override
public TargetRegister getTargetRegister(String name) {
return null;
}
@Override
public Register getTraceRegister(String name) {
return null;
}
@Override
public TargetRegister traceToTarget(Register register) {
return null;
}
@Override
public Register targetToTrace(TargetRegister tReg) {
return null;
}
@Override
public Set<Register> getRegistersOnTarget() {
return Set.of();
}
@Override
public void targetRegisterAdded(TargetRegister register) {
throw new UnsupportedOperationException();
}
@Override
public void targetRegisterRemoved(TargetRegister register) {
throw new UnsupportedOperationException();
}
}

View file

@ -1,146 +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.plugin.core.debug.service.model.record;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.program.model.address.*;
import ghidra.trace.model.memory.*;
import ghidra.util.Msg;
import utilities.util.IDKeyed;
@Deprecated(forRemoval = true, since = "11.3")
class MemoryRecorder {
protected final ObjectBasedTraceRecorder recorder;
protected final TraceMemoryManager memoryManager;
protected final Map<IDKeyed<AddressSpace>, TargetMemory> memoriesByTargetSpace =
new HashMap<>();
protected final Map<IDKeyed<TargetMemoryRegion>, AddressRange> regions = new HashMap<>();
protected MemoryRecorder(ObjectBasedTraceRecorder recorder) {
this.recorder = recorder;
this.memoryManager = recorder.trace.getMemoryManager();
}
private TargetMemory getMemoryForSpace(AddressSpace space) {
return memoriesByTargetSpace.get(new IDKeyed<>(space));
}
private void addMemoryForSpace(AddressSpace targetSpace, TargetMemory memory) {
TargetMemory exists =
memoriesByTargetSpace.put(new IDKeyed<>(targetSpace), memory);
if (exists != null && exists != memory) {
Msg.warn(this,
"Address space duplicated between memories: " + exists + " and " + memory);
}
}
protected void addRegionMemory(TargetMemoryRegion region, TargetMemory memory) {
addMemoryForSpace(region.getRange().getMinAddress().getAddressSpace(), memory);
}
protected void adjustRegionRange(TargetMemoryRegion region, AddressRange range) {
synchronized (regions) {
AddressRange tRange = recorder.memoryMapper.targetToTrace(range);
if (tRange == null) {
regions.remove(new IDKeyed<>(region));
}
else {
regions.put(new IDKeyed<>(region), tRange);
}
}
}
protected void removeMemory(TargetMemory memory) {
while (memoriesByTargetSpace.values().remove(memory))
;
}
protected void removeRegion(TargetMemoryRegion region) {
synchronized (regions) {
regions.remove(new IDKeyed<>(region));
}
}
protected CompletableFuture<byte[]> read(Address start, int length) {
Address tStart = recorder.memoryMapper.traceToTarget(start);
if (tStart == null) {
return CompletableFuture.completedFuture(new byte[] {});
}
TargetMemory memory = getMemoryForSpace(tStart.getAddressSpace());
if (memory == null) {
return CompletableFuture.completedFuture(new byte[] {});
}
return memory.readMemory(tStart, length);
}
protected CompletableFuture<Void> write(Address start, byte[] data) {
Address tStart = recorder.memoryMapper.traceToTarget(start);
if (tStart == null) {
throw new IllegalArgumentException(
"Address space " + start.getAddressSpace() + " not defined on the target");
}
TargetMemory memory = getMemoryForSpace(tStart.getAddressSpace());
if (memory == null) {
throw new IllegalArgumentException(
"Address space " + tStart.getAddressSpace() +
" cannot be found in target memory");
}
return memory.writeMemory(tStart, data);
}
protected void invalidate(TargetMemory memory, long snap) {
Set<AddressSpace> targetSpaces = memoriesByTargetSpace.entrySet()
.stream()
.filter(e -> e.getValue() == memory)
.map(e -> e.getKey().obj)
.collect(Collectors.toSet());
for (AddressSpace targetSpace : targetSpaces) {
Address traceMin = recorder.memoryMapper.targetToTrace(targetSpace.getMinAddress());
Address traceMax = traceMin.getAddressSpace().getMaxAddress();
memoryManager.setState(snap, traceMin, traceMax, TraceMemoryState.UNKNOWN);
}
}
protected void recordMemory(long snap, Address start, byte[] data) {
memoryManager.putBytes(snap, start, ByteBuffer.wrap(data));
}
public void recordError(long snap, Address tMin, DebuggerMemoryAccessException e) {
// TODO: Bookmark to describe error?
memoryManager.setState(snap, tMin, TraceMemoryState.ERROR);
}
protected boolean isAccessible(TraceMemoryRegion r) {
// TODO: Perhaps a bit aggressive, but haven't really been checking anyway.
return true;
}
public AddressSetView getAccessible() {
synchronized (regions) {
return regions.values()
.stream()
.collect(AddressCollectors.toAddressSet());
}
}
}

View file

@ -1,817 +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.plugin.core.debug.service.model.record;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import db.Transaction;
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.model.PermanentTransactionExecutor;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.error.DebuggerModelAccessException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.module.TraceObjectSection;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.stack.TraceObjectStackFrame;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public class ObjectBasedTraceRecorder implements TraceRecorder {
protected static final int POOL_SIZE = Math.min(16, Runtime.getRuntime().availableProcessors());
protected static final int DELAY_MS = 100;
protected static final int BLOCK_BITS = 12;
protected final Trace trace;
protected final TargetObject target;
protected final ObjectBasedDebuggerTargetTraceMapper mapper;
protected final DebuggerMemoryMapper memoryMapper;
protected final DebuggerRegisterMapper emptyRegisterMapper = new EmptyDebuggerRegisterMapper();
protected final TimeRecorder timeRecorder;
protected final ObjectRecorder objectRecorder;
protected final MemoryRecorder memoryRecorder;
protected final DataTypeRecorder dataTypeRecorder;
protected final SymbolRecorder symbolRecorder;
// upstream listeners
protected final ListenerForRecord listenerForRecord;
protected final ListenerSet<TraceRecorderListener> listeners =
new ListenerSet<>(TraceRecorderListener.class, true);
// TODO: I don't like this here. Should ask the model, not the recorder.
protected TargetObject curFocus;
protected boolean valid = true;
protected class ListenerForRecord extends AnnotatedDebuggerAttributeListener {
private final PermanentTransactionExecutor tx =
new PermanentTransactionExecutor(trace, "OBTraceRecorder: ", POOL_SIZE, DELAY_MS);
private boolean ignoreInvalidation = false;
// TODO: Do I need DebuggerCallbackReorderer?
public ListenerForRecord() {
super(MethodHandles.lookup());
}
@Override
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
String description, List<Object> parameters) {
if (!valid) {
return;
}
if (type == TargetEventType.RUNNING) {
/**
* Do not permit the current snapshot to be invalidated on account of the target
* running. When the STOP occurs, a new (completely UNKNOWN) snapshot is generated.
*/
ignoreInvalidation = true;
return;
/**
* TODO: Perhaps some configuration for this later. It's kind of interesting to
* record the RUNNING event time, but it gets pedantic when these exist between
* steps.
*/
}
/**
* Snapshot creation should not be offloaded to parallel executor or we may confuse
* event snaps.
*/
TraceObjectThread traceEventThread =
objectRecorder.getTraceInterface(eventThread, TraceObjectThread.class);
timeRecorder.createSnapshot(description, traceEventThread, null);
ignoreInvalidation = false;
// NB. Need not worry about CREATED, LOADED, etc. Just recording objects.
}
// NB. ignore executionStateChanged, since recording whole model
@Override
public void invalidateCacheRequested(TargetObject object) {
if (!valid) {
return;
}
if (ignoreInvalidation) {
return;
}
if (object instanceof TargetMemory) {
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Memory invalidated: " + path, () -> {
memoryRecorder.invalidate((TargetMemory) object, snap);
}, path);
}
}
// NB. ignore registersUpdated. All object-based, now.
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = memory.getJoinedPath(".");
Address tAddress = getMemoryMapper().targetToTrace(address);
tx.execute("Memory observed: " + path, () -> {
memoryRecorder.recordMemory(snap, tAddress, data);
}, path);
}
@Override
public void memoryReadError(TargetObject memory, AddressRange range,
DebuggerMemoryAccessException e) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = memory.getJoinedPath(".");
Address tMin = getMemoryMapper().targetToTrace(range.getMinAddress());
tx.execute("Memory read error: " + path, () -> {
memoryRecorder.recordError(snap, tMin, e);
}, path);
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
protected void focusChanged(TargetObject scope, TargetObject focused) {
if (!valid) {
return;
}
// NB. Don't care about ancestry. Focus should be model-wide anyway.
curFocus = focused;
}
@Override
public void created(TargetObject object) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
// Don't offload, because we need a consistent map
try (Transaction trans = trace.openTransaction("Object created: " + path)) {
objectRecorder.recordCreated(snap, object);
}
}
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Object invalidated: " + path, () -> {
objectRecorder.recordInvalidated(snap, object);
}, path);
if (object == target) {
stopRecording();
return;
}
if (object instanceof TargetMemory) {
memoryRecorder.removeMemory((TargetMemory) object);
}
if (object instanceof TargetMemoryRegion) {
memoryRecorder.removeRegion((TargetMemoryRegion) object);
}
}
@Override
public void attributesChanged(TargetObject object, Collection<String> removed,
Map<String, ?> added) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Object attributes changed: " + path, () -> {
objectRecorder.recordAttributes(snap, object, removed, added);
}, path);
super.attributesChanged(object, removed, added);
}
@AttributeCallback(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME)
public void rangeChanged(TargetObject object, AddressRange range) {
if (!valid) {
return;
}
if (!(object instanceof TargetMemoryRegion)) {
return;
}
memoryRecorder.adjustRegionRange((TargetMemoryRegion) object, range);
}
@AttributeCallback(TargetMemoryRegion.MEMORY_ATTRIBUTE_NAME)
public void memoryChanged(TargetObject object, TargetMemory memory) {
if (!valid) {
return;
}
if (!(object instanceof TargetMemoryRegion)) {
return;
}
memoryRecorder.addRegionMemory((TargetMemoryRegion) object, memory);
}
@Override
public void elementsChanged(TargetObject object, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Object elements changed: " + path, () -> {
objectRecorder.recordElements(snap, object, removed, added);
}, path);
}
}
public ObjectBasedTraceRecorder(PluginTool tool, Trace trace, TargetObject target,
ObjectBasedDebuggerTargetTraceMapper mapper) {
trace.addConsumer(this);
this.trace = trace;
this.target = target;
this.mapper = mapper;
// TODO: Don't depend on memory in interface.
// TODO: offerMemory not async
memoryMapper = mapper.offerMemory(null).getNow(null);
timeRecorder = new TimeRecorder(this);
objectRecorder = new ObjectRecorder(this);
memoryRecorder = new MemoryRecorder(this);
dataTypeRecorder = new DataTypeRecorder(this);
symbolRecorder = new SymbolRecorder(this);
listenerForRecord = new ListenerForRecord();
}
@Override
public CompletableFuture<Void> init() {
// TODO: Make this method synchronous?
timeRecorder.createSnapshot("Started recording " + target.getModel(), null, null);
target.getModel().addModelListener(listenerForRecord, true);
return AsyncUtils.nil();
}
@Override
public TargetObject getTarget() {
return target;
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public long getSnap() {
return timeRecorder.getSnap();
}
@Override
public TraceSnapshot forceSnapshot() {
return timeRecorder.forceSnapshot();
}
@Override
public boolean isRecording() {
return valid;
}
@Override
public void stopRecording() {
invalidate();
fireRecordingStopped();
}
protected void invalidate() {
target.getModel().removeModelListener(listenerForRecord);
synchronized (this) {
if (!valid) {
return;
}
valid = false;
}
trace.release(this);
}
@Override
public void addListener(TraceRecorderListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(TraceRecorderListener listener) {
listeners.remove(listener);
}
@Override
public TargetObject getTargetObject(TraceObject obj) {
return objectRecorder.toTarget(obj);
}
@Override
public TraceObject getTraceObject(TargetObject obj) {
return objectRecorder.toTrace(obj);
}
@Override
public TargetObject getTargetObject(TraceObjectKeyPath path) {
return target.getModel().getModelObject(path.getKeyList());
}
@Override
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
return objectRecorder.getTargetInterface(bpt, TraceObjectBreakpointLocation.class,
TargetBreakpointLocation.class);
}
@Override
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
return objectRecorder.getTraceInterface(bpt, TraceObjectBreakpointLocation.class);
}
@Override
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
return objectRecorder.getTargetInterface(region, TraceObjectMemoryRegion.class,
TargetMemoryRegion.class);
}
@Override
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
return objectRecorder.getTraceInterface(region, TraceObjectMemoryRegion.class);
}
@Override
public TargetModule getTargetModule(TraceModule module) {
return objectRecorder.getTargetInterface(module, TraceObjectModule.class,
TargetModule.class);
}
@Override
public TraceModule getTraceModule(TargetModule module) {
return objectRecorder.getTraceInterface(module, TraceObjectModule.class);
}
@Override
public TargetSection getTargetSection(TraceSection section) {
return objectRecorder.getTargetInterface(section, TraceObjectSection.class,
TargetSection.class);
}
@Override
public TraceSection getTraceSection(TargetSection section) {
return objectRecorder.getTraceInterface(section, TraceObjectSection.class);
}
@Override
public TargetThread getTargetThread(TraceThread thread) {
if (thread == null) {
return null;
}
return objectRecorder.getTargetInterface(thread, TraceObjectThread.class,
TargetThread.class);
}
@Override
public TargetExecutionState getTargetThreadState(TargetThread thread) {
return thread.getTypedAttributeNowByName(TargetExecutionStateful.STATE_ATTRIBUTE_NAME,
TargetExecutionState.class, TargetExecutionState.INACTIVE);
}
@Override
public TargetExecutionState getTargetThreadState(TraceThread thread) {
return getTargetThreadState(getTargetThread(thread));
}
@Override
public Set<TargetRegisterBank> getTargetRegisterBanks(TraceThread thread, int frameLevel) {
return Set.of(
objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class));
}
@Override
public TraceThread getTraceThread(TargetThread thread) {
return objectRecorder.getTraceInterface(thread, TraceObjectThread.class);
}
@Override
public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
TraceObject traceObject = objectRecorder.toTrace(successor);
if (traceObject == null) {
return null;
}
return traceObject.queryCanonicalAncestorsInterface(
TraceObjectThread.class).findFirst().orElse(null);
}
@Override
public TraceStackFrame getTraceStackFrame(TargetStackFrame frame) {
return objectRecorder.getTraceInterface(frame, TraceObjectStackFrame.class);
}
@Override
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
TraceObject traceObject = objectRecorder.toTrace(successor);
if (traceObject == null) {
return null;
}
return traceObject.queryCanonicalAncestorsInterface(
TraceObjectStackFrame.class).findFirst().orElse(null);
}
@Override
public TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel) {
return objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetStackFrame.class);
}
@Override
public Set<TargetThread> getLiveTargetThreads() {
return trace.getObjectManager()
.getRootObject()
.querySuccessorsInterface(Lifespan.at(getSnap()), TraceObjectThread.class, true)
.map(t -> objectRecorder.getTargetInterface(t.getObject(), TargetThread.class))
.collect(Collectors.toSet());
}
@Override
public DebuggerRegisterMapper getRegisterMapper(TraceThread thread) {
return emptyRegisterMapper;
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return memoryMapper;
}
@Override
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
// TODO: This seems a little aggressive, but the accessibility thing is already out of hand
return true;
}
@Override
public boolean isRegisterBankAccessible(TraceThread thread, int frameLevel) {
// TODO: This seems a little aggressive, but the accessibility thing is already out of hand
return true;
}
@Override
public AddressSetView getAccessibleMemory() {
return memoryRecorder.getAccessible();
}
protected TargetRegisterContainer getTargetRegisterContainer(TraceThread thread,
int frameLevel) {
if (!(thread instanceof TraceObjectThread tot)) {
throw new AssertionError("thread = " + thread);
}
TraceObject objThread = tot.getObject();
TraceObject regContainer = objThread.queryRegisterContainer(frameLevel);
if (regContainer == null) {
Msg.error(this,
"No register container for " + thread + " and frame " + frameLevel + " in trace");
return null;
}
TargetObject result =
target.getModel().getModelObject(regContainer.getCanonicalPath().getKeyList());
if (result == null) {
Msg.error(this,
"No register container for " + thread + " and frame " + frameLevel + " on target");
return null;
}
return (TargetRegisterContainer) result;
}
@Override
public CompletableFuture<Void> captureThreadRegisters(
TracePlatform platform, TraceThread thread, int frameLevel, Set<Register> registers) {
TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel);
/**
* TODO: Seems I should be able to single out specific registers.... Is this convention
* universal, or do some models allow refreshing on a register-by-register basis? If so,
* what communicates that convention?
*/
if (regContainer == null) {
return AsyncUtils.nil();
}
return regContainer.resync();
}
protected static byte[] encodeValue(int byteLength, BigInteger value) {
return Utils.bigIntegerToBytes(value, byteLength, true);
}
protected TargetRegisterBank isExactRegisterOnTarget(TracePlatform platform,
TargetRegisterContainer regContainer, Register register) {
PathMatcher matcher =
platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), register);
for (TargetObject targetObject : matcher.getCachedSuccessors(regContainer).values()) {
if (!(targetObject instanceof TargetRegister targetRegister)) {
continue;
}
DebuggerObjectModel model = targetRegister.getModel();
List<String> pathBank = model.getRootSchema()
.searchForAncestor(TargetRegisterBank.class, targetRegister.getPath());
if (pathBank == null ||
!(model.getModelObject(pathBank) instanceof TargetRegisterBank targetBank)) {
continue;
}
return targetBank;
}
return null;
}
protected TargetRegisterBank isExactRegisterOnTarget(TracePlatform platform, TraceThread thread,
int frameLevel, Register register) {
TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel);
if (regContainer == null) {
return null;
}
return isExactRegisterOnTarget(platform, regContainer, register);
}
@Override
public Register isRegisterOnTarget(TracePlatform platform, TraceThread thread, int frameLevel,
Register register) {
for (; register != null; register = register.getParentRegister()) {
TargetRegisterBank targetBank =
isExactRegisterOnTarget(platform, thread, frameLevel, register);
if (targetBank != null) {
/**
* TODO: A way to ask the target which registers are modifiable, but
* "isRegisterOnTarget" does not necessarily imply for writing
*/
return register;
}
}
return null;
}
@Override
public CompletableFuture<Void> writeThreadRegisters(TracePlatform platform, TraceThread thread,
int frameLevel, Map<Register, RegisterValue> values) {
TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel);
if (regContainer == null) {
return AsyncUtils.nil();
}
Map<TargetRegisterBank, Map<TargetRegister, byte[]>> writesByBank = new HashMap<>();
for (RegisterValue rv : values.values()) {
Register register = rv.getRegister();
PathMatcher matcher =
platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), register);
Collection<TargetObject> regs = matcher.getCachedSuccessors(regContainer).values();
if (regs.isEmpty()) {
Msg.warn(this, "No register object for " + register);
}
for (TargetObject objRegUntyped : regs) {
TargetRegister objReg = (TargetRegister) objRegUntyped;
List<String> pathBank = objReg.getModel()
.getRootSchema()
.searchForAncestor(TargetRegisterBank.class, objReg.getPath());
if (pathBank == null) {
Msg.warn(this, "No register bank for " + register);
continue;
}
TargetRegisterBank objBank =
(TargetRegisterBank) objReg.getModel().getModelObject(pathBank);
if (objBank == null) {
Msg.warn(this, "No register bank for " + register);
continue;
}
writesByBank.computeIfAbsent(objBank, __ -> new HashMap<>())
.put(objReg, encodeValue(objReg.getByteLength(), rv.getUnsignedValue()));
}
}
AsyncFence fence = new AsyncFence();
for (Map.Entry<TargetRegisterBank, Map<TargetRegister, byte[]>> ent : writesByBank
.entrySet()) {
fence.include(ent.getKey().writeRegisters(ent.getValue()));
}
return fence.ready();
}
@Override
public CompletableFuture<byte[]> readMemory(Address start, int length) {
return memoryRecorder.read(start, length);
}
@Override
public CompletableFuture<Void> writeMemory(Address start, byte[] data) {
return memoryRecorder.write(start, data);
}
@Override
public CompletableFuture<Void> readMemoryBlocks(AddressSetView set, TaskMonitor monitor) {
return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor);
}
@Override
public CompletableFuture<Void> captureDataTypes(TraceModule module, TaskMonitor monitor) {
return dataTypeRecorder.captureDataTypes(getTargetModule(module), monitor);
}
@Override
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor) {
return dataTypeRecorder.captureDataTypes(namespace, monitor);
}
@Override
public CompletableFuture<Void> captureSymbols(TraceModule module, TaskMonitor monitor) {
return symbolRecorder.captureSymbols(getTargetModule(module), monitor);
}
@Override
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
TaskMonitor monitor) {
return symbolRecorder.captureSymbols(namespace, monitor);
}
@Override
public List<TargetBreakpointSpecContainer> collectBreakpointContainers(TargetThread thread) {
if (thread == null) {
return objectRecorder.collectTargetSuccessors(target,
TargetBreakpointSpecContainer.class, false);
}
return objectRecorder.collectTargetSuccessors(thread, TargetBreakpointSpecContainer.class,
false);
}
private class BreakpointConvention {
private final TraceObjectThread thread;
private final TraceObject process;
private BreakpointConvention(TraceObjectThread thread) {
this.thread = thread;
TraceObject object = thread.getObject();
this.process = object
.queryAncestorsTargetInterface(Lifespan.at(getSnap()), TargetProcess.class)
.map(p -> p.getSource(object))
.findFirst()
.orElse(null);
}
private boolean appliesTo(TraceObjectBreakpointLocation loc) {
TraceObject object = loc.getObject();
if (object.queryAncestorsInterface(Lifespan.at(getSnap()), TraceObjectThread.class)
.anyMatch(t -> t == thread)) {
return true;
}
if (process == null) {
return false;
}
return object
.queryAncestorsTargetInterface(Lifespan.at(getSnap()), TargetProcess.class)
.map(p -> p.getSource(object))
.anyMatch(p -> p == process);
}
}
@Override
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
if (thread == null) {
return objectRecorder.collectTargetSuccessors(target, TargetBreakpointLocation.class,
true);
}
BreakpointConvention convention = new BreakpointConvention(
objectRecorder.getTraceInterface(thread, TraceObjectThread.class));
return trace.getObjectManager()
.queryAllInterface(Lifespan.at(getSnap()), TraceObjectBreakpointLocation.class)
.filter(convention::appliesTo)
.map(tl -> objectRecorder.getTargetInterface(tl.getObject(),
TargetBreakpointLocation.class))
.collect(Collectors.toList());
}
@Override
public Set<TraceBreakpointKind> getSupportedBreakpointKinds() {
return objectRecorder
.collectTargetSuccessors(target, TargetBreakpointSpecContainer.class, false)
.stream()
.flatMap(c -> c.getSupportedBreakpointKinds().stream())
.map(k -> TraceRecorder.targetToTraceBreakpointKind(k))
.collect(Collectors.toSet());
}
@Override
public boolean isSupportsFocus() {
return objectRecorder.isSupportsFocus;
}
@Override
public boolean isSupportsActivation() {
return objectRecorder.isSupportsActivation;
}
@Override
public TargetObject getFocus() {
return curFocus;
}
@Override
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
for (TargetFocusScope scope : objectRecorder.collectTargetSuccessors(target,
TargetFocusScope.class, false)) {
if (PathUtils.isAncestor(scope.getPath(), focus.getPath())) {
return scope.requestFocus(focus).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not focus " + focus + ": " + ex.getMessage();
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, msg, ex);
}
return false;
});
}
}
Msg.info(this, "Could not find suitable focus scope for " + focus);
return CompletableFuture.completedFuture(false);
}
@Override
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
for (TargetActiveScope scope : objectRecorder.collectTargetSuccessors(target,
TargetActiveScope.class, false)) {
if (PathUtils.isAncestor(scope.getPath(), active.getPath())) {
return scope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not activate " + active + ": " + ex.getMessage();
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, msg, ex);
}
return false;
});
}
}
Msg.info(this, "Could not find suitable active scope for " + active);
return CompletableFuture.completedFuture(false);
}
// UNUSED?
@Override
public CompletableFuture<Void> flushTransactions() {
return listenerForRecord.tx.flush();
}
protected void fireSnapAdvanced(long key) {
listeners.invoke().snapAdvanced(this, key);
}
protected void fireRecordingStopped() {
listeners.invoke().recordingStopped(this);
}
// TODO: Deprecate/remove the other callbacks: registerBankMapped, *accessibilityChanged
}

View file

@ -1,328 +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.plugin.core.debug.service.model.record;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import db.Transaction;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.TraceUniqueObject;
import ghidra.trace.model.target.*;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import utilities.util.IDKeyed;
@Deprecated(forRemoval = true, since = "11.3")
class ObjectRecorder {
protected final ObjectBasedTraceRecorder recorder;
protected final TraceObjectManager objectManager;
protected final boolean isSupportsFocus;
protected final boolean isSupportsActivation;
private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap =
new DualHashBidiMap<>();
protected ObjectRecorder(ObjectBasedTraceRecorder recorder) {
this.recorder = recorder;
this.objectManager = recorder.trace.getObjectManager();
TargetObjectSchema schema = recorder.target.getSchema();
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
this.isSupportsActivation = !schema.searchFor(TargetActiveScope.class, false).isEmpty();
try (Transaction tx = recorder.trace.openTransaction("Create root")) {
objectManager.createRootObject(schema);
}
}
protected TraceObject toTrace(TargetObject targetObject) {
IDKeyed<TraceObject> traceObject = objectMap.get(new IDKeyed<>(targetObject));
return traceObject == null ? null : traceObject.obj;
}
protected TargetObject toTarget(TraceObject traceObject) {
IDKeyed<TargetObject> targetObject = objectMap.getKey(new IDKeyed<>(traceObject));
return targetObject == null ? null : targetObject.obj;
}
/**
* List the names of interfaces on the object not already covered by the schema
*
* @param object the object
* @return the comma-separated list of interface names
*/
protected String computeExtraInterfaces(TargetObject object) {
Set<String> result = new LinkedHashSet<>(object.getInterfaceNames());
for (Class<? extends TargetObject> iface : object.getSchema().getInterfaces()) {
result.remove(DebuggerObjectModel.requireIfaceName(iface));
}
if (result.isEmpty()) {
return null;
}
return result.stream().collect(Collectors.joining(","));
}
protected void recordCreated(long snap, TargetObject object) {
TraceObject traceObject;
if (object.isRoot()) {
// Already have the root object
traceObject = objectManager.getRootObject();
}
else {
traceObject = objectManager.createObject(TraceObjectKeyPath.of(object.getPath()));
}
synchronized (objectMap) {
IDKeyed<TraceObject> exists =
objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject));
if (exists != null) {
Msg.error(this, "Received created for an object that already exists: " + exists);
}
}
String extras = computeExtraInterfaces(object);
// Note: null extras will erase previous value, if necessary.
traceObject.setAttribute(Lifespan.nowOn(snap),
TraceObject.EXTRA_INTERFACES_ATTRIBUTE_NAME, extras);
}
protected void recordInvalidated(long snap, TargetObject object) {
if (object.isRoot()) {
return;
}
IDKeyed<TraceObject> traceObject;
synchronized (objectMap) {
traceObject = objectMap.remove(new IDKeyed<>(object));
}
if (traceObject == null) {
Msg.error(this, "Unknown object was invalidated: " + object);
return;
}
traceObject.obj.removeTree(Lifespan.nowOn(snap));
}
protected String encodeEnum(Enum<?> e) {
return e.name();
}
protected String encodeEnumSet(Set<? extends Enum<?>> s) {
return s.stream()
.sorted(Comparator.comparing(Enum::ordinal))
.map(Enum::name)
.collect(Collectors.joining(","));
}
protected Object mapAttribute(Object attribute) {
if (attribute instanceof TargetObject) {
TraceObject traceObject = toTrace((TargetObject) attribute);
if (traceObject == null) {
Msg.error(this, "Unknown object appeared as an attribute: " + attribute);
}
return traceObject;
}
if (attribute instanceof Address) {
Address traceAddress = recorder.memoryMapper.targetToTrace((Address) attribute);
if (traceAddress == null) {
Msg.error(this, "Unmappable address appeared as an attribute: " + attribute);
}
return traceAddress;
}
if (attribute instanceof AddressRange) {
AddressRange traceRange = recorder.memoryMapper.targetToTrace((AddressRange) attribute);
if (traceRange == null) {
Msg.error(this, "Unmappable range appeared as an attribute: " + attribute);
}
return traceRange;
}
if (attribute instanceof TargetAttachKind) {
return encodeEnum((TargetAttachKind) attribute);
}
if (attribute instanceof TargetAttachKindSet) {
return encodeEnumSet((TargetAttachKindSet) attribute);
}
if (attribute instanceof TargetBreakpointKind) {
return encodeEnum((TargetBreakpointKind) attribute);
}
if (attribute instanceof TargetBreakpointKindSet) {
return encodeEnumSet((TargetBreakpointKindSet) attribute);
}
if (attribute instanceof TargetDataType dataType) {
// NOTE: some are also TargetObject, but that gets checked first
JsonElement element = dataType.toJson();
return new Gson().toJson(element);
}
if (attribute instanceof TargetExecutionState) {
return encodeEnum((TargetExecutionState) attribute);
}
if (attribute instanceof TargetParameterMap) {
return "[parameter map not recorded]";
}
if (attribute instanceof TargetStepKind) {
return encodeEnum((TargetStepKind) attribute);
}
if (attribute instanceof TargetStepKindSet) {
return encodeEnumSet((TargetStepKindSet) attribute);
}
return attribute;
}
protected void recordAttributes(long snap, TargetObject object, Collection<String> removed,
Map<String, ?> added) {
TraceObject traceObject;
Map<String, Object> traceAdded = new HashMap<>();
synchronized (objectMap) {
traceObject = toTrace(object);
if (traceObject == null) {
Msg.error(this, "Unknown object had attributes changed: " + object);
return;
}
for (Map.Entry<String, ?> entry : added.entrySet()) {
Object value = mapAttribute(entry.getValue());
if (value == null) {
continue;
}
traceAdded.put(entry.getKey(), value);
}
}
for (Map.Entry<String, Object> entry : traceAdded.entrySet()) {
traceObject.setAttribute(Lifespan.nowOn(snap), entry.getKey(), entry.getValue());
}
}
protected TraceObject mapElement(TargetObject element) {
TraceObject traceObject = toTrace(element);
if (traceObject == null) {
Msg.error(this, "Unknown object appeared as an element: " + element);
return null;
}
return traceObject;
}
protected void recordElements(long snap, TargetObject object, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
TraceObject traceObject;
Map<String, Object> traceAdded = new HashMap<>();
synchronized (objectMap) {
traceObject = toTrace(object);
if (traceObject == null) {
Msg.error(this, "Unknown object had attributes changed: " + object);
return;
}
for (Map.Entry<String, ? extends TargetObject> entry : added.entrySet()) {
Object value = mapElement(entry.getValue());
if (value == null) {
continue;
}
traceAdded.put(entry.getKey(), value);
}
}
for (Map.Entry<String, Object> entry : traceAdded.entrySet()) {
traceObject.setElement(Lifespan.nowOn(snap), entry.getKey(), entry.getValue());
}
}
protected <T extends TargetObject, I extends TraceObjectInterface> T getTargetInterface(
TraceUniqueObject traceUnique, Class<I> traceObjectIf, Class<T> targetObjectIf) {
if (!traceObjectIf.isAssignableFrom(traceUnique.getClass())) {
return null;
}
TraceObject traceObject = traceObjectIf.cast(traceUnique).getObject();
return getTargetInterface(traceObject, targetObjectIf);
}
protected <T extends TargetObject> T getTargetInterface(TraceObject traceObject,
Class<T> targetObjectIf) {
TargetObject targetObject = toTarget(traceObject);
return targetObject == null ? null : targetObject.as(targetObjectIf);
}
protected <I extends TraceObjectInterface> I getTraceInterface(TargetObject targetObject,
Class<I> traceObjectIf) {
TraceObject traceObject = toTrace(targetObject);
return traceObject == null ? null : traceObject.queryInterface(traceObjectIf);
}
protected <T extends TargetObject> T getTargetFrameInterface(TraceThread thread, int frameLevel,
Class<T> targetObjectIf) {
if (thread == null) {
return null;
}
TraceObject object = ((TraceObjectThread) thread).getObject();
PathMatcher matcher = object.getTargetSchema().searchFor(targetObjectIf, false);
PathPattern pattern = matcher.getSingletonPattern();
if (pattern == null) {
return null;
}
PathPredicates applied;
if (pattern.countWildcards() == 0) {
if (frameLevel != 0) {
return null;
}
applied = pattern;
}
else if (pattern.countWildcards() == 1) {
applied = pattern.applyIntKeys(frameLevel);
}
else {
return null;
}
TraceObjectValPath found = object
.getSuccessors(Lifespan.at(recorder.getSnap()), applied)
.findAny()
.orElse(null);
if (found == null) {
return null;
}
TraceObject last = found.getDestination(null);
if (last == null) {
return null;
}
return getTargetInterface(last, targetObjectIf);
}
protected <T extends TargetObject> List<T> collectTargetSuccessors(TargetObject targetSeed,
Class<T> targetIf, boolean requireCanonical) {
// TODO: Should this really go through the database?
TraceObject seed = toTrace(targetSeed);
if (seed == null) {
return List.of();
}
return seed.querySuccessorsTargetInterface(Lifespan.at(recorder.getSnap()), targetIf,
requireCanonical)
.map(p -> toTarget(p.getDestination(seed)).as(targetIf))
.collect(Collectors.toList());
}
}

View file

@ -1,76 +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.plugin.core.debug.service.model.record;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public enum RecorderUtils {
INSTANCE;
public AddressSetView quantize(int blockBits, AddressSetView set) {
if (blockBits == 1) {
return set;
}
long blockMask = -1L << blockBits;
AddressSet result = new AddressSet();
// Not terribly efficient, but this is one range most of the time
for (AddressRange range : set) {
AddressSpace space = range.getAddressSpace();
Address min = space.getAddress(range.getMinAddress().getOffset() & blockMask);
Address max = space.getAddress(range.getMaxAddress().getOffset() | ~blockMask);
result.add(new AddressRangeImpl(min, max));
}
return result;
}
public CompletableFuture<Void> readMemoryBlocks(
TraceRecorder recorder, int blockBits, AddressSetView set, TaskMonitor monitor) {
// NOTE: I don't intend to warn about the number of requests.
// They're delivered in serial, and there's a cancel button that works
int blockSize = 1 << blockBits;
int total = 0;
AddressSetView expSet = quantize(blockBits, set);
for (AddressRange r : expSet) {
total += Long.divideUnsigned(r.getLength() + blockSize - 1, blockSize);
}
monitor.initialize(total);
monitor.setMessage("Reading memory");
// TODO: Read blocks in parallel? Probably NO. Tends to overload the connector.
return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> {
AddressRangeChunker blocks = new AddressRangeChunker(r, blockSize);
AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (blk, inner) -> {
// The listener in the recorder will copy to the Trace.
monitor.incrementProgress(1);
CompletableFuture<byte[]> future =
recorder.readMemory(blk.getMinAddress(), (int) blk.getLength());
future.exceptionally(e -> {
Msg.error(this, "Could not read " + blk + ": " + e);
return null; // Continue looping on errors
}).thenApply(__ -> !monitor.isCancelled()).handle(inner::repeatWhile);
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
});
}
}

View file

@ -1,140 +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.plugin.core.debug.service.model.record;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.address.Address;
import ghidra.program.model.symbol.SourceType;
import ghidra.trace.model.Trace;
import ghidra.trace.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
@Deprecated(forRemoval = true, since = "11.3")
public class SymbolRecorder {
private final TraceRecorder recorder;
private final Trace trace;
public SymbolRecorder(TraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
}
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
TaskMonitor monitor) {
String path = PathUtils.toString(namespace.getPath());
monitor.setMessage("Capturing symbols for " + path);
return namespace.getSymbols().thenAccept(symbols -> {
try (RecorderPermanentTransaction tid = RecorderPermanentTransaction.start(trace,
"Capture types and symbols for " + path)) {
TraceNamespaceSymbol ns = createNamespaceIfAbsent(path);
monitor.setMessage("Capturing symbols for " + path);
monitor.initialize(symbols.size());
TraceEquateManager equateManager = trace.getEquateManager();
for (TargetSymbol sym : symbols) {
if (monitor.isCancelled()) {
return;
}
monitor.incrementProgress(1);
String symName = sym.getIndex();
if (sym.isConstant()) {
// TODO: Equate namespaces?
TraceEquate equate = equateManager.getByName(symName);
long symVal = sym.getValue().getOffset();
if (equate != null && equate.getValue() == symVal) {
continue;
}
try {
equateManager.create(symName, symVal);
}
catch (DuplicateNameException | IllegalArgumentException e) {
Msg.error(this, "Could not create equate: " + symName, e);
}
continue;
}
Address addr = recorder.getMemoryMapper().targetToTrace(sym.getValue());
try {
trace.getSymbolManager()
.labels()
.create(recorder.getSnap(), null, addr, symName, ns,
SourceType.IMPORTED);
}
catch (InvalidInputException e) {
Msg.error(this, "Could not add module symbol " + sym + ": " + e);
}
/**
* TODO: Lay down data type, if present
*
* TODO: Interpret "address" type correctly. A symbol with this type is itself
* the pointer. In other words, it is not specifying the type to lay down in
* memory.
*/
}
}
});
}
public CompletableFuture<Void> captureSymbols(TargetModule targetModule,
TaskMonitor monitor) {
CompletableFuture<? extends Map<String, ? extends TargetSymbolNamespace>> future =
targetModule.fetchChildrenSupporting(TargetSymbolNamespace.class);
// NOTE: I should expect exactly one namespace...
return future.thenCompose(namespaces -> {
AsyncFence fence = new AsyncFence();
for (TargetSymbolNamespace ns : namespaces.values()) {
fence.include(captureSymbols(ns, monitor));
}
return fence.ready();
});
}
private TraceNamespaceSymbol createNamespaceIfAbsent(String path) {
TraceSymbolManager symbolManager = trace.getSymbolManager();
try {
return symbolManager.namespaces()
.add(path, symbolManager.getGlobalNamespace(), SourceType.IMPORTED);
}
catch (DuplicateNameException e) {
Msg.info(this, "Namespace for module " + path +
" already exists or another exists with a conflicting name. Using the existing one: " +
e);
TraceNamespaceSymbol ns = symbolManager.namespaces().getGlobalNamed(path);
if (ns != null) {
return ns;
}
Msg.error(this, "Existing namespace for " + path +
" is not a plain namespace. Using global namespace.");
return symbolManager.getGlobalNamespace();
}
catch (InvalidInputException | IllegalArgumentException e) {
Msg.error(this,
"Could not create namespace for new module: " + path + ". Using global namespace.",
e);
return symbolManager.getGlobalNamespace();
}
}
}

View file

@ -1,67 +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.plugin.core.debug.service.model.record;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
@Deprecated(forRemoval = true, since = "11.3")
class TimeRecorder {
protected final ObjectBasedTraceRecorder recorder;
protected TraceSnapshot snapshot = null;
protected TimeRecorder(ObjectBasedTraceRecorder recorder) {
this.recorder = recorder;
}
protected TraceSnapshot getSnapshot() {
return snapshot;
}
protected long getSnap() {
return snapshot.getKey();
}
protected synchronized TraceSnapshot doCreateSnapshot(String description,
TraceThread eventThread) {
snapshot = recorder.trace.getTimeManager().createSnapshot(description);
snapshot.setEventThread(eventThread);
return snapshot;
}
protected TraceSnapshot createSnapshot(String description, TraceThread eventThread,
RecorderPermanentTransaction tid) {
TraceSnapshot snapshot;
if (tid != null) {
snapshot = doCreateSnapshot(description, eventThread);
}
else {
try (RecorderPermanentTransaction tid2 =
RecorderPermanentTransaction.start(recorder.trace, description)) {
snapshot = doCreateSnapshot(description, eventThread);
}
}
recorder.fireSnapAdvanced(snapshot.getKey());
return snapshot;
}
protected TraceSnapshot forceSnapshot() {
return createSnapshot("User-forced snapshot", null, null);
}
}

View file

@ -222,12 +222,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public void targetWithdrawn(Target target) {
Swing.runLater(() -> updateCurrentTarget());
boolean save = isSaveTracesByDefault();
CompletableFuture<Void> flush = save
? waitUnlockedDebounced(target)
: AsyncUtils.nil();
flush.thenRunAsync(() -> {
updateCurrentTarget();
if (!isAutoCloseOnTerminate()) {
return;
}
@ -561,13 +561,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
newCurrent = newCurrent == null ? DebuggerCoordinates.NOWHERE : newCurrent;
newCurrent = fillInTarget(newCurrent.getTrace(), newCurrent);
newCurrent = fillInPlatform(newCurrent);
if (cause == ActivationCause.START_RECORDING || cause == ActivationCause.FOLLOW_PRESENT) {
if (cause == ActivationCause.TARGET_UPDATED || cause == ActivationCause.FOLLOW_PRESENT) {
Target target = newCurrent.getTarget();
if (target != null) {
newCurrent = newCurrent.snap(target.getSnap());
}
}
newCurrent = validateCoordiantes(newCurrent, cause);
newCurrent = validateCoordinates(newCurrent, cause);
if (newCurrent == null || !doSetCurrent(newCurrent)) {
return null;
}
@ -589,7 +589,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return controlService == null ? ControlMode.DEFAULT : controlService.getCurrentMode(trace);
}
private DebuggerCoordinates validateCoordiantes(DebuggerCoordinates coordinates,
private DebuggerCoordinates validateCoordinates(DebuggerCoordinates coordinates,
ActivationCause cause) {
ControlMode mode = getEffectiveControlMode(coordinates.getTrace());
return mode.validateCoordinates(tool, coordinates, cause);
@ -633,11 +633,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
protected void updateCurrentTarget() {
Target target = computeTarget(current.getTrace());
if (target == null) {
if (target == null && current.getTarget() == null) {
return;
}
DebuggerCoordinates toActivate = current.target(target);
activate(toActivate, ActivationCause.FOLLOW_PRESENT);
activate(toActivate, ActivationCause.TARGET_UPDATED);
}
@Override
@ -1123,8 +1123,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
if (cause == ActivationCause.FOLLOW_PRESENT) {
if (!isFollowsPresent(newTrace)) {
if (cause == ActivationCause.FOLLOW_PRESENT || cause == ActivationCause.TARGET_UPDATED) {
if (!isFollowsPresent(newTrace) && cause == ActivationCause.FOLLOW_PRESENT) {
return AsyncUtils.nil();
}
if (current.getTrace() != newTrace) {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,7 +22,7 @@ import org.junit.*;
import db.Transaction;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.service.control.MockTarget;
import ghidra.app.plugin.core.debug.service.MockTarget;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.target.DebuggerTargetServicePlugin;

View file

@ -22,10 +22,8 @@ import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@ -44,26 +42,19 @@ import docking.action.DockingActionIf;
import docking.widgets.table.DynamicTableColumn;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.Unique;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectPropertyColumn;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
import ghidra.app.plugin.core.debug.service.target.DebuggerTargetServicePlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.model.AbstractTestTargetRegisterBank;
import ghidra.dbg.model.TestDebuggerModelBuilder;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.debug.api.action.*;
import ghidra.debug.api.model.*;
import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.action.LocationTrackingSpecFactory;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
@ -76,11 +67,7 @@ import ghidra.program.util.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.trace.util.TraceEvents;
import ghidra.trace.model.Trace;
import ghidra.util.InvalidNameException;
import ghidra.util.NumericUtilities;
import ghidra.util.datastruct.TestDataStructureErrorHandlerInstaller;
@ -92,23 +79,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
public static final String LANGID_TOYBE64 = "Toy:BE:64:default";
public static class TestDebuggerTargetTraceMapper extends DefaultDebuggerTargetTraceMapper {
public TestDebuggerTargetTraceMapper(TargetObject target)
throws LanguageNotFoundException, CompilerSpecNotFoundException {
super(target, new LanguageID(LANGID_TOYBE64), new CompilerSpecID("default"), Set.of());
}
@Override
protected DebuggerMemoryMapper createMemoryMapper(TargetMemory memory) {
return new DefaultDebuggerMemoryMapper(language, memory.getModel());
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, true);
}
}
protected static byte[] arr(String hex) {
return NumericUtilities.convertStringToBytes(hex);
}
@ -258,11 +228,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
}
}
protected static TargetBreakpointSpecContainer getBreakpointContainer(TraceRecorder r) {
return waitFor(() -> Unique.assertAtMostOne(r.collectBreakpointContainers(null)),
"No container");
}
// TODO: Propose this replace waitForProgram
public static void waitForDomainObject(DomainObject object) {
object.flushEvents();
@ -582,13 +547,10 @@ public abstract class AbstractGhidraHeadedDebuggerTest
protected TestEnv env;
protected PluginTool tool;
protected DebuggerModelService modelService;
protected DebuggerModelServiceInternal modelServiceInternal;
protected DebuggerTargetService targetService;
protected DebuggerTraceManagerService traceManager;
protected ProgramManager programManager;
protected TestDebuggerModelBuilder mb;
protected ToyDBTraceBuilder tb;
protected Program program;
@ -597,25 +559,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
protected final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
protected void waitRecorder(TraceRecorder recorder) throws Throwable {
if (recorder == null) {
return;
}
try {
waitOn(recorder.getTarget().getModel().flushEvents());
}
catch (RejectedExecutionException e) {
// Whatever
}
try {
waitOn(recorder.flushTransactions());
}
catch (RejectedExecutionException e) {
// Whatever
}
waitForDomainObject(recorder.getTrace());
}
@BeforeClass
public static void beforeClass() {
// Note: we decided to move this up to a framework-level base test class
@ -624,15 +567,9 @@ public abstract class AbstractGhidraHeadedDebuggerTest
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
DebuggerModelServiceProxyPlugin modelPlugin =
addPlugin(tool, DebuggerModelServiceProxyPlugin.class);
modelService = tool.getService(DebuggerModelService.class);
assertEquals(modelPlugin, modelService);
modelServiceInternal = modelPlugin;
targetService = addPlugin(tool, DebuggerTargetServicePlugin.class);
addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
@ -641,9 +578,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
programManager = tool.getService(ProgramManager.class);
env.showTool();
// Need this for the factory
mb = new TestDebuggerModelBuilder();
}
@After
@ -664,15 +598,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
tb.close();
}
if (mb != null) {
if (mb.testModel != null) {
modelService.removeModel(mb.testModel);
for (TraceRecorder recorder : modelService.getTraceRecorders()) {
recorder.stopRecording();
}
}
}
if (program != null) {
programManager.closeAllPrograms(true);
program.release(this);
@ -685,40 +610,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
}
}
protected void createTestModel() throws Exception {
mb.createTestModel();
modelService.addModel(mb.testModel);
}
protected void populateTestModel() throws Throwable {
mb.createTestProcessesAndThreads();
// NOTE: Test mapper uses TOYBE64
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
mb.createTestThreadRegisterBanks();
mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx");
mb.testProcess1.addRegion(".data", mb.rng(0x00600000, 0x00601000), "rw");
}
protected TargetObject chooseTarget() {
return mb.testProcess1;
}
protected TraceRecorder recordAndWaitSync() throws Throwable {
createTestModel();
populateTestModel();
TargetObject target = chooseTarget();
TraceRecorder recorder = modelService.recordTarget(target, createTargetTraceMapper(target),
ActionSource.AUTOMATIC);
waitRecorder(recorder);
return recorder;
}
protected void nop() {
}
protected void intoProject(DomainObject obj) {
waitForDomainObject(obj);
DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
@ -764,17 +655,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
tb = new ToyDBTraceBuilder(trace);
}
protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target)
throws Exception {
return new TestDebuggerTargetTraceMapper(target) {
@Override
public TraceRecorder startRecording(PluginTool tool, Trace trace) {
useTrace(trace);
return super.startRecording(tool, trace);
}
};
}
protected void createAndOpenTrace(String langID) throws IOException {
createTrace(langID);
traceManager.openTrace(tb.trace);
@ -823,53 +703,6 @@ public abstract class AbstractGhidraHeadedDebuggerTest
programManager.openProgram(program);
}
protected void setRegistersAndWaitForRecord(AbstractTestTargetRegisterBank<?> bank,
Map<String, byte[]> values, long timeoutMillis) throws Exception {
TraceThread traceThread = modelService.getTraceThread(bank.getThread());
assertNotNull(traceThread);
Trace trace = traceThread.getTrace();
TraceRecorder recorder = modelService.getRecorder(trace);
CompletableFuture<Void> observedTraceChange = new CompletableFuture<>();
TraceDomainObjectListener listener = new TraceDomainObjectListener() {
{
listenFor(TraceEvents.BYTES_CHANGED, this::bytesChanged);
}
void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, byte[] oldValue,
byte[] newValue) {
if (space.getThread() != traceThread) {
return;
}
TraceMemorySpace regSpace =
trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false);
assertNotNull(regSpace);
for (Map.Entry<String, byte[]> ent : values.entrySet()) {
String regName = ent.getKey();
Register register = trace.getBaseLanguage().getRegister(regName);
RegisterValue recorded = regSpace.getValue(recorder.getSnap(), register);
RegisterValue expected =
new RegisterValue(register, new BigInteger(1, ent.getValue()));
if (!recorded.equals(expected)) {
continue;
}
}
observedTraceChange.complete(null);
}
};
try {
trace.addListener(listener);
// get() is not my favorite, but it'll do for testing
// can't remove listener until observedTraceChange has completed.
bank.writeRegistersNamed(values)
.thenCompose(__ -> observedTraceChange)
.get(timeoutMillis, TimeUnit.MILLISECONDS);
}
finally {
trace.removeListener(listener);
}
}
protected File pack(DomainObject object) throws Exception {
File tempDir = Files.createTempDirectory("ghidra-" + name.getMethodName()).toFile();
File pack = new File(tempDir, "obj" + System.identityHashCode(object) + ".gzf");

View file

@ -1,790 +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.plugin.core.debug.gui.breakpoint;
import static org.junit.Assert.*;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import db.Transaction;
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*;
import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryConflictException;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebuggerTest {
protected static final long TIMEOUT_MILLIS =
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
protected DebuggerBreakpointsPlugin breakpointsPlugin;
protected DebuggerBreakpointsProvider breakpointsProvider;
protected DebuggerStaticMappingService mappingService;
protected DebuggerLogicalBreakpointService breakpointService;
@Before
public void setUpBreakpointsProviderTest() throws Exception {
breakpointsPlugin = addPlugin(tool, DebuggerBreakpointsPlugin.class);
breakpointsProvider = waitForComponentProvider(DebuggerBreakpointsProvider.class);
mappingService = tool.getService(DebuggerStaticMappingService.class);
breakpointService = tool.getService(DebuggerLogicalBreakpointService.class);
}
protected void addMapping(Trace trace, Program prog) throws Exception {
try (Transaction tx = trace.openTransaction("Add mapping")) {
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace, null, Lifespan.nowOn(0), addr(trace, 0x55550000)),
new ProgramLocation(prog, addr(prog, 0x00400000)), 0x1000, false);
}
}
protected void addLiveMemoryAndBreakpoint(TestTargetProcess process, TraceRecorder recorder)
throws Exception {
process.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
addLiveBreakpoint(recorder, 0x55550123);
}
protected void addLiveBreakpoint(TraceRecorder recorder, long offset) throws Exception {
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
protected void addStaticMemoryAndBreakpoint() throws LockException, MemoryConflictException,
AddressOverflowException, CancelledException {
try (Transaction tx = program.openTransaction("Add bookmark break")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00400000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);
program.getBookmarkManager()
.setBookmark(addr(program, 0x00400123), LogicalBreakpoint.ENABLED_BOOKMARK_TYPE,
"SW_EXECUTE;1", "");
}
}
protected void assertProviderEmpty() {
assertTrue(breakpointsProvider.breakpointTableModel.getModelData().isEmpty());
}
@Test
public void testEmpty() throws Exception {
waitForSwing();
assertProviderEmpty();
}
@Test
public void testAddLiveOpenTracePopulatesProvider() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
waitRecorder(recorder);
// NB, optionally open trace. Mapping only works if open...
traceManager.openTrace(trace);
waitForSwing();
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals("55550123", row.getAddress().toString());
assertEquals(trace, row.getDomainObject());
assertEquals("SW_EXECUTE", row.getKinds());
assertEquals(State.INCONSISTENT_ENABLED, row.getState());
}
@Test
public void testToggleLiveViaTable() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
waitForDomainObject(trace);
traceManager.openTrace(trace);
waitForSwing();
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals(State.INCONSISTENT_ENABLED, row.getState());
// NB, the row does not take the value immediately, but via async callbacks
row.setEnabled(false);
waitForPass(() -> assertEquals(State.INCONSISTENT_DISABLED, row.getState()));
row.setEnabled(true);
waitForPass(() -> assertEquals(State.INCONSISTENT_ENABLED, row.getState()));
}
@Test
public void testOpenProgramAddBookmarkPopulatesProvider() throws Exception {
createProgram();
programManager.openProgram(program);
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals("00400123", row.getAddress().toString());
assertEquals(program, row.getDomainObject());
assertEquals("SW_EXECUTE", row.getKinds());
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
}
@Test
public void testToggleStaticViaTable() throws Exception {
createProgram();
programManager.openProgram(program);
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
row.setEnabled(false); // Synchronous, but on swing thread
waitForDomainObject(program);
assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
row.setEnabled(true);
waitForDomainObject(program);
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
}
@Test
public void testEnablementColumnMapped() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
createProgramFromTrace(trace);
intoProject(trace);
intoProject(program);
addMapping(trace, program);
addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
addStaticMemoryAndBreakpoint();
programManager.openProgram(program);
traceManager.openTrace(trace);
// Because mapping service debounces, wait for breakpoints to be reconciled
waitForPass(() -> {
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
LogicalBreakpoint lb = row.getLogicalBreakpoint();
assertEquals(program, lb.getProgram());
assertEquals(Set.of(trace), lb.getParticipatingTraces());
assertEquals(State.ENABLED, row.getState());
});
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
LogicalBreakpoint lb = row.getLogicalBreakpoint();
lb.disableForProgram();
waitForDomainObject(program);
waitForPass(() -> assertEquals(State.INCONSISTENT_DISABLED, row.getState()));
// NOTE: This acts on the corresponding target, not directly on trace
waitOn(lb.disableForTrace(trace));
waitRecorder(recorder);
waitForPass(() -> assertEquals(State.DISABLED, row.getState()));
lb.enableForProgram();
waitForDomainObject(program);
waitForPass(() -> assertEquals(State.INCONSISTENT_ENABLED, row.getState()));
// This duplicates the initial case, but without it, I just feel incomplete
waitOn(lb.enableForTrace(trace));
waitRecorder(recorder);
waitForPass(() -> assertEquals(State.ENABLED, row.getState()));
}
@Test
public void testRenameStaticViaTable() throws Exception {
createProgram();
programManager.openProgram(program);
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals("", row.getName());
row.setName("Test name");
waitForDomainObject(program);
assertEquals("Test name", row.getName());
// Check that name persists, since bookmark is swapped
row.setEnabled(false);
waitForDomainObject(program);
assertEquals("Test name", row.getName());
}
// TODO: Test a scenario where one spec manifests two breaks, select both, and perform actions
// TODO: Test a scenario where one spec manifests the same mapped breakpoint in two traces
@Test
public void testActionEnableSelectedBreakpoints() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
row.setEnabled(false);
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
performAction(breakpointsProvider.actionEnableSelectedBreakpoints);
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointTable.clearSelection();
waitForSwing();
assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
// Bookmark part should actually be synchronous.
waitOn(row.getLogicalBreakpoint().delete());
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
}
@Test
public void testActionEnableAllBreakpoints() throws Exception {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
row.setEnabled(false);
waitForSwing();
assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
performAction(breakpointsProvider.actionEnableAllBreakpoints);
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
// Bookmark part should actually be synchronous.
row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
}
@Test
public void testActionDisableSelectedBreakpoints() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
waitForPass(() -> assertEquals(State.INEFFECTIVE_ENABLED, row.getState()));
assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
performAction(breakpointsProvider.actionDisableSelectedBreakpoints);
waitForPass(() -> assertEquals(State.INEFFECTIVE_DISABLED, row.getState()));
assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointTable.clearSelection();
waitForSwing();
assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
// Bookmark part should actually be synchronous.
waitOn(row.getLogicalBreakpoint().delete());
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
}
@Test
public void testActionDisableAllBreakpoints() throws Exception {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
performAction(breakpointsProvider.actionDisableAllBreakpoints);
assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
// Bookmark part should actually be synchronous.
row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
}
@Test
public void testActionClearSelectedBreakpoints() throws Exception {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointTable.clearSelection();
waitForSwing();
assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
performAction(breakpointsProvider.actionClearSelectedBreakpoints);
assertProviderEmpty();
assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
}
@Test
public void testActionClearAllBreakpoints() throws Exception {
createProgram();
programManager.openProgram(program);
waitForSwing();
assertFalse(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint();
waitForDomainObject(program);
assertTrue(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
performAction(breakpointsProvider.actionClearAllBreakpoints);
assertProviderEmpty();
assertFalse(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
}
@Test
public void testActionMakeBreakpointsEffective() throws Exception {
DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
createProgramFromTrace(trace);
intoProject(trace);
intoProject(program);
assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
programManager.openProgram(program);
assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
traceManager.openTrace(trace);
assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
addStaticMemoryAndBreakpoint();
assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
addMapping(trace, program);
waitForPass(() -> {
assertTrue(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
assertEquals(1,
consolePlugin.getRowCount(DebuggerMakeBreakpointsEffectiveActionContext.class));
});
performAction(breakpointsProvider.actionMakeBreakpointsEffective);
waitForPass(() -> {
assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
assertEquals(0,
consolePlugin.getRowCount(DebuggerMakeBreakpointsEffectiveActionContext.class));
});
}
protected static <R> List<R> copyModelData(
RowWrappedEnumeratedColumnTableModel<?, ?, R, ?> model) {
synchronized (model) {
return List.copyOf(model.getModelData());
}
}
@Test
public void testActionFilters() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder1 = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace1 = recorder1.getTrace();
TraceRecorder recorder3 = modelService.recordTarget(mb.testProcess3,
createTargetTraceMapper(mb.testProcess3), ActionSource.AUTOMATIC);
Trace trace3 = recorder3.getTrace();
createProgramFromTrace(trace1);
intoProject(trace1);
intoProject(trace3);
intoProject(program);
addMapping(trace1, program);
addMapping(trace3, program);
addLiveMemoryAndBreakpoint(mb.testProcess1, recorder1);
addLiveBreakpoint(recorder1, 0x55550321);
addLiveMemoryAndBreakpoint(mb.testProcess3, recorder3);
addLiveBreakpoint(recorder3, 0x55550321);
waitRecorder(recorder1);
waitRecorder(recorder3);
addStaticMemoryAndBreakpoint();
// Note, no program breakpoint for 0321...
programManager.openProgram(program);
traceManager.openTrace(trace1);
CompletableFuture<Void> mappingsSettled = mappingService.changesSettled();
CompletableFuture<Void> breakpointsSettled = breakpointService.changesSettled();
traceManager.openTrace(trace3);
waitForSwing();
waitOn(mappingsSettled);
waitOn(breakpointsSettled);
waitForSwing();
LogicalBreakpointTableModel bptModel = breakpointsProvider.breakpointTableModel;
List<LogicalBreakpointRow> data = copyModelData(bptModel);
assertEquals(2, data.size());
LogicalBreakpointRow row1 = data.get(0);
LogicalBreakpointRow row2 = data.get(1);
LogicalBreakpoint lb1 = row1.getLogicalBreakpoint();
LogicalBreakpoint lb2 = row2.getLogicalBreakpoint();
assertEquals(program, lb1.getProgram());
assertEquals(program, lb2.getProgram());
assertEquals(addr(program, 0x00400123), lb1.getAddress());
assertEquals(addr(program, 0x00400321), lb2.getAddress());
assertEquals(Set.of(trace1, trace3), lb1.getParticipatingTraces());
assertEquals(Set.of(trace1, trace3), lb2.getParticipatingTraces());
// Sanity check / experiment: Equal fields, but from different traces
TraceBreakpoint bl1t1 = Unique.assertOne(lb1.getTraceBreakpoints(trace1));
TraceBreakpoint bl1t3 = Unique.assertOne(lb1.getTraceBreakpoints(trace3));
assertNotEquals(bl1t1, bl1t3);
// OK, back to work
assertEquals(2, lb1.getTraceBreakpoints().size());
assertEquals(2, lb2.getTraceBreakpoints().size());
List<BreakpointLocationRow> filtLocs =
breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
for (LogicalBreakpointRow breakRow : data) {
assertEquals(2, breakRow.getLocationCount());
}
assertEquals(4, filtLocs.size());
assertTrue(breakpointsProvider.actionFilterByCurrentTrace.isEnabled());
performAction(breakpointsProvider.actionFilterByCurrentTrace);
// No trace active, so empty :)
data = copyModelData(bptModel);
filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
for (LogicalBreakpointRow breakRow : data) {
assertEquals(0, breakRow.getLocationCount());
}
assertEquals(0, filtLocs.size());
traceManager.activateTrace(trace1);
waitForSwing();
data = copyModelData(bptModel);
filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
for (LogicalBreakpointRow breakRow : data) {
assertEquals(1, breakRow.getLocationCount());
}
assertEquals(2, filtLocs.size());
assertTrue(breakpointsProvider.actionFilterLocationsByBreakpoints.isEnabled());
performAction(breakpointsProvider.actionFilterLocationsByBreakpoints);
// No breakpoint selected, so no change, yet.
data = copyModelData(bptModel);
filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
for (LogicalBreakpointRow breakRow : data) {
assertEquals(1, breakRow.getLocationCount());
}
assertEquals(2, filtLocs.size());
LogicalBreakpointRow bpRow = data.get(0);
runSwing(() -> breakpointsProvider
.setSelectedBreakpoints(Set.of(bpRow.getLogicalBreakpoint())));
waitForSwing();
filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
assertEquals(1, filtLocs.size());
assertTrue(breakpointsProvider.actionFilterByCurrentTrace.isEnabled());
performAction(breakpointsProvider.actionFilterByCurrentTrace);
data = copyModelData(bptModel);
filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
for (LogicalBreakpointRow breakRow : data) {
assertEquals(2, breakRow.getLocationCount());
}
assertEquals(2, filtLocs.size());
}
public static final Set<String> POPUP_ACTIONS = Set.of(
AbstractEnableSelectedBreakpointsAction.NAME, AbstractDisableSelectedBreakpointsAction.NAME,
AbstractClearSelectedBreakpointsAction.NAME);
@Test
public void testPopupActionsOnBreakpointSelections() throws Exception {
createProgram();
programManager.openProgram(program);
waitForSwing();
addStaticMemoryAndBreakpoint();
waitForProgram(program);
// NOTE: the row becomes selected by right-click
clickTableCellWithButton(breakpointsProvider.breakpointTable, 0, 0, MouseEvent.BUTTON3);
waitForSwing();
assertMenu(POPUP_ACTIONS,
Set.of(AbstractEnableSelectedBreakpointsAction.NAME,
AbstractDisableSelectedBreakpointsAction.NAME,
AbstractClearSelectedBreakpointsAction.NAME));
// NOTE: With no selection, no actions (even table built-in) apply, so no menu
}
@Test
public void testEmuBreakpointState() throws Throwable {
addPlugin(tool, DebuggerControlServicePlugin.class);
createProgram();
intoProject(program);
programManager.openProgram(program);
waitForSwing();
addStaticMemoryAndBreakpoint();
waitForProgram(program);
LogicalBreakpointRow row = waitForValue(
() -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
// Do our own launch, so that object mode is enabled during load (region creation)
createTrace(program.getLanguageID().getIdAsString());
try (Transaction startTransaction = tb.startTransaction()) {
TraceSnapshot initial = tb.trace.getTimeManager().getSnapshot(0, true);
ProgramEmulationUtils.loadExecutable(initial, program, List.of());
Address pc = program.getMinAddress();
ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, 0, program, pc, pc);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(mappingService.changesSettled());
waitOn(breakpointService.changesSettled());
waitForSwing();
row = waitForValue(
() -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
row.setEnabled(true);
waitForSwing();
row = waitForValue(
() -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
assertEquals(State.ENABLED, row.getState());
}
@Test
public void testTablesAndStatesWhenhModeChanges() throws Throwable {
DebuggerControlService controlService =
addPlugin(tool, DebuggerControlServicePlugin.class);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
createProgramFromTrace(trace);
intoProject(trace);
intoProject(program);
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
waitRecorder(recorder);
addMapping(trace, program);
addStaticMemoryAndBreakpoint();
programManager.openProgram(program);
traceManager.openTrace(trace);
waitForSwing();
LogicalBreakpointRow lbRow1 = waitForPass(() -> {
LogicalBreakpointRow newRow =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
LogicalBreakpoint lb = newRow.getLogicalBreakpoint();
assertEquals(program, lb.getProgram());
assertEquals(Set.of(trace), lb.getMappedTraces());
assertEquals(Set.of(), lb.getParticipatingTraces());
assertEquals(State.INEFFECTIVE_ENABLED, newRow.getState());
return newRow;
});
controlService.setCurrentMode(trace, ControlMode.RW_EMULATOR);
lbRow1.setEnabled(true);
TraceBreakpoint emuBpt = waitForValue(
() -> Unique.assertAtMostOne(trace.getBreakpointManager().getAllBreakpoints()));
assertNull(recorder.getTargetBreakpoint(emuBpt));
LogicalBreakpointRow lbRow2 =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
waitForPass(() -> assertEquals(State.ENABLED, lbRow2.getState()));
waitForPass(() -> {
BreakpointLocationRow newRow =
Unique.assertOne(breakpointsProvider.locationTableModel.getModelData());
assertEquals(State.ENABLED, newRow.getState());
});
for (int i = 0; i < 3; i++) {
controlService.setCurrentMode(trace, ControlMode.RO_TARGET);
waitOn(breakpointService.changesSettled());
waitForSwing();
assertEquals(0, breakpointsProvider.locationTableModel.getModelData().size());
controlService.setCurrentMode(trace, ControlMode.RW_EMULATOR);
waitOn(breakpointService.changesSettled());
waitForSwing();
assertEquals(1, breakpointsProvider.locationTableModel.getModelData().size());
}
}
}

View file

@ -1,397 +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.plugin.core.debug.gui.memory;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import db.Transaction;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerLegacyRegionsPanel.RegionTableColumns;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceStaticMapping;
@Category(NightlyCategory.class)
public class DebuggerRegionsProviderLegacyTest extends AbstractGhidraHeadedDebuggerTest {
DebuggerRegionsProvider provider;
protected TraceMemoryRegion regionExeText;
protected TraceMemoryRegion regionExeData;
protected TraceMemoryRegion regionLibText;
protected TraceMemoryRegion regionLibData;
protected MemoryBlock blockExeText;
protected MemoryBlock blockExeData;
@Before
public void setUpRegionsTest() throws Exception {
addPlugin(tool, DebuggerRegionsPlugin.class);
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
}
protected void addRegions() throws Exception {
TraceMemoryManager mm = tb.trace.getMemoryManager();
try (Transaction tx = tb.startTransaction()) {
regionExeText = mm.createRegion("Memory[/bin/echo 0x55550000]", 0,
tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionExeData = mm.createRegion("Memory[/bin/echo 0x55750000]", 0,
tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
regionLibText = mm.createRegion("Memory[/lib/libc.so 0x7f000000]", 0,
tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionLibData = mm.createRegion("Memory[/lib/libc.so 0x7f100000]", 0,
tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
}
protected void addBlocks() throws Exception {
try (Transaction tx = program.openTransaction("Add block")) {
Memory mem = program.getMemory();
blockExeText = mem.createInitializedBlock(".text", tb.addr(0x00400000), 0x100, (byte) 0,
monitor, false);
blockExeData = mem.createInitializedBlock(".data", tb.addr(0x00600000), 0x80, (byte) 0,
monitor, false);
}
}
@Test
public void testNoTraceEmpty() throws Exception {
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testActivateEmptyTraceEmpty() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testAddThenActivateTracePopulates() throws Exception {
createTrace();
TraceMemoryRegion region;
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
assertEquals("Memory[bin:.text]", row.getName());
assertEquals(tb.addr(0x00400000), row.getMinAddress());
assertEquals(tb.addr(0x0040ffff), row.getMaxAddress());
assertEquals(tb.range(0x00400000, 0x0040ffff), row.getRange());
assertEquals(0x10000, row.getLength());
assertEquals(0L, row.getCreatedSnap());
assertEquals("", row.getDestroyedSnap());
assertEquals(Lifespan.nowOn(0), row.getLifespan());
}
@Test
public void testActivateTraceThenAddPopulates() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
TraceMemoryRegion region;
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
}
@Test
public void testDeleteRemoves() throws Exception {
createTrace();
TraceMemoryRegion region;
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
try (Transaction tx = tb.startTransaction()) {
region.delete();
}
waitForDomainObject(tb.trace);
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testUndoRedo() throws Exception {
createTrace();
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
undo(tb.trace);
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
redo(tb.trace);
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
}
@Test
public void testAbort() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
waitForDomainObject(tb.trace);
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
tx.abort();
}
waitForDomainObject(tb.trace);
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testDoubleClickNavigates() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace();
TraceMemoryRegion region;
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
clickTableCell(provider.legacyPanel.regionTable, 0, RegionTableColumns.START.ordinal(), 2);
waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress()));
clickTableCell(provider.legacyPanel.regionTable, 0, RegionTableColumns.END.ordinal(), 2);
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
}
@Test
public void testActionMapRegions() throws Exception {
assertDisabled(provider, provider.actionMapRegions);
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
addRegions();
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertDisabled(provider, provider.actionMapRegions);
addBlocks();
try (Transaction tx = program.openTransaction("Change name")) {
program.setName("echo");
}
waitForDomainObject(program);
waitForPass(() -> assertEquals(4, provider.legacyPanel.regionTable.getRowCount()));
// NB. Feature works "best" when all regions of modules are selected
// TODO: Test cases where feature works "worst"?
provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
waitForSwing();
performEnabledAction(provider, provider.actionMapRegions, false);
DebuggerRegionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
List<RegionMapEntry> proposal = new ArrayList<>(propDialog.getTableModel().getModelData());
assertEquals(2, proposal.size());
RegionMapEntry entry;
// Table sorts by name by default.
// Names are file name followed by min address, so .text is first.
entry = proposal.get(0);
assertEquals(regionExeText, entry.getRegion());
assertEquals(blockExeText, entry.getBlock());
entry = proposal.get(1);
assertEquals(regionExeData, entry.getRegion());
assertEquals(blockExeData, entry.getBlock());
clickTableCell(propDialog.getTable(), 0, RegionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
MemoryBlockRow row = blockDialog.getTableFilterPanel().getSelectedItem();
assertEquals(blockExeText, row.getBlock());
pressButtonByText(blockDialog, "OK", true);
assertEquals(blockExeData, entry.getBlock()); // Unchanged
// TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings =
tb.trace.getStaticMappingManager().getAllEntries();
assertEquals(0, mappings.size());
pressButtonByText(propDialog, "OK", true);
waitForDomainObject(tb.trace);
assertEquals(2, mappings.size());
Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
TraceStaticMapping sm;
sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x100, sm.getLength());
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00600000", sm.getStaticAddress());
assertEquals(0x80, sm.getLength());
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
assertFalse(mit.hasNext());
}
// TODO: testActionMapRegionsTo
// TODO: testActionMapRegionTo
@Test
public void testActionSelectAddresses() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace();
TraceMemoryRegion region;
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
assertEquals(region, row.getRegion());
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
provider.setSelectedRegions(Set.of(region));
waitForSwing();
performEnabledAction(provider, provider.actionSelectAddresses, true);
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
new AddressSet(listing.getSelection())));
}
@Test
public void testActionAddRegion() throws Exception {
createAndOpenTrace();
}
@Test
public void testActionSelectRows() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace();
TraceMemoryRegion region;
try (Transaction tx = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
// NB. Table is debounced
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
assertEquals(region, row.getRegion());
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
runSwing(() -> listing
.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321)))));
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
new AddressSet(listing.getSelection())));
waitForSwing();
performEnabledAction(listing, provider.actionSelectRows, true);
waitForPass(
() -> assertEquals(Set.of(row), Set.copyOf(provider.legacyPanel.getSelectedRows())));
}
}

View file

@ -1,656 +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.plugin.core.debug.gui.modules;
import static org.junit.Assert.*;
import java.awt.event.MouseEvent;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import db.Transaction;
import docking.widgets.filechooser.GhidraFileChooser;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.*;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractImportFromFileSystemAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction;
import ghidra.app.plugin.core.debug.gui.action.NoneAutoMapSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.ModuleMapTableColumns;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
import ghidra.app.services.DebuggerListingService;
import ghidra.debug.api.action.AutoMapSpec;
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
import ghidra.framework.main.DataTreeDialog;
import ghidra.plugin.importer.ImporterPlugin;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceOverlappedRegionException;
import ghidra.trace.model.modules.*;
import ghidra.util.exception.DuplicateNameException;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerModulesProviderLegacyTest extends AbstractGhidraHeadedDebuggerTest {
protected DebuggerModulesPlugin modulesPlugin;
protected DebuggerModulesProvider modulesProvider;
protected TraceModule modExe;
protected TraceSection secExeText;
protected TraceSection secExeData;
protected TraceModule modLib;
protected TraceSection secLibText;
protected TraceSection secLibData;
@Before
public void setUpModulesProviderTest() throws Exception {
modulesPlugin = addPlugin(tool, DebuggerModulesPlugin.class);
modulesProvider = waitForComponentProvider(DebuggerModulesProvider.class);
// TODO: This seems to hold up the task manager.
modulesProvider.setAutoMapSpec(AutoMapSpec.fromConfigName(NoneAutoMapSpec.CONFIG_NAME));
}
protected void addRegionsFromModules()
throws TraceOverlappedRegionException, DuplicateNameException {
try (Transaction tx = tb.startTransaction()) {
DBTraceMemoryManager manager = tb.trace.getMemoryManager();
for (TraceModule module : tb.trace.getModuleManager().getAllModules()) {
for (TraceSection section : module.getSections()) {
Set<TraceMemoryFlag> flags = new HashSet<>();
flags.add(TraceMemoryFlag.READ);
if (".text".equals(section.getName())) {
flags.add(TraceMemoryFlag.EXECUTE);
}
else if (".data".equals(section.getName())) {
flags.add(TraceMemoryFlag.WRITE);
}
else {
throw new AssertionError();
}
manager.addRegion(
"Processes[1].Memory[" + module.getName() + ":" + section.getName() + "]",
module.getLifespan(), section.getRange(), flags);
}
}
}
}
protected void addModules() throws Exception {
TraceModuleManager manager = tb.trace.getModuleManager();
try (Transaction tx = tb.startTransaction()) {
modExe = manager.addLoadedModule("Processes[1].Modules[first_proc]", "first_proc",
tb.range(0x55550000, 0x5575007f), 0);
secExeText = modExe.addSection("Processes[1].Modules[first_proc].Sections[.text]",
".text", tb.range(0x55550000, 0x555500ff));
secExeData = modExe.addSection("Processes[1].Modules[first_proc].Sections[.data]",
".data", tb.range(0x55750000, 0x5575007f));
modLib = manager.addLoadedModule("Processes[1].Modules[some_lib]", "some_lib",
tb.range(0x7f000000, 0x7f10003f), 0);
secLibText = modLib.addSection("Processes[1].Modules[some_lib].Sections[.text]",
".text", tb.range(0x7f000000, 0x7f0003ff));
secLibData = modLib.addSection("Processes[1].Modules[some_lib].Sections[.data]",
".data", tb.range(0x7f100000, 0x7f10003f));
}
}
protected MemoryBlock addBlock() throws Exception {
try (Transaction tx = program.openTransaction("Add block")) {
return program.getMemory()
.createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, monitor,
false);
}
}
protected void assertProviderEmpty() {
List<ModuleRow> modulesDisplayed =
modulesProvider.legacyModulesPanel.moduleTableModel.getModelData();
assertTrue(modulesDisplayed.isEmpty());
List<SectionRow> sectionsDisplayed =
modulesProvider.legacySectionsPanel.sectionTableModel.getModelData();
assertTrue(sectionsDisplayed.isEmpty());
}
protected void assertProviderPopulated() {
List<ModuleRow> modulesDisplayed =
new ArrayList<>(modulesProvider.legacyModulesPanel.moduleTableModel.getModelData());
modulesDisplayed.sort(Comparator.comparing(r -> r.getBase()));
// I should be able to assume this is sorted by base address. It's the default sort column.
assertEquals(2, modulesDisplayed.size());
ModuleRow execRow = modulesDisplayed.get(0);
assertEquals(tb.addr(0x55550000), execRow.getBase());
assertEquals("first_proc", execRow.getName());
// Use only (start) offset for excess, as unique ID
ModuleRow libRow = modulesDisplayed.get(1);
assertEquals(tb.addr(0x7f000000), libRow.getBase());
List<SectionRow> sectionsDisplayed =
new ArrayList<>(modulesProvider.legacySectionsPanel.sectionTableModel.getModelData());
sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart()));
assertEquals(4, sectionsDisplayed.size());
SectionRow execTextRow = sectionsDisplayed.get(0);
assertEquals(tb.addr(0x55550000), execTextRow.getStart());
assertEquals(tb.addr(0x555500ff), execTextRow.getEnd());
assertEquals("first_proc", execTextRow.getModuleName());
assertEquals(".text", execTextRow.getName());
assertEquals(256, execTextRow.getLength());
SectionRow execDataRow = sectionsDisplayed.get(1);
assertEquals(tb.addr(0x55750000), execDataRow.getStart());
SectionRow libTextRow = sectionsDisplayed.get(2);
assertEquals(tb.addr(0x7f000000), libTextRow.getStart());
SectionRow libDataRow = sectionsDisplayed.get(3);
assertEquals(tb.addr(0x7f100000), libDataRow.getStart());
}
@Test
public void testEmpty() throws Exception {
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateThenAddModulesPopulatesProvider() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
addModules();
waitForSwing();
assertProviderPopulated();
}
@Test
public void testAddModulesThenActivatePopulatesProvider() throws Exception {
createAndOpenTrace();
addModules();
waitForSwing();
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderPopulated();
}
@Test
public void testBlockChooserDialogPopulates() throws Exception {
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
MemoryBlock block = addBlock();
try (Transaction tx = program.openTransaction("Change name")) {
program.setName(modExe.getName());
}
waitForDomainObject(program);
waitForPass(
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
runSwing(() -> modulesProvider.setSelectedSections(Set.of(secExeText)));
performEnabledAction(modulesProvider, modulesProvider.actionMapSections, false);
DebuggerSectionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
assertEquals(1, blockDialog.getTableModel().getRowCount());
MemoryBlockRow row = blockDialog.getTableModel().getModelData().get(0);
assertEquals(program, row.getProgram());
assertEquals(block, row.getBlock());
// NOTE: Other getters should be tested in a separate MemoryBlockRowTest
pressButtonByText(blockDialog, "Cancel", true);
}
@Test
public void testRemoveModulesRemovedFromProvider() throws Exception {
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderPopulated(); // Cheap sanity check
try (Transaction tx = tb.startTransaction()) {
modExe.delete();
}
waitForDomainObject(tb.trace);
List<ModuleRow> modulesDisplayed =
new ArrayList<>(modulesProvider.legacyModulesPanel.moduleTableModel.getModelData());
modulesDisplayed.sort(Comparator.comparing(r -> r.getBase()));
assertEquals(1, modulesDisplayed.size());
ModuleRow libRow = modulesDisplayed.get(0);
assertEquals("some_lib", libRow.getName());
List<SectionRow> sectionsDisplayed =
new ArrayList<>(modulesProvider.legacySectionsPanel.sectionTableModel.getModelData());
sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart()));
assertEquals(2, sectionsDisplayed.size());
SectionRow libTextRow = sectionsDisplayed.get(0);
assertEquals(".text", libTextRow.getName());
assertEquals("some_lib", libTextRow.getModuleName());
SectionRow libDataRow = sectionsDisplayed.get(1);
assertEquals(".data", libDataRow.getName());
assertEquals("some_lib", libDataRow.getModuleName());
}
@Test
public void testUndoRedoCausesUpdateInProvider() throws Exception {
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderPopulated(); // Cheap sanity check
undo(tb.trace);
assertProviderEmpty();
redo(tb.trace);
assertProviderPopulated();
}
@Test
public void testActivatingNoTraceEmptiesProvider() throws Exception {
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderPopulated(); // Cheap sanity check
traceManager.activateTrace(null);
waitForSwing();
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderPopulated();
}
@Test
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderPopulated(); // Cheap sanity check
traceManager.closeTrace(tb.trace);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActionMapIdentically() throws Exception {
assertFalse(modulesProvider.actionMapIdentically.isEnabled());
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
// No modules necessary
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(modulesProvider.actionMapIdentically.isEnabled());
// Need some substance in the program
try (Transaction tx = program.openTransaction("Populate")) {
addBlock();
}
waitForDomainObject(program);
performEnabledAction(modulesProvider, modulesProvider.actionMapIdentically, true);
waitForDomainObject(tb.trace);
Collection<? extends TraceStaticMapping> mappings =
tb.trace.getStaticMappingManager().getAllEntries();
assertEquals(1, mappings.size());
TraceStaticMapping sm = mappings.iterator().next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x1000, sm.getLength()); // Block is 0x1000 in length
assertEquals(tb.addr(0x00400000), sm.getMinTraceAddress());
}
@Test
public void testActionMapModules() throws Exception {
assertDisabled(modulesProvider, modulesProvider.actionMapModules);
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertDisabled(modulesProvider, modulesProvider.actionMapModules);
try (Transaction tx = program.openTransaction("Change name")) {
program.setImageBase(addr(program, 0x00400000), true);
program.setName(modExe.getName());
addBlock(); // So the program has a size
}
waitForDomainObject(program);
waitForPass(
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
assertEnabled(modulesProvider, modulesProvider.actionMapModules);
performEnabledAction(modulesProvider, modulesProvider.actionMapModules, false);
DebuggerModuleMapProposalDialog propDialog =
waitForDialogComponent(DebuggerModuleMapProposalDialog.class);
List<ModuleMapEntry> proposal = propDialog.getTableModel().getModelData();
ModuleMapEntry entry = Unique.assertOne(proposal);
assertEquals(modExe, entry.getModule());
assertEquals(program, entry.getToProgram());
clickTableCell(propDialog.getTable(), 0, ModuleMapTableColumns.CHOOSE.ordinal(), 1);
DataTreeDialog programDialog = waitForDialogComponent(DataTreeDialog.class);
assertEquals(program.getDomainFile(), programDialog.getDomainFile());
pressButtonByText(programDialog, "OK", true);
assertEquals(program, entry.getToProgram());
// TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings =
tb.trace.getStaticMappingManager().getAllEntries();
assertEquals(0, mappings.size());
pressButtonByText(propDialog, "OK", true);
waitForDomainObject(tb.trace);
assertEquals(1, mappings.size());
TraceStaticMapping sm = mappings.iterator().next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x1000, sm.getLength()); // Block is 0x1000 in length
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
}
// TODO: testActionMapModulesTo
// TODO: testActionMapModuleTo
@Test
public void testActionMapSections() throws Exception {
assertDisabled(modulesProvider, modulesProvider.actionMapSections);
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertDisabled(modulesProvider, modulesProvider.actionMapSections);
MemoryBlock block = addBlock();
try (Transaction tx = program.openTransaction("Change name")) {
program.setName(modExe.getName());
}
waitForDomainObject(program);
waitForPass(
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
modulesProvider.setSelectedSections(Set.of(secExeText));
waitForSwing();
assertEnabled(modulesProvider, modulesProvider.actionMapSections);
performEnabledAction(modulesProvider, modulesProvider.actionMapSections, false);
DebuggerSectionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
List<SectionMapEntry> proposal = propDialog.getTableModel().getModelData();
SectionMapEntry entry = Unique.assertOne(proposal);
assertEquals(secExeText, entry.getSection());
assertEquals(block, entry.getBlock());
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
MemoryBlockRow row = Unique.assertOne(blockDialog.getTableModel().getModelData());
assertEquals(block, row.getBlock());
pressButtonByText(blockDialog, "OK", true);
assertEquals(block, entry.getBlock()); // Unchanged
// TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings =
tb.trace.getStaticMappingManager().getAllEntries();
assertEquals(0, mappings.size());
pressButtonByText(propDialog, "OK", true);
waitForDomainObject(tb.trace);
assertEquals(1, mappings.size());
TraceStaticMapping sm = mappings.iterator().next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x100, sm.getLength()); // Section is 0x100, though block is 0x1000 long
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
}
// TODO: testActionMapSectionsTo
// TODO: testActionMapSectionTo
@Test
public void testActionSelectAddresses() throws Exception {
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
addPlugin(tool, DebuggerListingPlugin.class);
waitForComponentProvider(DebuggerListingProvider.class);
// TODO: Should I hide the action if this service is missing?
DebuggerListingService listing = tool.getService(DebuggerListingService.class);
createAndOpenTrace();
addModules();
addRegionsFromModules();
// Still
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
traceManager.activateTrace(tb.trace);
waitForSwing(); // NOTE: The table may select first by default, enabling action
waitForPass(
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
waitForPass(
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
performEnabledAction(modulesProvider, modulesProvider.actionSelectAddresses, true);
assertEquals(tb.set(tb.range(0x55550000, 0x555500ff), tb.range(0x55750000, 0x5575007f)),
new AddressSet(listing.getCurrentSelection()));
modulesProvider.setSelectedSections(Set.of(secExeText, secLibText));
waitForSwing();
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
performEnabledAction(modulesProvider, modulesProvider.actionSelectAddresses, true);
assertEquals(tb.set(tb.range(0x55550000, 0x555500ff), tb.range(0x7f000000, 0x7f0003ff)),
new AddressSet(listing.getCurrentSelection()));
}
@Test
public void testActionImportFromFileSystem() throws Exception {
addPlugin(tool, ImporterPlugin.class);
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
try (Transaction tx = tb.startTransaction()) {
modExe.setName("/bin/echo"); // File has to exist
}
waitForPass(
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
performAction(modulesProvider.actionImportFromFileSystem, false);
GhidraFileChooser dialog = waitForDialogComponent(GhidraFileChooser.class);
dialog.close();
}
protected Set<SectionRow> visibleSections() {
return Set
.copyOf(modulesProvider.legacySectionsPanel.sectionFilterPanel.getTableFilterModel()
.getModelData());
}
@Test
public void testActionFilterSections() throws Exception {
addPlugin(tool, ImporterPlugin.class);
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
waitForPass(
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
assertEquals(4, visibleSections().size());
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
assertEquals(4, visibleSections().size());
assertTrue(modulesProvider.actionFilterSectionsByModules.isEnabled());
performEnabledAction(modulesProvider, modulesProvider.actionFilterSectionsByModules, true);
waitForSwing();
assertEquals(2, visibleSections().size());
for (SectionRow row : visibleSections()) {
assertEquals(modExe, row.getModule());
}
modulesProvider.setSelectedModules(Set.of());
waitForSwing();
waitForPass(() -> assertEquals(4, visibleSections().size()));
}
protected static final Set<String> POPUP_ACTIONS = Set.of(AbstractSelectAddressesAction.NAME,
DebuggerResources.NAME_MAP_MODULES, DebuggerResources.NAME_MAP_SECTIONS,
AbstractImportFromFileSystemAction.NAME);
@Test
public void testPopupActionsOnModuleSelections() throws Exception {
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
// NB. Table is debounced
waitForPass(
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
clickTableCellWithButton(modulesProvider.legacyModulesPanel.moduleTable, 0, 0,
MouseEvent.BUTTON3);
waitForSwing();
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
AbstractSelectAddressesAction.NAME));
pressEscape();
addPlugin(tool, ImporterPlugin.class);
waitForSwing();
clickTableCellWithButton(modulesProvider.legacyModulesPanel.moduleTable, 0, 0,
MouseEvent.BUTTON3);
waitForSwing();
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
AbstractSelectAddressesAction.NAME, AbstractImportFromFileSystemAction.NAME));
}
@Test
public void testPopupActionsOnSectionSelections() throws Exception {
createAndOpenTrace();
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
clickTableCellWithButton(modulesProvider.legacySectionsPanel.sectionTable, 0, 0,
MouseEvent.BUTTON3);
waitForSwing();
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
AbstractSelectAddressesAction.NAME));
}
}

View file

@ -1,503 +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.plugin.core.debug.gui.stack;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import db.Transaction;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebuggerTest {
protected DebuggerStackPlugin stackPlugin;
protected DebuggerStackProvider stackProvider;
protected DebuggerStaticMappingService mappingService;
protected Register pc;
@Before
public void setUpStackProviderTest() throws Exception {
stackPlugin = addPlugin(tool, DebuggerStackPlugin.class);
stackProvider = waitForComponentProvider(DebuggerStackProvider.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
pc = getToyBE64Language().getProgramCounter();
}
protected TraceThread addThread(String n) throws DuplicateNameException {
try (Transaction tx = tb.startTransaction()) {
return tb.trace.getThreadManager().createThread(n, 0);
}
}
protected void addRegVals(TraceThread thread) {
try (Transaction tx = tb.startTransaction()) {
TraceMemorySpace regs =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
regs.setValue(0, new RegisterValue(pc, new BigInteger("00400123", 16)));
}
}
protected TraceStack addStack(TraceThread thread, int snap) {
try (Transaction tx = tb.startTransaction()) {
return tb.trace.getStackManager().getStack(thread, snap, true);
}
}
protected TraceStack addStack(TraceThread thread) {
return addStack(thread, 0);
}
protected void addStackFrames(TraceStack stack) {
try (Transaction tx = tb.startTransaction()) {
stack.setDepth(2, false);
TraceStackFrame frame = stack.getFrame(0, false);
frame.setProgramCounter(Lifespan.ALL, tb.addr(0x00400100));
frame.setComment(stack.getSnap(), "Hello");
frame = stack.getFrame(1, false);
frame.setProgramCounter(Lifespan.ALL, tb.addr(0x00400200));
frame.setComment(stack.getSnap(), "World");
}
}
protected void assertProviderEmpty() {
List<StackFrameRow> framesDisplayed =
stackProvider.legacyPanel.stackTableModel.getModelData();
assertTrue(framesDisplayed.isEmpty());
}
protected void assertProviderPopulatedSynthetic() {
List<StackFrameRow> framesDisplayed =
stackProvider.legacyPanel.stackTableModel.getModelData();
StackFrameRow row = Unique.assertOne(framesDisplayed);
assertNull(row.frame);
assertEquals(0x00400123, row.getProgramCounter().getOffset());
}
protected void assertTableSize(int size) {
assertEquals(size, stackProvider.legacyPanel.stackTableModel.getModelData().size());
}
protected void assertRow(int level, Address pcVal, String comment, Function func) {
StackFrameRow row = stackProvider.legacyPanel.stackTableModel.getModelData().get(level);
assertEquals(level, row.getFrameLevel());
assertNotNull(row.frame);
assertEquals(pcVal, row.getProgramCounter());
assertEquals(comment, row.getComment());
assertEquals(func, row.getFunction());
}
protected void assertProviderPopulated() {
assertTableSize(2);
assertRow(0, tb.addr(0x00400100), "Hello", null);
assertRow(1, tb.addr(0x00400200), "World", null);
}
@Test
public void testEmpty() throws Exception {
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateTraceNoThreadEmpty() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateThreadNoStackNoRegsEmpty() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateThreadNoStackRegsSynthetic() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
addRegVals(thread);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulatedSynthetic();
}
@Test
public void testActivateThreadRegsThenAddEmptyStackEmpty() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
addRegVals(thread);
addStack(thread);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateThreadThenAddStackPopulatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
traceManager.activateThread(thread);
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
assertProviderPopulated();
}
@Test
public void testAddStackThenActivateThreadPopulatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
}
@Test
public void testAppendStackUpdatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
try (Transaction tx = tb.startTransaction()) {
stack.setDepth(3, false);
}
waitForDomainObject(tb.trace);
assertTableSize(3);
assertRow(0, tb.addr(0x00400100), "Hello", null);
assertRow(1, tb.addr(0x00400200), "World", null);
assertRow(2, null, null, null);
}
@Test
public void testPushStackUpdatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
try (Transaction tx = tb.startTransaction()) {
stack.setDepth(3, true);
}
waitForDomainObject(tb.trace);
assertTableSize(3);
assertRow(0, null, null, null);
assertRow(1, tb.addr(0x00400100), "Hello", null);
assertRow(2, tb.addr(0x00400200), "World", null);
}
@Test
public void testTruncateStackUpdatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
try (Transaction tx = tb.startTransaction()) {
stack.setDepth(1, false);
}
waitForDomainObject(tb.trace);
assertTableSize(1);
assertRow(0, tb.addr(0x00400100), "Hello", null);
}
@Test
public void testPopStackUpdatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
try (Transaction tx = tb.startTransaction()) {
stack.setDepth(1, true);
}
waitForDomainObject(tb.trace);
assertTableSize(1);
assertRow(0, tb.addr(0x00400200), "World", null);
}
@Test
public void testDeleteStackUpdatesProvider() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
try (Transaction tx = tb.startTransaction()) {
stack.delete();
}
waitForDomainObject(tb.trace);
assertProviderEmpty();
}
@Test
public void testActivateOtherThread() throws Exception {
createAndOpenTrace();
TraceThread thread1 = addThread("Processes[1].Threads[1]");
TraceThread thread2 = addThread("Processes[1].Threads[2]");
TraceStack stack = addStack(thread1);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread1);
waitForSwing();
assertProviderPopulated();
traceManager.activateThread(thread2);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateSnap() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
addStack(thread, 1);
waitForSwing();
assertProviderPopulated();
traceManager.activateSnap(1);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testCloseCurrentTraceEmpty() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
traceManager.closeTrace(tb.trace);
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateFrameSelectsRow() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
traceManager.activateFrame(0);
waitForSwing();
assertEquals(0, stackProvider.legacyPanel.stackTable.getSelectedRow());
traceManager.activateFrame(1);
waitForSwing();
assertEquals(1, stackProvider.legacyPanel.stackTable.getSelectedRow());
}
@Test
public void testDoubleClickRowActivatesFrame() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
clickTableCell(stackProvider.legacyPanel.stackTable, 0, 0, 2);
assertEquals(0, traceManager.getCurrentFrame());
clickTableCell(stackProvider.legacyPanel.stackTable, 1, 0, 2);
assertEquals(1, traceManager.getCurrentFrame());
}
@Test
public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception {
createTrace();
createProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
traceManager.openTrace(tb.trace);
programManager.openProgram(program);
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
Function func;
try (Transaction tx = program.openTransaction("Add Function")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00600000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);
AddressSet body = new AddressSet();
body.add(addr(program, 0x00600100), addr(program, 0x00600123));
func = program.getFunctionManager()
.createFunction("func", body.getMinAddress(), body, SourceType.USER_DEFINED);
}
waitForDomainObject(program);
try (Transaction tx = tb.startTransaction()) {
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[bin:.text]", Lifespan.nowOn(0),
tb.drng(0x00400000, 0x00400fff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
TraceLocation dloc =
new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0x00400000));
ProgramLocation sloc = new ProgramLocation(program, addr(program, 0x00600000));
DebuggerStaticMappingUtils.addMapping(dloc, sloc, 0x1000, false);
}
waitForDomainObject(tb.trace);
assertTableSize(2);
assertRow(0, tb.addr(0x00400100), "Hello", func);
assertRow(1, tb.addr(0x00400200), "World", null);
}
}

View file

@ -1,295 +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.plugin.core.debug.gui.thread;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import db.Transaction;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceTimeManager;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebuggerTest {
protected DebuggerThreadsPlugin threadsPlugin;
protected DebuggerThreadsProvider threadsProvider;
protected TraceThread thread1;
protected TraceThread thread2;
@Before
public void setUpThreadsProviderTest() throws Exception {
threadsPlugin = addPlugin(tool, DebuggerThreadsPlugin.class);
threadsProvider = waitForComponentProvider(DebuggerThreadsProvider.class);
}
protected void addThreads() throws Exception {
TraceThreadManager manager = tb.trace.getThreadManager();
try (Transaction tx = tb.startTransaction()) {
thread1 = manager.addThread("Processes[1].Threads[1]", Lifespan.nowOn(0));
thread1.setComment("A comment");
thread2 = manager.addThread("Processes[1].Threads[2]", Lifespan.span(5, 10));
thread2.setComment("Another comment");
}
}
protected void assertThreadsEmpty() {
List<ThreadRow> threadsDisplayed =
threadsProvider.legacyPanel.threadTableModel.getModelData();
assertTrue(threadsDisplayed.isEmpty());
}
protected void assertThreadsPopulated() {
List<ThreadRow> threadsDisplayed =
threadsProvider.legacyPanel.threadTableModel.getModelData();
assertEquals(2, threadsDisplayed.size());
ThreadRow thread1Record = threadsDisplayed.get(0);
assertEquals(thread1, thread1Record.getThread());
assertEquals("Processes[1].Threads[1]", thread1Record.getName());
assertEquals(Lifespan.nowOn(0), thread1Record.getLifespan());
assertEquals(0, thread1Record.getCreationSnap());
assertEquals("", thread1Record.getDestructionSnap());
assertEquals(tb.trace, thread1Record.getTrace());
assertEquals(ThreadState.ALIVE, thread1Record.getState());
assertEquals("A comment", thread1Record.getComment());
ThreadRow thread2Record = threadsDisplayed.get(1);
assertEquals(thread2, thread2Record.getThread());
}
protected void assertNoThreadSelected() {
assertNull(threadsProvider.legacyPanel.threadFilterPanel.getSelectedItem());
}
protected void assertThreadSelected(TraceThread thread) {
ThreadRow row = threadsProvider.legacyPanel.threadFilterPanel.getSelectedItem();
assertNotNull(row);
assertEquals(thread, row.getThread());
}
protected void assertProviderEmpty() {
assertThreadsEmpty();
}
@Test
public void testEmpty() {
waitForSwing();
assertProviderEmpty();
}
@Test
public void testActivateNoTraceEmptiesProvider() throws Exception {
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertThreadsPopulated(); // Sanity
traceManager.activateTrace(null);
waitForSwing();
assertThreadsEmpty();
}
@Test
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertThreadsPopulated();
traceManager.closeTrace(tb.trace);
waitForSwing();
assertThreadsEmpty();
}
@Test
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
addThreads();
waitForSwing();
assertThreadsPopulated();
}
@Test
public void testAddThreadsThenActivatePopulatesProvider() throws Exception {
createAndOpenTrace();
addThreads();
waitForSwing();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertThreadsPopulated();
}
@Test
public void testAddSnapUpdatesTimelineMax() throws Exception {
createAndOpenTrace();
TraceTimeManager manager = tb.trace.getTimeManager();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(1, threadsProvider.legacyPanel.spanRenderer.getFullRange().max().longValue());
try (Transaction tx = tb.startTransaction()) {
manager.getSnapshot(10, true);
}
waitForSwing();
assertEquals(11, threadsProvider.legacyPanel.spanRenderer.getFullRange().max().longValue());
}
// NOTE: Do not test delete updates timeline max, as maxSnap does not reflect deletion
@Test
public void testChangeThreadUpdatesProvider() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
try (Transaction tx = tb.startTransaction()) {
thread1.setDestructionSnap(15);
}
waitForSwing();
assertEquals("15", threadsProvider.legacyPanel.threadTableModel.getModelData()
.get(0)
.getDestructionSnap());
// NOTE: Plot max is based on time table, never thread destruction
}
@Test
public void testDeleteThreadUpdatesProvider() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(2, threadsProvider.legacyPanel.threadTableModel.getModelData().size());
try (Transaction tx = tb.startTransaction()) {
thread2.delete();
}
waitForSwing();
assertEquals(1, threadsProvider.legacyPanel.threadTableModel.getModelData().size());
// NOTE: Plot max is based on time table, never thread destruction
}
@Test
public void testEditThreadFields() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
runSwing(() -> {
threadsProvider.legacyPanel.threadTableModel.setValueAt("My Thread", 0,
ThreadTableColumns.NAME.ordinal());
threadsProvider.legacyPanel.threadTableModel.setValueAt("A different comment", 0,
ThreadTableColumns.COMMENT.ordinal());
});
assertEquals("My Thread", thread1.getName());
assertEquals("A different comment", thread1.getComment());
}
@Test
public void testUndoRedoCausesUpdateInProvider() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertThreadsPopulated();
undo(tb.trace);
assertThreadsEmpty();
redo(tb.trace);
assertThreadsPopulated();
}
@Test
public void testActivateThreadSelectsThread() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertThreadsPopulated();
assertThreadSelected(thread1);
traceManager.activateThread(thread2);
waitForSwing();
assertThreadSelected(thread2);
}
@Test
public void testDoubleClickThreadInTableActivatesThread() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForDomainObject(tb.trace);
assertThreadsPopulated();
clickTableCell(threadsProvider.legacyPanel.threadTable, 1, 0, 2);
assertEquals(thread2, traceManager.getCurrentThread());
}
@Test
public void testActivateSnapUpdatesTimelineCursor() throws Exception {
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertThreadsPopulated();
assertEquals(0, traceManager.getCurrentSnap());
assertEquals(0, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
traceManager.activateSnap(6);
waitForSwing();
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
}
}

View file

@ -4,16 +4,16 @@
* 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.plugin.core.debug.service.control;
package ghidra.app.plugin.core.debug.service;
import java.util.Map;
import java.util.Set;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -34,7 +34,6 @@ import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.Unique;
import generic.test.rule.Repeated;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.assembler.*;
@ -1073,8 +1072,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
}
public record HoverLocation(ProgramLocation pLoc, FieldLocation fLoc, Field field,
ClangToken token) {
}
ClangToken token) {}
public static <T extends ProgramLocation> HoverLocation findLocation(ListingPanel panel,
Address address, Class<T> locType, Predicate<T> predicate) {

View file

@ -1,198 +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.pcode.exec;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.Map;
import org.junit.Test;
import db.Transaction;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.emulation.PcodeDebuggerAccess;
import ghidra.debug.api.model.DebuggerRegisterMapper;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
/**
* Test the {@link DirectBytesTracePcodeExecutorState} in combination with
* {@link PcodeDebuggerAccess} to ensure it reads and writes the target when appropriate.
*/
public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerTest {
@Test
public void testExecutorEval() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
waitOn(regs.writeRegistersNamed(Map.of(
"r0", new byte[] { 5 },
"r1", new byte[] { 6 })));
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
Trace trace = recorder.getTrace();
SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
PcodeExpression expr = SleighProgramCompiler.compileExpression(language, "r0 + r1");
Register r0 = language.getRegister("r0");
Register r1 = language.getRegister("r1");
waitForPass(() -> {
// TODO: A little brittle: Depends on a specific snap advancement strategy
assertEquals(3, trace.getTimeManager().getSnapshotCount());
DebuggerRegisterMapper rm = recorder.getRegisterMapper(thread);
assertNotNull(rm);
assertNotNull(rm.getTargetRegister("r0"));
assertNotNull(rm.getTargetRegister("r1"));
assertTrue(rm.getRegistersOnTarget().contains(r0));
assertTrue(rm.getRegistersOnTarget().contains(r1));
});
Target target = targetService.getTarget(trace);
assertNotNull(target);
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
DebuggerCoordinates.NOWHERE.target(target).thread(thread));
// In practice, this should be backgrounded, but we're in a test thread
byte[] result = expr.evaluate(executor);
assertEquals(11, Utils.bytesToLong(result, result.length, language.isBigEndian()));
}
@Test
public void testExecutorEvalInScratchReadsLive() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
waitOn(regs.writeRegistersNamed(Map.of(
"r0", new byte[] { 5 },
"r1", new byte[] { 6 })));
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
Trace trace = recorder.getTrace();
SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
PcodeExpression expr = SleighProgramCompiler
.compileExpression(language, "r0 + r1");
Register r0 = language.getRegister("r0");
Register r1 = language.getRegister("r1");
waitForPass(() -> {
// TODO: A little brittle: Depends on a specific snap advancement strategy
assertEquals(3, trace.getTimeManager().getSnapshotCount());
DebuggerRegisterMapper rm = recorder.getRegisterMapper(thread);
assertNotNull(rm);
assertNotNull(rm.getTargetRegister("r0"));
assertNotNull(rm.getTargetRegister("r1"));
assertTrue(rm.getRegistersOnTarget().contains(r0));
assertTrue(rm.getRegistersOnTarget().contains(r1));
});
TraceSchedule oneTick = TraceSchedule.snap(recorder.getSnap()).steppedForward(thread, 1);
try (Transaction tx = trace.openTransaction("Scratch")) {
TraceSnapshot scratch = trace.getTimeManager().getSnapshot(Long.MIN_VALUE, true);
scratch.setSchedule(oneTick);
scratch.setDescription("Faked");
TraceMemorySpace space = trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
space.setValue(scratch.getKey(), new RegisterValue(r0, BigInteger.valueOf(10)));
}
Target target = targetService.getTarget(trace);
assertNotNull(target);
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
DebuggerCoordinates.NOWHERE.target(target).thread(thread).time(oneTick));
// In practice, this should be backgrounded, but we're in a test thread
byte[] result = expr.evaluate(executor);
assertEquals(16, Utils.bytesToLong(result, result.length, language.isBigEndian()));
}
@Test
public void testExecutorWrite() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
waitOn(regs.writeRegistersNamed(Map.of(
"r0", new byte[] { 5 },
"r1", new byte[] { 6 })));
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitRecorder(recorder);
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
Trace trace = recorder.getTrace();
SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test",
"r2 = r0 + r1;", PcodeUseropLibrary.NIL);
Register r0 = language.getRegister("r0");
Register r1 = language.getRegister("r1");
waitForPass(() -> {
// TODO: A little brittle: Depends on a specific snap advancement strategy
assertEquals(3, trace.getTimeManager().getSnapshotCount());
DebuggerRegisterMapper rm = recorder.getRegisterMapper(thread);
assertNotNull(rm);
assertNotNull(rm.getTargetRegister("r0"));
assertNotNull(rm.getTargetRegister("r1"));
assertTrue(rm.getRegistersOnTarget().contains(r0));
assertTrue(rm.getRegistersOnTarget().contains(r1));
});
Target target = targetService.getTarget(trace);
assertNotNull(target);
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
DebuggerCoordinates.NOWHERE.target(target).thread(thread));
executor.execute(prog, PcodeUseropLibrary.nil());
// Ignore return value. We'll assert that it got written to the trace
executor.state.getVar(language.getRegister("r2"), Reason.INSPECT);
assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2")));
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -66,4 +66,10 @@ public class AsyncPairingQueue<T> {
return null;
});
}
public boolean isEmpty() {
synchronized (givers) {
return givers.isEmpty() && takers.isEmpty();
}
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -21,6 +21,7 @@ import java.util.function.Function;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
@Deprecated(forRemoval = true, since = "11.3")
public class TestDebuggerModelBuilder {
public TestDebuggerModelFactory testFactory = new TestDebuggerModelFactory();
public TestDebuggerObjectModel testModel;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -122,7 +122,8 @@ public class AddressesReadTracePcodeExecutorStatePiece
}
Address start = data.translate(space.getAddress(offset));
if (start == null) {
return new AddressSet();
// This can happen if the register space for the thread doesn't exist
return null;
}
try {
return new AddressSet(new AddressRangeImpl(start, size));

View file

@ -760,7 +760,7 @@ public class ToyDBTraceBuilder implements AutoCloseable {
return getLanguage(langID).getCompilerSpecByID(new CompilerSpecID(compID));
}
public void createObjectsProcessAndThreads() {
public TraceObjectThread createObjectsProcessAndThreads() {
DBTraceObjectManager objs = trace.getObjectManager();
TraceObjectKeyPath pathProc1 = TraceObjectKeyPath.parse("Processes[1]");
TraceObject proc1 = objs.createObject(pathProc1);
@ -772,6 +772,8 @@ public class ToyDBTraceBuilder implements AutoCloseable {
t2.insert(zeroOn, ConflictResolution.DENY);
proc1.setAttribute(zeroOn, "_state", TargetExecutionState.STOPPED.name());
return t1.queryInterface(TraceObjectThread.class);
}
public void createObjectsFramesAndRegs(TraceObjectThread thread, Lifespan lifespan,

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -44,7 +44,7 @@ public enum AddressesReadPcodeArithmetic implements PcodeArithmetic<AddressSetVi
@Override
public AddressSetView binaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1,
int sizein2, AddressSetView in2) {
return in1.union(in2);
return in1 == null || in2 == null ? null : in1.union(in2);
}
@Override
@ -56,7 +56,7 @@ public enum AddressesReadPcodeArithmetic implements PcodeArithmetic<AddressSetVi
@Override
public AddressSetView modAfterLoad(int sizeout, int sizeinAddress, AddressSetView inAddress,
int sizeinValue, AddressSetView inValue) {
return inValue.union(inAddress);
return inValue == null || inAddress == null ? null : inValue.union(inAddress);
}
@Override

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,8 +22,7 @@ import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
import db.Transaction;
import docking.DefaultActionContext;
@ -32,10 +31,11 @@ import docking.menu.MultiStateDockingAction;
import generic.Unique;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest.TestDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.service.MockTarget;
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.target.DebuggerTargetServicePlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.decompile.DecompilePlugin;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
@ -47,17 +47,21 @@ import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.dbg.model.TestDebuggerModelBuilder;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.target.Target;
import ghidra.graph.viewer.VisualGraphViewUpdater;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.RunManager;
@ -65,14 +69,14 @@ import ghidra.util.task.TaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotGenerator {
private DebuggerModelService modelService;
private DebuggerTargetService targetService;
private DebuggerTraceManagerService traceManager;
private DebuggerStaticMappingService mappingService;
private DebuggerLogicalBreakpointService breakpointService;
private DebuggerBreakpointMarkerPlugin breakpointMarkerPlugin;
private ProgramManager programManager;
private TestDebuggerModelBuilder mb;
private ToyDBTraceBuilder tb;
private CodeViewerProvider listing;
@ -82,7 +86,7 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
@Before
public void setUpMine() throws Exception {
modelService = addPlugin(tool, DebuggerModelServiceProxyPlugin.class);
targetService = addPlugin(tool, DebuggerTargetServicePlugin.class);
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
breakpointService = addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class);
@ -93,52 +97,72 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
program = programManager.getCurrentProgram();
mb = new TestDebuggerModelBuilder();
tb = new ToyDBTraceBuilder(getName(), "x86:LE:32:default");
}
@After
public void tearDownMine() throws Exception {
if (tb != null) {
tb.close();
}
}
protected void placeBreakpoint(long index, Address dynAddr, boolean enabled) {
try (Transaction tx = tb.startTransaction()) {
TraceObjectKeyPath pathSpec =
TraceObjectKeyPath.parse("Processes[1].Breakpoints").index(index);
DBTraceObject objBptSpec = tb.trace.getObjectManager().createObject(pathSpec);
DBTraceObject objBptLoc = tb.trace.getObjectManager().createObject(pathSpec.index(1));
objBptLoc.setAttribute(Lifespan.nowOn(0), "Range",
new AddressRangeImpl(dynAddr, dynAddr));
objBptSpec.setAttribute(Lifespan.nowOn(0), "Kinds", "SW_EXECUTE");
objBptSpec.setAttribute(Lifespan.nowOn(0), "Enabled", enabled);
objBptLoc.insert(Lifespan.nowOn(0), ConflictResolution.DENY);
}
}
@Test
public void testCaptureDebuggerBreakpointMarkerPlugin() throws Throwable {
ListingPanel panel = listing.getListingPanel();
moveProviderToItsOwnWindow(listing, 1024, 680);
mb.createTestModel();
modelService.addModel(mb.testModel);
mb.createTestProcessesAndThreads();
TestDebuggerTargetTraceMapper mapper = new TestDebuggerTargetTraceMapper(mb.testProcess1);
TraceRecorder recorder =
modelService.recordTarget(mb.testProcess1, mapper, ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
Target target = new MockTarget(tb.trace);
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager()
.createRootObject(AbstractGhidraHeadedDebuggerIntegrationTest.SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
}
targetService.publishTarget(target);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
tool.getProject()
.getProjectData()
.getRootFolder()
.createFile("WinHelloCPP", program, TaskMonitor.DUMMY);
try (Transaction tx = trace.openTransaction("Add Mapping")) {
mappingService.addIdentityMapping(trace, program, Lifespan.nowOn(0), true);
try (Transaction tx = tb.startTransaction()) {
mappingService.addIdentityMapping(tb.trace, program, Lifespan.nowOn(0), true);
}
waitForValue(() -> mappingService.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(0), mb.addr(0x00401c60))));
new DefaultTraceLocation(tb.trace, null, Lifespan.at(0), tb.addr(0x00401c60))));
Msg.debug(this, "Placing breakpoint");
Msg.debug(this, "Placing disabled breakpoint");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401c60), 1,
Set.of(TraceBreakpointKind.SW_EXECUTE), "");
Msg.debug(this, "Disabling breakpoint");
LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
LogicalBreakpoint lbDis = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00401c60))));
waitForCondition(() -> lb.computeState() == State.ENABLED);
placeBreakpoint(1, lbDis.getTraceAddress(tb.trace), false);
waitForCondition(() -> lbDis.computeState() == State.DISABLED);
lb.disable();
waitForCondition(() -> lb.computeState() == State.DISABLED);
Msg.debug(this, "Placing another");
Msg.debug(this, "Placing enabled breakpoint");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401c63), 1,
Set.of(TraceBreakpointKind.SW_EXECUTE), "");
LogicalBreakpoint lbEn = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00401c63))));
placeBreakpoint(2, lbEn.getTraceAddress(tb.trace), true);
waitForCondition(() -> lbEn.computeState() == State.ENABLED);
Msg.debug(this, "Saving program");
program.save("Placed breakpoints", TaskMonitor.DUMMY);
@ -158,15 +182,10 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
protected void waitForBusyRunManager(FGController controller) {
FGModel model = controller.getModel();
// long start = System.nanoTime();
waitForSwing();
RunManager runManager = (RunManager) TestUtils.getInstanceField("runManager", model);
waitForCondition(() -> !runManager.isInProgress());
// long end = System.nanoTime();
// long total = end - start;
// Msg.debug(this,
// "Run manager wait time: " + TimeUnit.MILLISECONDS.convert(total, TimeUnit.NANOSECONDS));
}
protected FGComponent getGraphComponent(FGProvider fgProvider) {
@ -204,8 +223,6 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
return; // nothing to wait for; no active graph
}
// long start = System.nanoTime();
waitForSwing();
int tryCount = 3;
@ -215,11 +232,6 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
waitForSwing();
assertFalse(updater.isBusy());
// long end = System.nanoTime();
// long total = end - start;
// Msg.debug(this,
// "Animation wait time: " + TimeUnit.MILLISECONDS.convert(total, TimeUnit.NANOSECONDS));
}
@SuppressWarnings("rawtypes")
@ -229,23 +241,21 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
final MultiStateDockingAction<?> action =
(MultiStateDockingAction<?>) getInstanceField("layoutAction", actionManager);
Object minCrossState = null;
Object wantState = null;
List<?> states = action.getAllActionStates();
for (Object state : states) {
if (((ActionState) state).getName().indexOf("Nested Code Layout") != -1) {
minCrossState = state;
if (((ActionState) state).getName().indexOf("Flow Chart") != -1) {
wantState = state;
break;
}
}
assertNotNull("Could not find min cross layout!", minCrossState);
assertNotNull("Could not find desired layout!", wantState);
//@formatter:off
invokeInstanceMethod( "setCurrentActionState",
action,
new Class<?>[] { ActionState.class },
new Object[] { minCrossState });
//@formatter:on
invokeInstanceMethod("setCurrentActionState",
action,
new Class<?>[] { ActionState.class },
new Object[] { wantState });
runSwing(() -> action.actionPerformed(new DefaultActionContext()));
@ -262,31 +272,35 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
@Test
public void testCaptureDebuggerFunctionGraphBreakpointMargin() throws Throwable {
mb.createTestModel();
modelService.addModel(mb.testModel);
mb.createTestProcessesAndThreads();
TestDebuggerTargetTraceMapper mapper = new TestDebuggerTargetTraceMapper(mb.testProcess1);
TraceRecorder recorder =
modelService.recordTarget(mb.testProcess1, mapper, ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
Target target = new MockTarget(tb.trace);
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager()
.createRootObject(AbstractGhidraHeadedDebuggerIntegrationTest.SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
}
targetService.publishTarget(target);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
tool.getProject()
.getProjectData()
.getRootFolder()
.createFile("WinHelloCPP", program, TaskMonitor.DUMMY);
try (Transaction tx = trace.openTransaction("Add Mapping")) {
mappingService.addIdentityMapping(trace, program, Lifespan.nowOn(0), true);
try (Transaction tx = tb.startTransaction()) {
mappingService.addIdentityMapping(tb.trace, program, Lifespan.nowOn(0), true);
}
waitForValue(() -> mappingService.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(0), mb.addr(0x00401070))));
new DefaultTraceLocation(tb.trace, null, Lifespan.at(0), tb.addr(0x00401070))));
Msg.debug(this, "Placing breakpoint");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401070), 1,
Set.of(TraceBreakpointKind.SW_EXECUTE), "");
LogicalBreakpoint lbEn = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00401070))));
placeBreakpoint(1, lbEn.getTraceAddress(tb.trace), true);
waitForCondition(() -> lbEn.computeState() == State.ENABLED);
addPlugin(tool, FunctionGraphPlugin.class);
@ -299,31 +313,35 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
@Test
public void testCaptureDebuggerDecompilerBreakpointMargin() throws Throwable {
mb.createTestModel();
modelService.addModel(mb.testModel);
mb.createTestProcessesAndThreads();
TestDebuggerTargetTraceMapper mapper = new TestDebuggerTargetTraceMapper(mb.testProcess1);
TraceRecorder recorder =
modelService.recordTarget(mb.testProcess1, mapper, ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
Target target = new MockTarget(tb.trace);
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager()
.createRootObject(AbstractGhidraHeadedDebuggerIntegrationTest.SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
}
targetService.publishTarget(target);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
tool.getProject()
.getProjectData()
.getRootFolder()
.createFile("WinHelloCPP", program, TaskMonitor.DUMMY);
try (Transaction tx = trace.openTransaction("Add Mapping")) {
mappingService.addIdentityMapping(trace, program, Lifespan.nowOn(0), true);
try (Transaction tx = tb.startTransaction()) {
mappingService.addIdentityMapping(tb.trace, program, Lifespan.nowOn(0), true);
}
waitForValue(() -> mappingService.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(0), mb.addr(0x00401070))));
new DefaultTraceLocation(tb.trace, null, Lifespan.at(0), tb.addr(0x00401070))));
Msg.debug(this, "Placing breakpoint");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401070), 1,
Set.of(TraceBreakpointKind.SW_EXECUTE), "");
LogicalBreakpoint lbEn = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00401070))));
placeBreakpoint(1, lbEn.getTraceAddress(tb.trace), true);
waitForCondition(() -> lbEn.computeState() == State.ENABLED);
addPlugin(tool, DecompilePlugin.class);

View file

@ -24,8 +24,8 @@ import java.util.Set;
import org.junit.*;
import db.Transaction;
import ghidra.app.plugin.core.debug.service.MockTarget;
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
import ghidra.app.plugin.core.debug.service.control.MockTarget;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,20 +15,38 @@
*/
package ghidra.app.plugin.core.debug.gui;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.ArrayUtils;
import db.Transaction;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.*;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.target.ActionName;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.stack.TraceObjectStackFrame;
import ghidra.trace.model.target.*;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.util.NumericUtilities;
public class AbstractGhidraHeadedDebuggerIntegrationTest
extends AbstractGhidraHeadedDebuggerTest {
@ -129,6 +147,10 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
protected TestRemoteMethod rmiMethodExecute;
protected TestRemoteMethod rmiMethodActivateProcess;
protected TestRemoteMethod rmiMethodActivateThread;
protected TestRemoteMethod rmiMethodActivateFrame;
protected TestRemoteMethod rmiMethodResume;
protected TestRemoteMethod rmiMethodInterrupt;
protected TestRemoteMethod rmiMethodKill;
@ -151,7 +173,17 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
protected TestRemoteMethod rmiMethodWriteMem;
protected void createRmiConnection() {
rmiCx = new TestTraceRmiConnection();
rmiCx = new TestTraceRmiConnection() {
@Override
protected DebuggerTraceManagerService getTraceManager() {
return traceManager;
}
@Override
protected DebuggerControlService getControlService() {
return tool.getService(DebuggerControlService.class);
}
};
}
protected void addExecuteMethod() {
@ -165,6 +197,36 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
rmiCx.getMethods().add(rmiMethodExecute);
}
protected void addActivateMethods() {
rmiMethodActivateProcess =
new TestRemoteMethod("activate_process", ActionName.ACTIVATE, "Activate Process",
"Activate a process", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
"The process to activate"));
rmiMethodActivateThread =
new TestRemoteMethod("activate_thread", ActionName.ACTIVATE, "Activate Thread",
"Activate a thread", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to activate"));
rmiMethodActivateFrame =
new TestRemoteMethod("activate_frame", ActionName.ACTIVATE, "Activate Frame",
"Activate a frame", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("frame", new SchemaName("Frame"), true, null, "Frame",
"The frame to activate"));
rmiCx.getMethods().add(rmiMethodActivateProcess);
rmiCx.getMethods().add(rmiMethodActivateThread);
rmiCx.getMethods().add(rmiMethodActivateFrame);
}
protected boolean activationMethodsQueuesEmpty() {
return rmiMethodActivateProcess.argQueue().isEmpty() &&
rmiMethodActivateThread.argQueue().isEmpty() &&
rmiMethodActivateFrame.argQueue().isEmpty();
}
protected void addControlMethods() {
rmiMethodResume = new TestRemoteMethod("resume", ActionName.RESUME, "Resume",
"Resume the target", EnumerableTargetObjectSchema.VOID,
@ -286,6 +348,26 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
reg.add(rmiMethodWriteReg);
}
protected void handleReadRegsInvocation(TraceObject container, Callable<Object> action)
throws Throwable {
Map<String, Object> args = rmiMethodReadRegs.expect();
rmiMethodReadRegs.result(action.call());
assertEquals(Map.ofEntries(
Map.entry("container", container)),
args);
}
protected void handleWriteRegInvocation(TraceObjectStackFrame frame, String name, long value)
throws Throwable {
Map<String, Object> args = rmiMethodWriteReg.expect();
rmiMethodWriteReg.result(null);
assertEquals(Set.of("frame", "name", "value"), args.keySet());
assertEquals(frame.getObject(), args.get("frame"));
assertEquals(name, args.get("name"));
byte[] bytes = (byte[]) args.get("value");
assertEquals(value, Utils.bytesToLong(bytes, bytes.length, true));
}
protected void addMemoryMethods() {
rmiMethodReadMem = new TestRemoteMethod("read_mem", ActionName.READ_MEM, "Read Memory",
"Read memory", EnumerableTargetObjectSchema.VOID,
@ -322,6 +404,84 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
return regionText;
}
protected void handleReadMemInvocation(TraceObject process, AddressRange range,
Callable<Object> action) throws Exception {
assertEquals(Map.ofEntries(
Map.entry("process", process),
Map.entry("range", range)),
rmiMethodReadMem.expect());
rmiMethodReadMem.result(action.call());
}
protected void handleReadMemInvocation(TraceObject process, AddressRange range)
throws Exception {
handleReadMemInvocation(process, range, () -> {
try (Transaction tx = tb.startTransaction()) {
tb.trace.getMemoryManager().setState(0, range, TraceMemoryState.KNOWN);
}
return null;
});
}
protected void flushMemoryReadInvocations(Supplier<CompletableFuture<?>> taskSupplier,
TraceObject process, AddressRange range) throws Exception {
while (!taskSupplier.get().isDone()) {
while (!rmiMethodReadMem.argQueue().isEmpty()) {
handleReadMemInvocation(process, range, () -> null);
}
}
}
protected void handleAtLeastOneMemReadInv(Supplier<CompletableFuture<?>> taskSupplier,
TraceObject process, AddressRange range) throws Exception {
handleReadMemInvocation(process, range);
flushMemoryReadInvocations(taskSupplier, process, range);
}
public record Bytes(byte[] bytes) {
public static Object wrapMaybe(Object v) {
return switch (v) {
case byte[] b -> new Bytes(b);
default -> v;
};
}
public static Map<String, Object> wrapVals(Map<String, Object> map) {
return map.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> Bytes.wrapMaybe(e.getValue())));
}
public Bytes(int... values) {
this(ArrayUtils.toPrimitive(
IntStream.of(values).mapToObj(i -> (byte) i).toArray(Byte[]::new)));
}
@Override
public final boolean equals(Object o) {
return o instanceof Bytes that && Arrays.equals(this.bytes, that.bytes);
}
@Override
public String toString() {
return "Bytes[" + NumericUtilities.convertBytesToString(bytes) + "]";
}
public ByteBuffer buf() {
return ByteBuffer.wrap(bytes);
}
}
protected void handleWriteMemInvocation(TraceObject process, Address start, Bytes data)
throws Exception {
assertEquals(Map.ofEntries(
Map.entry("process", tb.obj("Processes[1]")),
Map.entry("start", start),
Map.entry("data", data)),
Bytes.wrapVals(rmiMethodWriteMem.expect()));
rmiMethodWriteMem.result(null);
}
protected TraceObject ensureBreakpointContainer(TraceObjectManager objs) {
try (Transaction tx = tb.startTransaction()) {
return objs.createObject(TraceObjectKeyPath.parse("Processes[1].Breakpoints"));

View file

@ -1,116 +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.plugin.core.debug.gui.breakpoint;
import java.io.IOException;
import org.junit.experimental.categories.Category;
import db.Transaction;
import generic.test.category.NightlyCategory;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath;
@Category(NightlyCategory.class)
public class DebuggerObjectRecorderBreakpointsProviderTest
extends DebuggerRecorderBreakpointsProviderTest {
protected SchemaContext ctx;
@Override
protected void createTrace(String langID) throws IOException {
super.createTrace(langID);
try {
activateObjectsMode();
}
catch (Exception e) {
throw new AssertionError(e);
}
}
@Override
protected void useTrace(Trace trace) {
super.useTrace(trace);
try {
activateObjectsMode();
}
catch (Exception e) {
throw new AssertionError(e);
}
}
public void activateObjectsMode() throws Exception {
// NOTE the use of index='...' allowing object-based managers to ID unique path
// TODO: I guess this'll burn down if the naming scheme changes....
int index = tb.trace.getName().startsWith("[3]") ? 3 : 1;
ctx = XmlSchemaContext.deserialize(String.format("""
<context>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='Processes' schema='ProcessContainer' />
</schema>
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element index='%d' schema='Process' /> <!-- NOTE HERE -->
</schema>
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
<interface name='Aggregate' />
<attribute name='Threads' schema='ThreadContainer' />
<attribute name='Memory' schema='RegionContainer' />
<attribute name='Breakpoints' schema='BreakpointContainer' />
</schema>
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Thread' />
</schema>
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Aggregate' />
<interface name='Thread' />
<attribute name='Registers' schema='Registers' />
</schema>
<schema name='Registers' elementResync='NEVER' attributeResync='NEVER'>
<interface name='RegisterBank' />
<interface name='RegisterContainer' />
</schema>
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Region' />
</schema>
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
<interface name='MemoryRegion' />
</schema>
<schema name='BreakpointContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<interface name='BreakpointSpecContainer' />
<element schema='Breakpoint' />
</schema>
<schema name='Breakpoint' elementResync='NEVER' attributeResync='NEVER'>
<interface name='BreakpointSpec' />
<interface name='BreakpointLocation' />
</schema>
</context>
""", index));
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
tb.trace.getObjectManager()
.createObject(
TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints"));
}
}
}

View file

@ -1,82 +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.plugin.core.debug.gui.breakpoint;
import java.util.Set;
import java.util.concurrent.*;
import org.junit.experimental.categories.Category;
import generic.test.category.NightlyCategory;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerRecorderBreakpointMarkerPluginTest
extends AbstractDebuggerBreakpointMarkerPluginTest<TraceRecorder> {
@Override
protected TraceRecorder createLive() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitRecorder(recorder);
return recorder;
}
@Override
protected Trace getTrace(TraceRecorder recorder) {
return recorder.getTrace();
}
@Override
protected void waitT(TraceRecorder recorder) throws Throwable {
waitRecorder(recorder);
}
@Override
protected void addLiveMemoryAndBreakpoint(TraceRecorder recorder)
throws InterruptedException, ExecutionException, TimeoutException {
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(0x55550123), Set.of(TargetBreakpointKind.SW_EXECUTE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
@Override
protected void handleSetBreakpointInvocation(Set<TraceBreakpointKind> expectedKinds,
long dynOffset) {
// The logic is already embedded in the Test model
}
@Override
protected void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint,
boolean expectedEn) throws Throwable {
// The logic is already embedded in the Test model
}
@Override
protected void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint)
throws Throwable {
// The logic is already embedded in the Test model
}
}

View file

@ -1,98 +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.plugin.core.debug.gui.breakpoint;
import static org.junit.Assert.assertNull;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
public class DebuggerRecorderBreakpointsProviderTest
extends AbstractDebuggerBreakpointsProviderTest<TraceRecorder, TestTargetProcess> {
@Override
protected TraceRecorder createTarget1() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
return modelService.recordTarget(mb.testProcess1, createTargetTraceMapper(mb.testProcess1),
ActionSource.AUTOMATIC);
}
@Override
protected TraceRecorder createTarget3() throws Throwable {
return modelService.recordTarget(mb.testProcess3, createTargetTraceMapper(mb.testProcess3),
ActionSource.AUTOMATIC);
}
@Override
protected TestTargetProcess getProcess1() {
return mb.testProcess1;
}
@Override
protected TestTargetProcess getProcess3() {
return mb.testProcess3;
}
@Override
protected Trace getTrace(TraceRecorder recorder) {
return recorder.getTrace();
}
@Override
protected void waitTarget(TraceRecorder recorder) throws Throwable {
waitRecorder(recorder);
}
@Override
protected void addLiveMemory(TestTargetProcess process) throws Exception {
process.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
}
@Override
protected void addLiveBreakpoint(TraceRecorder recorder, long offset) throws Exception {
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
@Override
protected void handleSetBreakpointInvocation(Set<TraceBreakpointKind> expectedKinds,
long dynOffset) throws Throwable {
// Logic already in Test model
}
@Override
protected void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint,
boolean expectedEnabled) throws Throwable {
// Logic already in Test model
}
@Override
protected void assertNotLiveBreakpoint(TraceRecorder recorder, TraceBreakpoint breakpoint)
throws Throwable {
assertNull(recorder.getTargetBreakpoint(breakpoint));
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -43,7 +43,6 @@ import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPluginTestHelper;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
@ -51,18 +50,12 @@ import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerEmulationService;
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.dbg.model.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.model.DebuggerTargetTraceMapper;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.pcode.exec.SuspendedPcodeExecutionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.ShortDataType;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.listing.Instruction;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Lifespan;
@ -97,204 +90,6 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerInteg
controlService = addPlugin(tool, DebuggerControlServicePlugin.class);
emulationService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
controlPlugin = addPlugin(tool, DebuggerControlPlugin.class);
mb = new TestDebuggerModelBuilder() {
@Override
protected TestDebuggerObjectModel newModel(String typeHint) {
commands.clear();
return new TestDebuggerObjectModel(typeHint) {
@Override
protected TestTargetThread newTestTargetThread(
TestTargetThreadContainer container, int tid) {
return new TestTargetThread(container, tid) {
{
setState(TargetExecutionState.STOPPED);
}
@Override
public CompletableFuture<Void> resume() {
commands.add("resume");
setState(TargetExecutionState.RUNNING);
return super.resume();
}
@Override
public CompletableFuture<Void> interrupt() {
commands.add("interrupt");
setState(TargetExecutionState.STOPPED);
return super.interrupt();
}
@Override
public CompletableFuture<Void> kill() {
commands.add("kill");
setState(TargetExecutionState.TERMINATED);
return super.kill();
}
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
commands.add("step(" + kind + ")");
setState(TargetExecutionState.RUNNING);
setState(TargetExecutionState.STOPPED);
return super.step(kind);
}
};
}
@Override
public CompletableFuture<Void> close() {
commands.add("close");
return super.close();
}
};
}
};
}
@Override
protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target)
throws Exception {
return new ObjectBasedDebuggerTargetTraceMapper(target,
new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of());
}
@Override
protected TraceRecorder recordAndWaitSync() throws Throwable {
TraceRecorder recorder = super.recordAndWaitSync();
useTrace(recorder.getTrace());
return recorder;
}
@Override
protected TargetObject chooseTarget() {
return mb.testModel.session;
}
@Test
public void testRecorderTargetResumeAction() throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(recorder.requestFocus(mb.testThread1));
waitRecorder(recorder);
waitForSwing();
DockingAction actionTargetResume = controlPlugin.actionTargetResume;
assertInTool(actionTargetResume);
performEnabledAction(null, actionTargetResume, true);
waitForTasks();
waitRecorder(recorder);
assertEquals(List.of("resume"), commands);
waitForSwing();
assertFalse(actionTargetResume.isEnabled());
}
@Test
public void testRecorderTargetInterruptAction() throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(recorder.requestFocus(mb.testThread1));
waitRecorder(recorder);
waitForSwing();
DockingAction actionTargetInterrupt = controlPlugin.actionTargetInterrupt;
assertInTool(actionTargetInterrupt);
assertFalse(actionTargetInterrupt.isEnabled());
waitOn(mb.testThread1.resume());
waitRecorder(recorder);
commands.clear();
waitForSwing();
performEnabledAction(null, actionTargetInterrupt, true);
waitForTasks();
waitRecorder(recorder);
assertEquals(List.of("interrupt"), commands);
waitForSwing();
assertFalse(actionTargetInterrupt.isEnabled());
}
@Test
public void testRecorderTargetKillAction() throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(recorder.requestFocus(mb.testThread1));
waitRecorder(recorder);
waitForSwing();
DockingAction actionTargetKill = controlPlugin.actionTargetKill;
assertInTool(actionTargetKill);
performEnabledAction(null, actionTargetKill, true);
waitForTasks();
waitRecorder(recorder);
assertEquals(List.of("kill"), commands);
waitForSwing();
assertFalse(actionTargetKill.isEnabled());
}
@Test
public void testRecorderTargetDisconnectAction() throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
DockingAction actionTargetDisconnect = controlPlugin.actionTargetDisconnect;
assertInTool(actionTargetDisconnect);
performEnabledAction(null, actionTargetDisconnect, true);
waitForTasks();
waitRecorder(recorder);
assertEquals(List.of("close"), commands);
waitForSwing();
waitForPass(() -> assertFalse(actionTargetDisconnect.isEnabled()));
}
protected void runTestRecorderTargetStepAction(Supplier<DockingAction> actionSupplier,
TargetStepKind expected) throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(recorder.requestFocus(mb.testThread1));
waitRecorder(recorder);
waitForSwing();
DockingAction action = actionSupplier.get();
assertInTool(action);
performEnabledAction(null, action, true);
waitForTasks();
waitRecorder(recorder);
assertEquals(List.of("step(" + expected + ")"), commands);
waitForSwing();
assertTrue(actionSupplier.get().isEnabled());
}
@Test
public void testRecorderTargetStepIntoAction() throws Throwable {
runTestRecorderTargetStepAction(() -> controlPlugin.actionTargetStepInto,
TargetStepKind.INTO);
}
@Test
public void testRecorderTargetStepOverAction() throws Throwable {
runTestRecorderTargetStepAction(() -> controlPlugin.actionTargetStepOver,
TargetStepKind.OVER);
}
@Test
public void testRecorderTargetStepOutAction() throws Throwable {
runTestRecorderTargetStepAction(() -> controlPlugin.actionTargetStepOut,
TargetStepKind.FINISH);
}
protected TraceObject setUpRmiTarget() throws Throwable {

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,159 +17,79 @@ package ghidra.app.plugin.core.debug.gui.control;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.jdom.JDOMException;
import org.junit.Before;
import org.junit.Test;
import db.Transaction;
import docking.action.DockingActionIf;
import generic.Unique;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.*;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.async.AsyncUtils;
import ghidra.dbg.model.*;
import ghidra.dbg.target.TargetMethod;
import ghidra.dbg.target.TargetMethod.AnnotatedTargetMethod;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Lifespan;
public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebuggerTest {
public static final XmlSchemaContext SCHEMA_CTX;
public static final TargetObjectSchema MOD_ROOT_SCHEMA;
static {
try {
SCHEMA_CTX = XmlSchemaContext.deserialize(
EmptyDebuggerObjectModel.class.getResourceAsStream("test_schema.xml"));
SchemaBuilder builder =
new SchemaBuilder(SCHEMA_CTX, SCHEMA_CTX.getSchema(SCHEMA_CTX.name("Thread")));
SchemaName method = SCHEMA_CTX.name("Method");
builder.addAttributeSchema(
new DefaultAttributeSchema("Advance", method, true, true, true), "manual");
builder.addAttributeSchema(
new DefaultAttributeSchema("StepExt", method, true, true, true), "manual");
builder.addAttributeSchema(
new DefaultAttributeSchema("AdvanceWithFlag", method, true, true, true), "manual");
builder.addAttributeSchema(
new DefaultAttributeSchema("Between", method, true, true, true), "manual");
SCHEMA_CTX.replaceSchema(builder.build());
MOD_ROOT_SCHEMA = SCHEMA_CTX.getSchema(SCHEMA_CTX.name("Test"));
}
catch (IOException | JDOMException e) {
throw new AssertionError(e);
}
}
public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebuggerIntegrationTest {
DebuggerListingPlugin listingPlugin;
DebuggerStaticMappingService mappingService;
DebuggerMethodActionsPlugin methodsPlugin;
List<String> commands = Collections.synchronizedList(new ArrayList<>());
TestRemoteMethod rmiMethodAdvance;
TestRemoteMethod rmiMethodStepExt;
TestRemoteMethod rmiMethodAdvanceWithFlag;
TestRemoteMethod rmiMethodBetween;
@Before
public void setUpMethodActionsTest() throws Exception {
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
methodsPlugin = addPlugin(tool, DebuggerMethodActionsPlugin.class);
mb = new TestDebuggerModelBuilder() {
@Override
protected TestDebuggerObjectModel newModel(String typeHint) {
commands.clear();
return new TestDebuggerObjectModel(typeHint) {
@Override
public TargetObjectSchema getRootSchema() {
return MOD_ROOT_SCHEMA;
}
@Override
protected TestTargetThread newTestTargetThread(
TestTargetThreadContainer container, int tid) {
return new TestTargetThread(container, tid) {
{
changeAttributes(List.of(),
AnnotatedTargetMethod.collectExports(MethodHandles.lookup(),
testModel, this),
"Initialize");
}
@TargetMethod.Export("Advance")
public CompletableFuture<Void> advance(
@TargetMethod.Param(
description = "The target address",
display = "Target",
name = "target") Address target) {
commands.add("advance(" + target + ")");
return AsyncUtils.nil();
}
// Takes no address context
@TargetMethod.Export("StepExt")
public CompletableFuture<Void> stepExt() {
commands.add("stepExt");
return AsyncUtils.nil();
}
// Takes a second required non-default parameter
@TargetMethod.Export("AdvanceWithFlag")
public CompletableFuture<Void> advanceWithFlag(
@TargetMethod.Param(
description = "The target address",
display = "Target",
name = "target") Address address,
@TargetMethod.Param(
description = "The flag",
display = "Flag",
name = "flag") boolean flag) {
commands.add("advanceWithFlag(" + address + "," + flag + ")");
return AsyncUtils.nil();
}
// Takes a second address parameter
@TargetMethod.Export("Between")
public CompletableFuture<Void> between(
@TargetMethod.Param(
description = "The starting address",
display = "Start",
name = "start") Address start,
@TargetMethod.Param(
description = "The ending address",
display = "End",
name = "end") Address end) {
commands.add("between(" + start + "," + end + ")");
return AsyncUtils.nil();
}
};
}
};
}
};
}
protected Collection<TargetMethod> collectMethods(TargetObject object) {
return object.getModel()
.getRootSchema()
.matcherForSuitable(TargetMethod.class, object.getPath())
.getCachedSuccessors(object.getModel().getModelRoot())
.values()
.stream()
.filter(o -> o instanceof TargetMethod)
.map(o -> (TargetMethod) o)
.toList();
protected void addMethods() {
TestRemoteMethodRegistry reg = rmiCx.getMethods();
rmiMethodAdvance = new TestRemoteMethod("advance", null, "Advance",
"Advance to the given address", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to advance"),
new TestRemoteParameter("target", EnumerableTargetObjectSchema.ADDRESS, true, null,
"Target", "The target address"));
reg.add(rmiMethodAdvance);
rmiMethodStepExt = new TestRemoteMethod("step_ext", null, "StepExt",
"Step in some special way", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to step"));
reg.add(rmiMethodStepExt);
rmiMethodAdvanceWithFlag = new TestRemoteMethod("advance_flag", null, "Advance With Flag",
"Advance to the given address, with flag", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to advance"),
new TestRemoteParameter("target", EnumerableTargetObjectSchema.ADDRESS, true, null,
"Target", "The target address"),
new TestRemoteParameter("flag", EnumerableTargetObjectSchema.BOOL, true, null,
"Flag", "The flag"));
reg.add(rmiMethodAdvanceWithFlag);
rmiMethodBetween = new TestRemoteMethod("between", null, "Between",
"Advance between two given addresses", EnumerableTargetObjectSchema.VOID,
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
"The thread to advance"),
new TestRemoteParameter("start", EnumerableTargetObjectSchema.ADDRESS, true, null,
"Start", "The starting address"),
new TestRemoteParameter("end", EnumerableTargetObjectSchema.ADDRESS, true, null,
"End", "The ending address"));
reg.add(rmiMethodBetween);
}
@Test
@ -184,36 +104,45 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge
@Test
public void testGetPopupActionsNoThread() throws Throwable {
createTestModel();
recordAndWaitSync();
createRmiConnection();
addMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
}
rmiCx.publishTarget(tool, tb.trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(4, collectMethods(mb.testThread1).size());
createProgramFromTrace(tb.trace);
programManager.openProgram(program);
// TODO: I think the real reason for empty is the address is not mappable
ProgramLocationActionContext ctx =
new ProgramLocationActionContext(listingPlugin.getProvider(), program,
new ProgramLocation(program, addr(program, 0)), null, null);
waitOn(mb.testModel.requestFocus(mb.testProcess1));
traceManager.activateObject(tb.obj("Processes[1]"));
waitForSwing();
assertEquals(List.of(), methodsPlugin.getPopupActions(tool, ctx));
}
@Test
public void testGetPopupActions() throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(recorder.requestFocus(mb.testThread1));
waitRecorder(recorder);
waitForSwing();
createRmiConnection();
addMethods();
createTrace();
assertEquals(4, collectMethods(mb.testThread1).size());
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
}
rmiCx.publishTarget(tool, tb.trace);
traceManager.openTrace(tb.trace);
traceManager.activateObject(tb.obj("Processes[1].Threads[1]"));
createProgramFromTrace(tb.trace);
intoProject(program);
@ -234,22 +163,28 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge
ProgramLocationActionContext ctx =
new ProgramLocationActionContext(listingPlugin.getProvider(), program,
new ProgramLocation(program, addr(program, 0x00400000)), null, null);
assertEquals(List.of("Advance"),
methodsPlugin.getPopupActions(tool, ctx).stream().map(a -> a.getName()).toList());
// TODO: Should "Between" be included, too?
assertEquals(Set.of("Advance", "Advance With Flag"),
methodsPlugin.getPopupActions(tool, ctx)
.stream()
.map(a -> a.getName())
.collect(Collectors.toSet()));
}
@Test
public void testMethodInvocation() throws Throwable {
createTestModel();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(recorder.requestFocus(mb.testThread1));
waitRecorder(recorder);
waitForSwing();
createRmiConnection();
addMethods();
createTrace();
assertEquals(4, collectMethods(mb.testThread1).size());
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
}
rmiCx.publishTarget(tool, tb.trace);
traceManager.openTrace(tb.trace);
traceManager.activateObject(tb.obj("Processes[1].Threads[1]"));
createProgramFromTrace(tb.trace);
intoProject(program);
@ -271,11 +206,17 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge
new ProgramLocationActionContext(listingPlugin.getProvider(), program,
new ProgramLocation(program, addr(program, 0x00400000)), null, null);
DockingActionIf advance = Unique.assertOne(methodsPlugin.getPopupActions(tool, ctx));
assertEquals("Advance", advance.getName());
performAction(advance, ctx, true);
waitRecorder(recorder);
DockingActionIf advance = methodsPlugin.getPopupActions(tool, ctx)
.stream()
.filter(a -> a.getName().equals("Advance"))
.findFirst()
.orElseThrow();
performAction(advance, ctx, false);
assertEquals(List.of("advance(00400000)"), commands);
assertEquals(Map.ofEntries(
Map.entry("thread", tb.obj("Processes[1].Threads[1]")),
Map.entry("target", tb.addr(0x00400000))),
rmiMethodAdvance.expect());
rmiMethodAdvance.result(null);
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,8 +17,7 @@ package ghidra.app.plugin.core.debug.gui.copying;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
@ -28,7 +27,7 @@ import db.Transaction;
import docking.action.DockingActionIf;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec;
import ghidra.app.plugin.core.debug.gui.action.NoneAutoReadMemorySpec;
import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyIntoProgramDialog.RangeEntry;
@ -36,9 +35,6 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.target.TargetObject;
import ghidra.debug.api.action.ActionSource;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
@ -47,9 +43,10 @@ import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.target.TraceObject;
@Category(NightlyCategory.class)
public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerTest {
public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerIntegrationTest {
DebuggerCopyActionsPlugin copyActionsPlugin;
DebuggerListingPlugin listingPlugin;
@ -430,31 +427,24 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerT
public void testActionCopyIntoNewProgramCaptureLive() throws Throwable {
assertDisabled(copyActionsPlugin.actionCopyIntoNewProgram);
createTestModel();
var listener = new DebuggerModelListener() {
int count = 0;
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
count++;
}
};
mb.testModel.addModelListener(listener);
mb.createTestProcessesAndThreads();
modelService.recordTarget(mb.testProcess1, createTargetTraceMapper(mb.testProcess1),
ActionSource.AUTOMATIC);
mb.testProcess1.memory.addRegion(".text", mb.rng(0x55550000, 0x5555ffff), "rx");
mb.testProcess1.memory.setMemory(mb.addr(0x55550000), mb.arr(1, 2, 3, 4, 5, 6, 7, 8));
waitForPass(() -> {
assertEquals(1, tb.trace.getMemoryManager().getAllRegions().size());
});
createRmiConnection();
addMemoryMethods();
createAndOpenTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[.text]", Lifespan.nowOn(0),
tb.range(0x55550000, 0x5555ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE))
.setName("[.text]");
}
TraceObject process = tb.obj("Processes[1]");
rmiCx.publishTarget(tool, tb.trace);
listingProvider.setAutoReadMemorySpec(
AutoReadMemorySpec.fromConfigName(NoneAutoReadMemorySpec.CONFIG_NAME));
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
assertDisabled(copyActionsPlugin.actionCopyIntoNewProgram);
@ -476,11 +466,21 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerT
assertTrue(entry.isCreate());
entry.setBlockName(".my_text");
assertEquals(0, listener.count);
dialog.okCallback();
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
runSwingLater(() -> dialog.okCallback());
for (int i = 0; i < 16; i++) {
long start = 0x55550000 + 0x1000 * i;
handleReadMemInvocation(process, tb.range(start, start + 0xfff), () -> {
try (Transaction tx = tb.startTransaction()) {
// Yeah, it'll get written 16 times, but no harm.
tb.trace.getMemoryManager()
.putBytes(0, tb.addr(0x55550000), tb.buf(1, 2, 3, 4, 5, 6, 7, 8));
}
return null;
});
}
waitOn(dialog.lastTask);
waitForSwing();
assertEquals(16, listener.count);
// Declare my own, or the @After will try to release it erroneously
Program program = waitForValue(() -> programManager.getCurrentProgram());
@ -490,13 +490,7 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerT
assertEquals(tb.addr(stSpace, 0x55550000), text.getStart());
assertEquals(".my_text", text.getName());
byte[] arr = new byte[8];
/**
* While waitOn will ensure the read request completes, it doesn't ensure the recorder has
* actually written the result to the database, yet.
*/
waitForPass(noExc(() -> {
text.getBytes(tb.addr(stSpace, 0x55550000), arr);
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), arr);
}));
text.getBytes(tb.addr(stSpace, 0x55550000), arr);
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8), arr);
}
}

View file

@ -23,7 +23,6 @@ import java.awt.event.MouseEvent;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Set;
import org.junit.*;
@ -40,19 +39,20 @@ import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.codebrowser.hover.ReferenceListingHoverPlugin;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.ProgramManager;
import ghidra.app.plugin.core.debug.service.progress.ProgressServicePlugin;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.modules.DebuggerMissingModuleActionContext;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.*;
@ -73,14 +73,14 @@ import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@Category(NightlyCategory.class)
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTest {
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerIntegrationTest {
protected DebuggerListingPlugin listingPlugin;
protected DebuggerListingProvider listingProvider;
@ -657,114 +657,98 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
@Test
public void testAutoReadMemoryReads() throws Exception {
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
// Otherwise, some tasks may never finish, and test fails
addPlugin(tool, ProgressServicePlugin.class);
assertEquals(readVisROOnce, listingProvider.getAutoReadMemorySpec());
listingProvider.setAutoReadMemorySpec(readNone);
runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone));
createTestModel();
mb.createTestProcessesAndThreads();
createRmiConnection();
addMemoryMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L),
tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ,
TraceMemoryFlag.EXECUTE);
}
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
rmiCx.publishTarget(tool, tb.trace);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data);
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
goToDyn(addr(trace, 0x55550800));
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
goToDyn(addr(trace, 0x55551800));
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
/**
* NOTE: Should read immediately upon setting auto-read, but we're not focused on the
* written block
*/
listingProvider.setAutoReadMemorySpec(readVisROOnce);
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
/**
* We're now moving to the written block
*/
goToDyn(addr(trace, 0x55550800));
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForDomainObject(trace);
// NB. Recorder can delay writing in a thread / queue
waitForPass(() -> {
buf.clear();
assertEquals(data.length, trace.getMemoryManager()
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(data, buf.array());
});
goToDyn(tb.addr(0x55550800));
waitForSwing();
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
goToDyn(tb.addr(0x55551800));
waitForSwing();
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
// Should read upon setting auto-read.
runSwing(() -> listingProvider.setAutoReadMemorySpec(readVisROOnce));
handleAtLeastOneMemReadInv(listingProvider::getLastAutoRead, process,
tb.range(0x55551000, 0x55551fff));
// Should also read upon navigating elsewhere.
goToDyn(tb.addr(0x55550800));
handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff));
/**
* Because goToDyn has to retry, it can generate many read requests. Turn off auto-read and
* then flush out any pending reads, so that all the tasks can complete.
*/
runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone));
flushMemoryReadInvocations(listingProvider::getLastAutoRead, process,
tb.range(0x55550000, 0x55550fff));
}
public void runTestAutoReadMemoryReadsWithForceFullView(AutoReadMemorySpec spec)
throws Throwable {
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
listingProvider.setAutoReadMemorySpec(spec);
runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone));
createTestModel();
mb.createTestProcessesAndThreads();
createRmiConnection();
addMemoryMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
// NOTE: Do not add a region. Depend on Force full view!
}
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
rmiCx.publishTarget(tool, tb.trace);
// NOTE: Do not add a region. Depend on Force full view!
// Populate target memory before recording
mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data);
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
waitRecorder(recorder);
traceManager.activateTrace(trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
runSwing(() -> tb.trace.getProgramView().getMemory().setForceFullView(true));
runSwing(() -> trace.getProgramView().getMemory().setForceFullView(true));
goToDyn(tb.addr(0x55550000));
runSwing(() -> listingProvider.setAutoReadMemorySpec(spec));
waitForPass(noExc(() -> {
goToDyn(addr(trace, 0x55550000));
waitRecorder(recorder);
handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff));
buf.clear();
assertEquals(data.length, trace.getMemoryManager()
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(data, buf.array());
}));
runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone));
flushMemoryReadInvocations(listingProvider::getLastAutoRead, process,
tb.range(0x55550000, 0x55550fff));
}
@Test
@Ignore("Until GP-5180")
public void testAutoReadMemoryVisROOnceReadsWithForceFullView() throws Throwable {
runTestAutoReadMemoryReadsWithForceFullView(readVisROOnce);
}
@Test
@Ignore("Until GP-5180")
public void testAutoReadMemoryVisibleReadsWithForceFullView() throws Throwable {
runTestAutoReadMemoryReadsWithForceFullView(readVisible);
}
@ -1122,101 +1106,55 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
}
@Test
@Ignore("TODO") // Needs attention, but low priority
public void testActionReadSelectedMemory() throws Exception {
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
public void testActionRefreshMemory() throws Exception {
DebuggerControlService controlService = addPlugin(tool, DebuggerControlServicePlugin.class);
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
listingProvider.setAutoReadMemorySpec(readNone);
runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone));
// To verify enabled requires live target
createAndOpenTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
tb.trace.getMemoryManager()
.addRegion("exe:.text", Lifespan.nowOn(0), tb.range(0x55550000, 0x555500ff),
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0),
tb.range(0x55550000, 0x5555ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
ProgramSelection sel = new ProgramSelection(tb.set(tb.range(0x55550040, 0x5555004f)));
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
runSwing(() -> listingProvider.setSelection(sel));
controlService.setCurrentMode(tb.trace, ControlMode.RO_TARGET);
waitForSwing();
// Still
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
// Now, simulate the sequence that typically enables the action
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x555500ff), "rx");
mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data); // No effect yet
waitForDomainObject(trace);
waitFor(() -> trace.getMemoryManager().getAllRegions().size() == 1);
// NOTE: recordTargetContainerAndOpenTrace has already activated the trace
// Action is still disabled, because it requires a selection
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
runSwing(() -> listingProvider.setSelection(sel));
createRmiConnection();
addMemoryMethods();
rmiCx.publishTarget(tool, tb.trace);
waitForSwing();
// Now, it should be enabled
// Action no longer requires a selection. It uses visible memory.
assertTrue(listingProvider.actionRefreshSelectedMemory.isEnabled());
// First check nothing captured yet
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
// Verify that the action performs the expected task
performAction(listingProvider.actionRefreshSelectedMemory);
waitForBusyTool(tool);
waitForDomainObject(trace);
performAction(listingProvider.actionRefreshSelectedMemory, false);
handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff));
waitForPass(() -> {
buf.clear();
assertEquals(data.length, trace.getMemoryManager()
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
// NOTE: The region is only 256 bytes long
// TODO: This fails unpredictably, and I'm not sure why.
assertArrayEquals(Arrays.copyOf(data, 256), Arrays.copyOf(buf.array(), 256));
});
// Verify that setting the memory inaccessible disables the action
mb.testProcess1.memory.setAccessible(false);
waitForPass(() -> assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that setting it accessible re-enables it (assuming we still have selection)
mb.testProcess1.memory.setAccessible(true);
waitForPass(() -> assertTrue(listingProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that moving into the past disables the action
TraceSnapshot forced = recorder.forceSnapshot();
waitForSwing(); // UI Wants to sync with new snap. Wait....
traceManager.activateSnap(forced.getKey() - 1);
rmiCx.setLastSnapshot(tb.trace, 1);
traceManager.activateSnap(1);
waitForSwing();
controlService.setCurrentMode(tb.trace, ControlMode.RO_TRACE);
traceManager.activateSnap(0);
waitForSwing();
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that advancing to the present enables the action (assuming a selection)
traceManager.activateSnap(forced.getKey());
traceManager.activateSnap(1);
waitForSwing();
assertTrue(listingProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that stopping the recording disables the action
recorder.stopRecording();
rmiCx.withdrawTarget(tool, tb.trace);
waitForSwing();
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
// TODO: When resume recording is implemented, verify action is enabled with selection
}
@Test

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -21,10 +21,10 @@ import static org.junit.Assert.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;
import org.junit.*;
@ -38,45 +38,45 @@ import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.test.category.NightlyCategory;
import ghidra.GhidraOptions;
import ghidra.app.plugin.core.byteviewer.ByteViewerComponent;
import ghidra.app.plugin.core.byteviewer.ByteViewerPanel;
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
import ghidra.app.plugin.core.debug.service.progress.ProgressServicePlugin;
import ghidra.app.services.DebuggerControlService;
import ghidra.async.SwingExecutorService;
import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.stack.DBTraceStackManager;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
@Category(NightlyCategory.class)
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerTest {
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerIntegrationTest {
protected DebuggerMemoryBytesPlugin memBytesPlugin;
protected DebuggerMemoryBytesProvider memBytesProvider;
protected DebuggerControlService editingService;
protected DebuggerControlService controlService;
@Before
public void setUpMemoryBytesProviderTest() throws Exception {
@ -84,7 +84,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
memBytesProvider = waitForComponentProvider(DebuggerMemoryBytesProvider.class);
memBytesProvider.setVisible(true);
editingService = addPlugin(tool, DebuggerControlServicePlugin.class);
controlService = addPlugin(tool, DebuggerControlServicePlugin.class);
}
protected void goToDyn(Address address) {
@ -469,77 +469,56 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testAutoReadMemoryReads() throws Exception {
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
// Otherwise, some tasks may never finish, and test fails
addPlugin(tool, ProgressServicePlugin.class);
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
createTestModel();
mb.createTestProcessesAndThreads();
createRmiConnection();
addMemoryMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L),
tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ,
TraceMemoryFlag.EXECUTE);
}
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
rmiCx.publishTarget(tool, tb.trace);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data);
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
goToDyn(addr(trace, 0x55550800));
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
goToDyn(addr(trace, 0x55551800));
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
/**
* Assure ourselves the block under test is not on screen
*/
waitForPass(() -> {
goToDyn(addr(trace, 0x55551800));
AddressSetView visible = memBytesProvider.readsMemTrait.getVisible();
assertFalse(visible.isEmpty());
assertFalse(visible.contains(addr(trace, 0x55550000)));
assertFalse(visible.contains(addr(trace, 0x55550fff)));
});
/**
* NOTE: Should read immediately upon setting auto-read, but we're not looking at the
* written block
*/
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readVisROOnce));
waitForDomainObject(trace);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
/**
* We're now moving to the written block
*/
goToDyn(addr(trace, 0x55550800));
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForDomainObject(trace);
// NB. Recorder can delay writing in a thread / queue
waitForPass(() -> {
buf.clear();
assertEquals(data.length, trace.getMemoryManager()
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(data, buf.array());
});
goToDyn(tb.addr(0x55550800));
waitForSwing();
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
goToDyn(tb.addr(0x55551800));
waitForSwing();
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
// Should read upon setting auto-read.
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readVisROOnce));
handleAtLeastOneMemReadInv(memBytesProvider::getLastAutoRead, process,
tb.range(0x55551000, 0x55551fff));
// Should also read upon navigating elsewhere.
goToDyn(tb.addr(0x55550800));
handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff));
/**
* Because goToDyn has to retry, it can generate many read requests. Turn off auto-read and
* then flush out any pending reads, so that all the tasks can complete.
*/
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
flushMemoryReadInvocations(memBytesProvider::getLastAutoRead, process,
tb.range(0x55550000, 0x55550fff));
}
@Test
@ -752,102 +731,56 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
}
@Test
@Ignore("TODO") // Needs attention, but low priority
// Accessibility listener does not seem to work
public void testActionReadSelectedMemory() throws Exception {
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
public void testActionRefreshMemory() throws Exception {
DebuggerControlService controlService = addPlugin(tool, DebuggerControlServicePlugin.class);
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
// To verify enabled requires live target
createAndOpenTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
tb.trace.getMemoryManager()
.addRegion("exe:.text", Lifespan.nowOn(0), tb.range(0x55550000, 0x555500ff),
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0),
tb.range(0x55550000, 0x5555ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
ProgramSelection sel = new ProgramSelection(tb.set(tb.range(0x55550040, 0x5555004f)));
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
memBytesProvider.setSelection(sel);
controlService.setCurrentMode(tb.trace, ControlMode.RO_TARGET);
waitForSwing();
// Still
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// Now, simulate the sequence that typically enables the action
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x555500ff), "rx");
mb.testProcess1.memory.setMemory(mb.addr(0x55550000), data); // No effect yet
waitForDomainObject(trace);
waitFor(() -> trace.getMemoryManager().getAllRegions().size() == 1);
// NOTE: recordTargetContainerAndOpenTrace has already activated the trace
// Action is still disabled, because it requires a selection
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
memBytesProvider.setSelection(sel);
createRmiConnection();
addMemoryMethods();
rmiCx.publishTarget(tool, tb.trace);
waitForSwing();
// Now, it should be enabled
// Action no longer requires a selection. It uses visible memory.
assertTrue(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// First check nothing recorded yet
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(zero, buf.array());
assertTrue(rmiMethodReadMem.argQueue().isEmpty());
// Verify that the action performs the expected task
performAction(memBytesProvider.actionRefreshSelectedMemory);
waitForBusyTool(tool);
waitForDomainObject(trace);
performAction(memBytesProvider.actionRefreshSelectedMemory, false);
handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff));
waitForPass(() -> {
buf.clear();
assertEquals(data.length, trace.getMemoryManager()
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
// NOTE: The region is only 256 bytes long
// TODO: This fails unpredictably, and I'm not sure why.
assertArrayEquals(Arrays.copyOf(data, 256), Arrays.copyOf(buf.array(), 256));
});
// Verify that setting the memory inaccessible disables the action
mb.testProcess1.memory.setAccessible(false);
waitForPass(() -> assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that setting it accessible re-enables it (assuming we still have selection)
mb.testProcess1.memory.setAccessible(true);
waitForPass(() -> assertTrue(memBytesProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that moving into the past disables the action
TraceSnapshot forced = recorder.forceSnapshot();
waitForSwing(); // UI Wants to sync with new snap. Wait....
traceManager.activateSnap(forced.getKey() - 1);
rmiCx.setLastSnapshot(tb.trace, 1);
traceManager.activateSnap(1);
waitForSwing();
controlService.setCurrentMode(tb.trace, ControlMode.RO_TRACE);
traceManager.activateSnap(0);
waitForSwing();
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that advancing to the present enables the action (assuming a selection)
traceManager.activateSnap(forced.getKey());
traceManager.activateSnap(1);
waitForSwing();
assertTrue(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that stopping the recording disables the action
recorder.stopRecording();
rmiCx.withdrawTarget(tool, tb.trace);
waitForSwing();
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// TODO: When resume recording is implemented, verify action is enabled with selection
}
@Test
@ -1096,80 +1029,108 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress());
}
protected void sendKeyStroke(int vk, char c) {
ByteViewerComponent viewer = memBytesProvider.getByteViewerPanel().getViewList().get(0);
FieldLocation loc = viewer.getCursorLocation();
KeyEvent ev = new KeyEvent(viewer, 0, new Date().getTime(), 0, vk, c);
viewer.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
loc.getCol(), viewer.getCurrentField());
}
protected void sendKeyStrokeForChar(char c) {
int vk = KeyEvent.getExtendedKeyCodeForChar(c);
assertFalse(vk == KeyEvent.VK_UNDEFINED);
sendKeyStroke(vk, c);
}
protected void sendKeyStrokesForText(String text) {
for (char c : text.toCharArray()) {
sendKeyStrokeForChar(c);
}
}
@Test
public void testEditLiveBytesWritesTarget() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
createRmiConnection();
addMemoryMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
editingService.setCurrentMode(trace, ControlMode.RW_TARGET);
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L),
tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ,
TraceMemoryFlag.EXECUTE);
}
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
rmiCx.publishTarget(tool, tb.trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitRecorder(recorder);
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
byte[] data = new byte[4];
performAction(actionEdit);
waitForPass(noExc(() -> {
traceManager.activateTrace(trace);
goToDyn(addr(trace, 0x55550800));
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
waitForSwing();
waitRecorder(recorder);
mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data);
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
}));
goToDyn(tb.addr(0x55550800));
runSwingLater(() -> sendKeyStrokesForText("42"));
/**
* One for each key press :/
*
* TODO: Perhaps also a curious result that the second is 0x02 and not 0x42. In the test, we
* can easily see because the method doesn't actually mutate the database. However, in
* practice, what if the method is too slow to mutate, and the next keystroke wins the race?
*/
handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x40));
handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x02));
performAction(actionEdit);
rmiCx.withdrawTarget(tool, tb.trace);
}
@Test
public void testEditTraceBytesWritesNotTarget() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
createRmiConnection();
addMemoryMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
editingService.setCurrentMode(trace, ControlMode.RW_TRACE);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L),
tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ,
TraceMemoryFlag.EXECUTE);
}
waitForDomainObject(tb.trace);
rmiCx.publishTarget(tool, tb.trace);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitRecorder(recorder);
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
// Because mode is RW_TRACE, we're not necessarily at recorder's snap
traceManager.activateSnap(recorder.getSnap());
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
byte[] data = new byte[4];
performAction(actionEdit);
waitForPass(noExc(() -> {
traceManager.activateTrace(trace);
goToDyn(addr(trace, 0x55550800));
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
waitForSwing();
waitRecorder(recorder);
trace.getMemoryManager()
.getBytes(traceManager.getCurrentSnap(), addr(trace, 0x55550800),
ByteBuffer.wrap(data));
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
}));
performAction(actionEdit);
waitRecorder(recorder);
controlService.setCurrentMode(tb.trace, ControlMode.RW_TRACE);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
// Verify the target was not touched
Arrays.fill(data, (byte) 0); // test model uses semisparse array
waitForPass(() -> {
mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data);
assertArrayEquals(mb.arr(0, 0, 0, 0), data);
});
performAction(actionEdit);
goToDyn(tb.addr(0x55550800));
runSwing(() -> sendKeyStrokesForText("42"));
Bytes data = new Bytes(new byte[1]);
tb.trace.getMemoryManager().getBytes(0, tb.addr(0x55550800), data.buf());
assertEquals(new Bytes(0x42), data);
assertTrue(rmiMethodWriteMem.argQueue().isEmpty());
performAction(actionEdit);
rmiCx.withdrawTarget(tool, tb.trace);
}
@Test
@ -1177,22 +1138,33 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
addPlugin(tool, ClipboardPlugin.class);
ActionContext ctx;
createTestModel();
mb.createTestProcessesAndThreads();
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
createRmiConnection();
addMemoryMethods();
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
tb.createObjectsProcessAndThreads();
editingService.setCurrentMode(trace, ControlMode.RW_TARGET);
tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L),
tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ,
TraceMemoryFlag.EXECUTE);
}
waitForDomainObject(tb.trace);
TraceObject process = tb.obj("Processes[1]");
rmiCx.publishTarget(tool, tb.trace);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
goToDyn(addr(trace, 0x55550800));
controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
performAction(actionEdit);
performAction(actionEdit);
goToDyn(tb.addr(0x55550800));
Clipboard clipboard = GClipboard.getSystemClipboard();
clipboard.setContents(new StringSelection("42 53 64 75"), null);
@ -1206,12 +1178,14 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
performAction(actionPaste, memBytesProvider, false);
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
pressButtonByText(confirm, "Yes");
// TODO: This shouldn't be separate calls per byte!
handleWriteMemInvocation(process, tb.addr(0x55550800), new Bytes(0x42));
handleWriteMemInvocation(process, tb.addr(0x55550801), new Bytes(0x53));
handleWriteMemInvocation(process, tb.addr(0x55550802), new Bytes(0x64));
handleWriteMemInvocation(process, tb.addr(0x55550803), new Bytes(0x75));
performAction(actionEdit);
byte[] data = new byte[4];
waitForPass(() -> {
mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data);
assertArrayEquals(mb.arr(0x42, 0x53, 0x64, 0x75), data);
});
rmiCx.withdrawTarget(tool, tb.trace);
}
}

Some files were not shown because too many files have changed in this diff Show more