diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerModelService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerModelService.java deleted file mode 100644 index 30bf7f34dd..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerModelService.java +++ /dev/null @@ -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 getModelFactories(); - - /** - * Get the set of models registered with this service - * - * @return the set of models - */ - Set getModels(); - - /** - * Disconnect or close all debugging models (connections) - * - * @return a future which completes when all models are closed - */ - CompletableFuture closeAllModels(); - - /** - * Get the set of active recorders - * - *

- * 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 getTraceRecorders(); - - /** - * Register a model with this service - * - *

- * 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 - * - *

- * 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. - * - *

- * Any given container can be traced by at most one recorder. - * - *

- * 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * A recorder uses conventions to discover the "process" in the model, given a target object. - * - *

- * 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 - * - *

- * 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." - * - *

- * 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * The caller must keep a strong reference to the listener, or it will be automatically removed. - * - * @param listener the listener - */ - void addFactoriesChangedListener(CollectionChangeListener listener); - - /** - * Remove a listener for changes in available model factories - * - * @param listener the listener - */ - void removeFactoriesChangedListener(CollectionChangeListener listener); - - /** - * Listen for changes in registered models - * - *

- * The caller must beep a strong reference to the listener, or it will be automatically removed. - * - *

- * TODO: Probably replace this with a {@link PluginEvent} - * - * @param listener the listener - */ - void addModelsChangedListener(CollectionChangeListener listener); - - /** - * Remove a listener for changes in registered models - * - *

- * TODO: Probably replace this with a {@link PluginEvent} - * - * @param listener the listener - */ - void removeModelsChangedListener(CollectionChangeListener listener); - - /** - * Listen for changes in active trace recorders - * - *

- * The caller must beep a strong reference to the listener, or it will be automatically removed. - * - *

- * TODO: Probably replace this with a {@link PluginEvent} - * - * @param listener the listener - */ - void addTraceRecordersChangedListener(CollectionChangeListener listener); - - /** - * Remove a listener for changes in active trace recorders - * - *

- * TODO: Probably replace this with a {@link PluginEvent} - * - * @param listener the listener - */ - void removeTraceRecordersChangedListener(CollectionChangeListener listener); - - /** - * Collect all offers for launching the given program - * - * @param program the program to launch - * @return the offers - */ - Stream getProgramLaunchOffers(Program program); - - /** - * Prompt the user to create a new connection - * - * @return a future which completes with the new connection, possibly cancelled - */ - CompletableFuture 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 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 showConnectDialog(DebuggerModelFactory factory); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index a00938e210..6e5f7be7f1 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -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 */ diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java index b5d8b175b9..ee6966b4ed 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java @@ -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 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 * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOffer.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOffer.java deleted file mode 100644 index 79654b8719..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOffer.java +++ /dev/null @@ -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. - * - *

- * 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 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 offers) { - List 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. - * - *

- * 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. - * - *

- * 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. - * - *

- * 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(); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOpinion.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOpinion.java deleted file mode 100644 index 8c3307dab9..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOpinion.java +++ /dev/null @@ -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 - * - *

- * 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. - * - *

- * 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 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 - * - *

- * 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 queryOpinions(TargetObject target, - boolean includeOverrides) { - List result = new ArrayList<>(); - for (DebuggerMappingOpinion opinion : ClassSearcher - .getInstances(DebuggerMappingOpinion.class)) { - try { - Set 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 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 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 offersForEnv(TargetEnvironment env, TargetObject target, - boolean includeOverrides); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMemoryMapper.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMemoryMapper.java deleted file mode 100644 index 4c439214ba..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMemoryMapper.java +++ /dev/null @@ -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); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java deleted file mode 100644 index 3cd49b5f53..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java +++ /dev/null @@ -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 - * - *

- * 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 - * - *

- * 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> configureLauncher(TargetLauncher launcher, - Map> 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 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 launchProgram(TaskMonitor monitor, PromptMode prompt) { - return launchProgram(monitor, prompt, LaunchConfigurator.NOP); - } - - /** - * A name so that this offer can be recognized later - * - *

- * 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 cannot 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * 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; - } -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerRegisterMapper.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerRegisterMapper.java deleted file mode 100644 index 9f6ca66e05..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerRegisterMapper.java +++ /dev/null @@ -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. - * - *

- * 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 - * - *

- * 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 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 traceToTarget(Collection registerValues) { - Map result = new LinkedHashMap<>(); - for (RegisterValue rv : registerValues) { - Entry 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 targetToTrace(Map values) { - Map result = new LinkedHashMap<>(); - for (Map.Entry 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 getRegistersOnTarget(); - - /** - * The recorder is informing this mapper of a new target register - * - *

- * 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 - * - *

- * 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. - * - *

- * The mapper should check that the given register is/was in its scope. - * - * @param register the old register - */ - void targetRegisterRemoved(TargetRegister register); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerTargetTraceMapper.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerTargetTraceMapper.java deleted file mode 100644 index 0850c1b7b9..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerTargetTraceMapper.java +++ /dev/null @@ -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); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java deleted file mode 100644 index dc8a22249a..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java +++ /dev/null @@ -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 - * - *

- * 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: - * - *

    - *
  1. The particular type may not be supported or of interest to the recorder.
  2. - *
  3. 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.
  4. - *
  5. The target object may no longer exist for a given trace object.
  6. - *
- * - *

- * 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 targetToTraceBreakpointKinds( - Collection 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 traceToTargetBreakpointKinds( - Collection 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 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 - * - *

- * 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 - * - *

- * 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 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 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. - * - *

- * 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 base 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 captureThreadRegisters(TracePlatform platform, - TraceThread thread, int frameLevel, Set registers); - - /** - * Write a target thread's registers. - * - *

- * 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 writeThreadRegisters(TracePlatform platform, TraceThread thread, - int frameLevel, Map 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 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 writeMemory(Address start, byte[] data); - - /** - * Read (and capture) several blocks of target memory - * - *

- * 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. - * - *

- * 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 readMemoryBlocks(AddressSetView set, TaskMonitor monitor); - - /** - * Write a variable (memory or register) of the given thread or the process - * - *

- * 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 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 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. - * - *

- * 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 captureDataTypes(TraceModule module, TaskMonitor monitor); - - /** - * Capture the data types of a target's namespace. - * - *

- * 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 captureDataTypes(TargetDataTypeNamespace namespace, - TaskMonitor monitor); - - /** - * Capture the symbols of a target's module. - * - *

- * 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 captureSymbols(TraceModule module, TaskMonitor monitor); - - /** - * Capture the symbols of a target's namespace. - * - *

- * 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 captureSymbols(TargetSymbolNamespace namespace, TaskMonitor monitor); - - /** - * Collect breakpoint containers pertinent to the target or a given thread - * - *

- * 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. - * - *

- * 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 collectBreakpointContainers(TargetThread thread); - - /** - * Collect effective breakpoint pertinent to the target or a given thread - * - *

- * 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 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 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 - * - *

- * 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 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 requestActivation(TargetObject active); - - /** - * Wait for pending transactions finish execution. - * - *

- * 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 flushTransactions(); -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorderListener.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorderListener.java deleted file mode 100644 index f45aa6819e..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorderListener.java +++ /dev/null @@ -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 - * - *

- * 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) { - } -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java deleted file mode 100644 index 33a2b03a63..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java +++ /dev/null @@ -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 - * - *

- * 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 - * - *

- * 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 - * - *

- * 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 - * - *

- * WARNING: This method will likely change or be removed in the future. - * - * @param 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 findInterface(TargetObject seed, Class iface) { - DebuggerObjectModel model = seed.getModel(); - List 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 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 findInterface(TraceThread thread, Class 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 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 findInterface(Trace trace, Class iface) { - TargetObject focus = getTargetFocus(trace); - if (focus == null) { - return null; - } - return findInterface(focus, iface); - } - - /** - * Find the interface related to the current thread or trace - * - *

- * 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 the interface type - * @param iface the interface class - * @return the related interface, or null - * @throws IllegalStateException if there is no current trace - */ - default T findInterface(Class 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 - * - *

- * 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 - * - *

- * NOTE: 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 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 - * - *

- * 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 - * - *

- * 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 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 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 - * - *

- * 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 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} - * - *

- * 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 getLaunchOffers(Program program) { - return getModelService().getProgramLaunchOffers(program).collect(Collectors.toList()); - } - - /** - * Get offers for launching the current program - * - * @return the offers - */ - default List 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 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 - * - *

- * NOTE: 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. - * - *

- * 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> configureLauncher(TargetLauncher launcher, - Map> arguments, RelPrompt relPrompt) { - Map> 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 - * - *

- * 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 - * - *

- * 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); - } -} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java index b0b2cd275b..eac9ff551a 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java @@ -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); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/AbstractTraceRmiConnection.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/AbstractTraceRmiConnection.java new file mode 100644 index 0000000000..a8da9a17c0 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/AbstractTraceRmiConnection.java @@ -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); + } + } + }); + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index fd974c2d4c..9296c0dfff 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -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(); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java index 1ee0071121..656c32cacb 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java @@ -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); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java index deac96838d..0cda9dd218 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java @@ -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 firstTrace = new CompletableFuture<>(); - protected final Map snapshots = new HashMap<>(); + protected final Map snapshots = new HashMap<>(); protected final CompletableFuture closed = new CompletableFuture<>(); protected final Map targets = new HashMap<>(); @@ -88,8 +90,9 @@ public class TestTraceRmiConnection implements TraceRmiConnection { return result; } - public Map expect() throws InterruptedException, ExecutionException { - return argQueue.take().get(); + public Map 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> expect( Function, Object> impl) { - record ArgsRet(Map args, Object ret) { - } + record ArgsRet(Map 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 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)); + } } diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/MonitorModelEventsScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/MonitorModelEventsScript.java deleted file mode 100644 index f765970707..0000000000 --- a/Ghidra/Debug/Debugger/ghidra_scripts/MonitorModelEventsScript.java +++ /dev/null @@ -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 removed, - Map added) { - System.err.println("attributesChanged(%s, removed=%s, added=%s)" - .formatted(object.getJoinedPath("."), removed, added)); - } - - @Override - public void elementsChanged(TargetObject object, Collection removed, - Map 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 parameters) { - System.err.println( - "event(%s, thread=%s, type=%s, desc=%s)".formatted(object.getJoinedPath("."), - eventThread == 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); - } -} diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java index 20ce21ba43..c8fdd38755 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java @@ -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 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 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 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 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 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); } } } diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png index 66932c2199..1f87cffedd 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerFunctionGraphBreakpointMargin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerFunctionGraphBreakpointMargin.png index ec934cdd0a..3a95f0545d 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerFunctionGraphBreakpointMargin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerFunctionGraphBreakpointMargin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 8a426a0e4e..2e1090db49 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -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 MultiStateActionBuilder buttonBuilder(Plugin owner, Plugin helpOwner) { - return new MultiStateActionBuilder(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 { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java index b7a683ca33..91e60752ee 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java @@ -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 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(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 211b88326f..c39b650e75 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -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); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index 1dddf5485c..d8eae4b92b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -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(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index 2e358af4d7..fd9e1143ae 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DefaultWatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DefaultWatchRow.java index 7f857534fc..be32095d51 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DefaultWatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DefaultWatchRow.java @@ -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 executor = provider.asyncWatchExecutor; - PcodeExecutor prevExec = provider.prevValueExecutor; - if (executor == null) { - provider.contextChanged(); - return; - } - CompletableFuture.runAsync(() -> { - recompile(); - if (compiled == null) { + PcodeExecutor executor; + PcodeExecutor 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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOpinion.java index d9fcb111f9..8bf311b196 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOpinion.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java deleted file mode 100644 index 8083a2bd80..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java +++ /dev/null @@ -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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerRegisterMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerRegisterMapper.java deleted file mode 100644 index ed52cf7f26..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerRegisterMapper.java +++ /dev/null @@ -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 languageRegs = new LinkedHashMap<>(); - protected final Map filtLanguageRegs = new LinkedHashMap<>(); - protected final Map 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 map, Register lReg) { - map.put(normalizeName(lReg.getName()), lReg); - for (String alias : lReg.getAliases()) { - map.put(normalizeName(alias), lReg); - } - } - - protected synchronized void removeLanguageRegister(Map 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 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerTargetTraceMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerTargetTraceMapper.java deleted file mode 100644 index a97433a585..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerTargetTraceMapper.java +++ /dev/null @@ -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 extraRegNames; - - public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID, - CompilerSpecID csID, Collection 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 - * - *

- * 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 offerMemory(TargetMemory memory) { - DebuggerMemoryMapper mm = createMemoryMapper(memory); - return CompletableFuture.completedFuture(mm); - } - - // TODO: Make this synchronous, or remove it - public CompletableFuture offerRegisters( - TargetRegisterContainer registers) { - DebuggerRegisterMapper rm = createRegisterMapper(registers); - return CompletableFuture.completedFuture(rm); - } - - public Set 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerMemoryMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerMemoryMapper.java deleted file mode 100644 index 709d387547..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerMemoryMapper.java +++ /dev/null @@ -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 targetToTraceSpaces = new HashMap<>(); - protected final Map 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); - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerTargetTraceMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerTargetTraceMapper.java deleted file mode 100644 index d1894b07d2..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerTargetTraceMapper.java +++ /dev/null @@ -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 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java index 95ea326f73..7f96d17944 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java @@ -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 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(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java deleted file mode 100644 index 7fa51b2f49..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java +++ /dev/null @@ -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 = "Description: "; - - protected class FactoriesChangedListener - implements CollectionChangeListener { - @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 { - 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 factories = new HashMap<>(); - private FactoriesChangedListener listener = new FactoriesChangedListener(); - - private JComboBox dropdown; - protected final DefaultComboBoxModel dropdownModel = new DefaultComboBoxModel<>(); - - private final BidiMap, PropertyEditor> propertyEditors = - new DualLinkedHashBidiMap<>(); - private final Map, Component> components = new LinkedHashMap<>(); - - protected JLabel description; - protected JPanel gridPanel; - - protected JButton connectButton; - protected CompletableFuture futureConnect; - protected CompletableFuture 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 toAdd = new ArrayList<>(); - Set 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 + ""); - 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, PropertyEditor> ent : propertyEditors.entrySet()) { - Property prop = ent.getKey(); - @SuppressWarnings("unchecked") - Property objProp = (Property) 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 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, 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> optsMap = currentFactory.getOptions(); - gridPanel.removeAll(); - GridBagConstraints constraints; - - if (optsMap.isEmpty()) { - JLabel label = - new JLabel("There are no configuration options for this connector."); - constraints = new GridBagConstraints(); - gridPanel.add(label, constraints); - } - - int i = 0; - for (Map.Entry> opt : optsMap.entrySet()) { - Property property = opt.getValue(); - JLabel label = new JLabel("" + 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 objProp = (Property) 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 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; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java deleted file mode 100644 index f5e3b4f098..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java +++ /dev/null @@ -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 - * - *

- * 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 factories); - - /** - * Set the model factories back to those found on the classpath - * - *

- * 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 not required to restore - * the classpath-discovered factories. - * - * @see #setModelFactories(Collection) - */ - void refreshFactoryInstances(); - - /** - * Start and open a new trace on the given target - * - *

- * 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); - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java deleted file mode 100644 index 77b58808dd..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ /dev/null @@ -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 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 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 proxies = new HashSet<>(); - - protected final Set factories = new HashSet<>(); - // Keep strong references to my listeners, or they'll get torched - //protected final Map listenersByModel = - //new LinkedHashMap<>(); - protected final Set models = new LinkedHashSet<>(); - protected final ForRemovalAndFocusListener forRemovalAndFocusListener = - new ForRemovalAndFocusListener(); - protected final Map recordersByTarget = new WeakHashMap<>(); - - protected final ListenerSet> factoryListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true); - protected final ListenerSet> modelListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true); - protected final ListenerSet> 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 getModelFactories() { - synchronized (factories) { - return Set.copyOf(factories); - } - } - - @Override - public Set getModels() { - synchronized (models) { - return Set.copyOf(models); - } - } - - @Override - public CompletableFuture closeAllModels() { - AsyncFence fence = new AsyncFence(); - for (DebuggerObjectModel model : getModels()) { - fence.include(model.close().exceptionally(showError(null, "Problem disconnecting"))); - } - return fence.ready(); - } - - @Override - public Collection 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 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 newFactories = - ClassSearcher.getInstances(DebuggerModelFactory.class); - setModelFactories(newFactories); - } - - @Internal - @Override - public synchronized void setModelFactories(Collection newFactories) { - Set 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 listener) { - factoryListeners.add(listener); - } - - @Override - public void removeFactoriesChangedListener( - CollectionChangeListener listener) { - factoryListeners.remove(listener); - } - - @Override - public synchronized void addModelsChangedListener( - CollectionChangeListener listener) { - modelListeners.add(listener); - } - - @Override - public synchronized void removeModelsChangedListener( - CollectionChangeListener listener) { - modelListeners.remove(listener); - } - - @Override - public synchronized void addTraceRecordersChangedListener( - CollectionChangeListener listener) { - recorderListeners.add(listener); - } - - @Override - public synchronized void removeTraceRecordersChangedListener( - CollectionChangeListener 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 doGetProgramLaunchOffers(PluginTool tool, - Program program) { - return ClassSearcher.getInstances(DebuggerProgramLaunchOpinion.class) - .stream() - .flatMap(opinion -> opinion.getOffers(program, tool, this).stream()); - } - - @Override - public Stream getProgramLaunchOffers(Program program) { - return doGetProgramLaunchOffers(tool, program); - } - - protected CompletableFuture doShowConnectDialog(PluginTool tool, - DebuggerModelFactory factory, Program program) { - CompletableFuture future = connectDialog.reset(factory, program); - tool.showDialog(connectDialog); - return future; - } - - @Override - public CompletableFuture showConnectDialog() { - return doShowConnectDialog(tool, null, null); - } - - @Override - public CompletableFuture showConnectDialog(Program program) { - return doShowConnectDialog(tool, null, program); - } - - @Override - public CompletableFuture showConnectDialog(DebuggerModelFactory factory) { - return doShowConnectDialog(tool, factory, null); - } - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java deleted file mode 100644 index c218c20f0d..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ /dev/null @@ -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 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 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 { - @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 { - @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 { - @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 actionDebugProgram; - Set actionDebugProgramMenus = new HashSet<>(); - DockingAction actionDisconnectAll; - - protected final ListenerSet> factoryListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true); - protected final ListenerSet> modelListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true); - protected final ListenerSet> recorderListeners = - new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true); - - protected final Map 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 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 showConnectDialog() { - return delegate.doShowConnectDialog(tool, null, null); - } - - @Override - public CompletableFuture showConnectDialog(Program program) { - return delegate.doShowConnectDialog(tool, null, program); - } - - @Override - public CompletableFuture showConnectDialog(DebuggerModelFactory factory) { - return delegate.doShowConnectDialog(tool, factory, null); - } - - @Override - public Stream getProgramLaunchOffers(Program program) { - return orderOffers(delegate.doGetProgramLaunchOffers(tool, program), program); - } - - protected List 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 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 { - Map fastIndex = new HashMap<>(); - - public OfferComparator(List 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 orderOffers( - Stream offers, Program program) { - List mrl = readMostRecentLaunches(program); - return offers.sorted(Comparator.comparingInt(o -> -mrl.indexOf(o.getConfigName()))); - } - - private void debugProgram(DebuggerProgramLaunchOffer offer, Program program, - PromptMode prompt) { - - List 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 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 offers = program == null ? List.of() - : getProgramLaunchOffers(program).collect(Collectors.toList()); - List> 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 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 factories) { - delegate.setModelFactories(factories); - } - - @Override - public Set getModelFactories() { - return delegate.getModelFactories(); - } - - @Override - public Set getModels() { - return delegate.getModels(); - } - - @Override - public CompletableFuture closeAllModels() { - return delegate.closeAllModels(); - } - - @Override - public Collection 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 listener) { - factoryListeners.add(listener); - } - - @Override - public void removeFactoriesChangedListener( - CollectionChangeListener listener) { - factoryListeners.remove(listener); - } - - @Override - public void addModelsChangedListener(CollectionChangeListener listener) { - modelListeners.add(listener); - } - - @Override - public void removeModelsChangedListener( - CollectionChangeListener listener) { - modelListeners.remove(listener); - } - - @Override - public void addTraceRecordersChangedListener(CollectionChangeListener listener) { - recorderListeners.add(listener); - } - - @Override - public void removeTraceRecordersChangedListener( - CollectionChangeListener 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java deleted file mode 100644 index ac8ca94d2d..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java +++ /dev/null @@ -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 { - 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 getter; - private final SortDirection sortDir; - - OfferTableColumns(String header, Class cls, - Function getter, SortDirection sortDir) { - this.header = header; - this.cls = cls; - this.getter = getter; - this.sortDir = sortDir; - } - - OfferTableColumns(String header, Class cls, - Function 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 { - - public OfferTableModel(PluginTool tool) { - super(tool, "Offers", OfferTableColumns.class); - } - - @Override - public List 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 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 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 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 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 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. - * - *

- * 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 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 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 - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java deleted file mode 100644 index 3948fcda90..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java +++ /dev/null @@ -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 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 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 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 bpts, boolean enabled, - Collection 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 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; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java deleted file mode 100644 index c011059738..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java +++ /dev/null @@ -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 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 getTraceFlags(TargetMemoryRegion region) { - Collection 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultModuleRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultModuleRecorder.java deleted file mode 100644 index 745e8aeceb..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultModuleRecorder.java +++ /dev/null @@ -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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java deleted file mode 100644 index 0e2835fcfd..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java +++ /dev/null @@ -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 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 readProcessMemory(Address start, int length) { - Address tStart = recorder.getMemoryMapper().traceToTarget(start); - return getProcessMemory().readMemory(tStart, length); - } - - public CompletableFuture 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(); - } - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java deleted file mode 100644 index 7bd4f8fdb9..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java +++ /dev/null @@ -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 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 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 pcsByLevel) { - recorder.parTx.execute("Stack changed", () -> { - TraceStack traceStack = stackManager.getStack(thread, snap, true); - traceStack.setDepth(stackDepth(), false); - for (Map.Entry ent : pcsByLevel.entrySet()) { - doRecordFrame(traceStack, ent.getKey(), ent.getValue()); - } - }, thread.getPath()); - } - - public void recordFrame(TargetStackFrame frame) { - long snap = recorder.getSnap(); - Map 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); - } - } - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java deleted file mode 100644 index 88b3ef9cd0..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java +++ /dev/null @@ -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> regs = new HashMap<>(); - protected Collection 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 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. - * - *

- * 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 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> captureThreadRegisters( - TraceThread thread, int frameLevel, Set registers) { - if (regMapper == null) { - return CompletableFuture.failedFuture( - new IllegalStateException("Have not found register descriptions for " + thread)); - } - List 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 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 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 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 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 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 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 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 writeThreadRegisters(int frameLevel, - Map 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 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 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 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 updateRegsMem(TargetMemoryRegion limit) { - TargetRegisterBank bank; - TargetRegister pc; - TargetRegister sp; - Set 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; - }); - } - */ - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java deleted file mode 100644 index 2507c305ea..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java +++ /dev/null @@ -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()); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java deleted file mode 100644 index d69607b59f..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ /dev/null @@ -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 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 collectBreakpointContainers(TargetThread thread) { - List result = new ArrayList<>(); - objectManager.onBreakpointContainers(thread, result::add); - return result; - } - - @Override - public List collectBreakpoints(TargetThread thread) { - return objectManager.collectBreakpoints(thread); - } - - @Override - public Set getSupportedBreakpointKinds() { - Set 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 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 readMemoryBlocks(AddressSetView set, TaskMonitor monitor) { - if (set.isEmpty()) { - return AsyncUtils.nil(); - } - return memoryRecorder.captureProcessMemory(set, monitor); - } - - @Override - public CompletableFuture captureDataTypes(TargetDataTypeNamespace namespace, - TaskMonitor monitor) { - if (!valid) { - return AsyncUtils.nil(); - } - return datatypeRecorder.captureDataTypes(namespace, monitor); - } - - @Override - public CompletableFuture 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 captureSymbols(TargetSymbolNamespace namespace, - TaskMonitor monitor) { - if (!valid) { - return AsyncUtils.nil(); - } - return symbolRecorder.captureSymbols(namespace, monitor); - } - - @Override - public CompletableFuture 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 captureThreadRegisters( - TracePlatform platform, TraceThread thread, int frameLevel, Set registers) { - DefaultThreadRecorder rec = getThreadRecorder(thread); - return rec.captureThreadRegisters(thread, frameLevel, registers).thenApply(__ -> null); - } - - /*---------------- SNAPSHOT METHODS -------------------*/ - - @Override - public CompletableFuture init() { - return lazyInit.request(); - } - - protected CompletableFuture 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 path = target.getModel() - .getRootSchema() - .searchForSuitable(TargetFocusScope.class, target.getPath()); - if (path == null) { - return null; - } - return (TargetFocusScope) target.getModel().getModelObject(path); - } - - protected TargetActiveScope findActiveScope() { - List 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 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 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 getThreadsView() { - return getThreadMap().byTargetThread.keySet(); - } - - // UNUSED? - @Override - public Set 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 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 readMemory(Address start, int length) { - return processRecorder.readProcessMemory(start, length); - } - - @Override - public CompletableFuture 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 onTarget = getRegisterMapper(thread).getRegistersOnTarget(); - for (; register != null; register = register.getParentRegister()) { - if (onTarget.contains(register)) { - return register; - } - } - return null; - } - - @Override - public CompletableFuture writeThreadRegisters(TracePlatform platform, TraceThread thread, - int frameLevel, Map 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 flushTransactions() { - return CompletableFuture.runAsync(() -> { - }, privateQueue).thenCompose(__ -> { - return objectManager.flushEvents(); - }).thenCompose(__ -> { - return parTx.flush(); - }); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java deleted file mode 100644 index e40c9c3ac8..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java +++ /dev/null @@ -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 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 flush() { - Runnable nop = () -> { - }; - return CompletableFuture.allOf(Stream.of(threads) - .map(t -> CompletableFuture.runAsync(nop, t)) - .toArray(CompletableFuture[]::new)); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderBreakpointLocationResolver.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderBreakpointLocationResolver.java deleted file mode 100644 index 8d6be1fe46..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderBreakpointLocationResolver.java +++ /dev/null @@ -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 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 getAffects(TargetBreakpointLocation bpt) { - TargetObject findProc = bpt; - while (!(findProc instanceof TargetProcess)) { - findProc = findProc.getParent(); - } - return List.of(findProc); - } - - private CompletableFuture 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 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; - }); - } - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderPermanentTransaction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderPermanentTransaction.java deleted file mode 100644 index 93a7c77a3f..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderPermanentTransaction.java +++ /dev/null @@ -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(); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderSimpleMemory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderSimpleMemory.java deleted file mode 100644 index b532603b86..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderSimpleMemory.java +++ /dev/null @@ -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 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 readMemory(Address address, int length) { - synchronized (this) { - if (memory != null) { - return memory.readMemory(address, length); - } - return CompletableFuture.completedFuture(new byte[0]); - } - } - - @Override - public CompletableFuture 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 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 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 floor = findChainedFloor(address); - if (floor == null) { - return null; - } - return align(address, length).intersect(floor.getValue().getRange()); - } - - protected Entry 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderThreadMap.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderThreadMap.java deleted file mode 100644 index dcf4b99af8..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderThreadMap.java +++ /dev/null @@ -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 observedThreadPathLengths = new TreeSet<>(); - protected final Map byTargetThread = new HashMap<>(); - protected final Map 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 recorders() { - return byTargetThread.values(); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java deleted file mode 100644 index 9f29d50714..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java +++ /dev/null @@ -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 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 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 parameters) { - for (Object p : parameters) { - if (!(p instanceof TargetObject)) { - continue; - } - return true; - } - return false; - } - - private boolean anySuccessor(Collection 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 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 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 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 flushEvents() { - return reorderer.flushEvents(); - } - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java deleted file mode 100644 index 89b267a040..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java +++ /dev/null @@ -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, TargetObject> initialized = - new TreeMap<>(PathComparator.KEYED); - protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this); - protected final PrivatelyQueuedListener 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 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 added) { - if (objectManager.hasObject(changed)) { - objectManager.attributesChanged(changed, added); - } - } - - protected void processElementsChanged(TargetObject changed, Map 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 removed, - Map added) { - //System.err.println("AC:" + added + ":" + parent); - if (parent.isValid()) { - processInit(parent); - processAttributesChanged(parent, added); - } - } - - @Override - public void elementsChanged(TargetObject parent, Collection removed, - Map added) { - //System.err.println("EC:" + added + ":" + parent); - if (parent.isValid()) { - processElementsChanged(parent, added); - } - } - - public List collectBreakpoints(TargetThread thread) { - synchronized (objectManager.objects) { - return objectManager.collectBreakpoints(thread); - } - } - - protected void onProcessBreakpointContainers( - Consumer action) { - synchronized (objectManager.objects) { - objectManager.onProcessBreakpointContainers(action); - } - } - - protected void onThreadBreakpointContainers(TargetThread thread, - Consumer 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 it = objects.values().iterator(); it.hasNext();) { - TargetObject obj = it.next(); - obj.removeListener(this); - it.remove(); - } - } - } - */ - - private CompletableFuture> findInitialObjects(TargetObject target) { - List result = new ArrayList<>(); - result.add(target); - AsyncFence fence = new AsyncFence(); - CompletableFuture 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 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 flushEvents() { - return reorderer.flushEvents(); - } - - /* - private CompletableFuture> findDependenciesTop(TargetObject added) { - List result = new ArrayList<>(); - result.add(added); - return findDependencies(added, result); - } - - private CompletableFuture> findDependencies(TargetObject added, - List 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; - }); - } - */ -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java deleted file mode 100644 index 0585a0c857..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java +++ /dev/null @@ -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, TargetObject> objects = - new TreeMap<>(PathComparator.KEYED); - - private DefaultTraceRecorder recorder; - - private DefaultDebuggerTargetTraceMapper mapper; - protected DebuggerMemoryMapper memMapper; - protected AsyncLazyMap regMappers; - //private AbstractRecorderRegisterSet threadRegisters; - - private final ListenerSet listeners = - new ListenerSet<>(TraceRecorderListener.class, true); - - protected final Set breakpoints = new HashSet<>(); - - // NB: We add the objects in top-down order and initialize them bottom-up - private LinkedHashMap, Function> handlerMapCreate = - new LinkedHashMap<>(); - private LinkedHashMap, Function> handlerMapInit = - new LinkedHashMap<>(); - private LinkedHashMap, Function> handlerMapRemove = - new LinkedHashMap<>(); - private LinkedHashMap, BiFunction, Void>> handlerMapElements = - new LinkedHashMap<>(); - private LinkedHashMap, BiFunction, 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 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 Function putHandler(Class key, - Consumer handler, - LinkedHashMap, Function> handlerMap) { - return handlerMap.put(key, (u) -> { - handler.accept(u); - return null; - }); - } - - @SuppressWarnings("unchecked") - private BiFunction, Void> putHandler( - Class key, BiConsumer> handler, - LinkedHashMap, BiFunction, Void>> handlerMap) { - return handlerMap.put(key, (u, v) -> { - handler.accept((U) u, v); - return null; - }); - } - - public Function putCreateHandler(Class key, - Consumer handler) { - return putHandler(key, handler, handlerMapCreate); - } - - public Function putInitHandler(Class key, - Consumer handler) { - return putHandler(key, handler, handlerMapInit); - } - - public Function putRemHandler(Class key, - Consumer handler) { - return putHandler(key, handler, handlerMapRemove); - } - - public BiFunction, Void> putAttributesHandler( - Class key, BiConsumer> handler) { - return putHandler(key, handler, handlerMapAttributes); - } - - public BiFunction, Void> putElementsHandler( - Class key, BiConsumer> handler) { - return putHandler(key, handler, handlerMapElements); - } - - private void processObject(TargetObject targetObject, - LinkedHashMap, Function> handlerMap) { - Set> interfaces = targetObject.getSchema().getInterfaces(); - for (Class ifc : interfaces) { - Function function = handlerMap.get(ifc); - if (function != null) { - function.apply(targetObject); - } - } - } - - private void processObject(TargetObject targetObject, Map map, - LinkedHashMap, BiFunction, Void>> handlerMap) { - Set> interfaces = targetObject.getSchema().getInterfaces(); - for (Class ifc : interfaces) { - BiFunction, ? 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 added) { - processObject(changed, added, handlerMapAttributes); - } - - public void elementsChanged(TargetObject changed, Map 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> interfaces = obj.getSchema().getInterfaces(); - for (Class 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 getRegMappers() { - return regMappers; - } - - public Set getBreakpoints() { - return breakpoints; - } - - public void attributesChangedBreakpointSpec(TargetObject bpt, Map added) { - if (added.containsKey(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME) || - added.containsKey(TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME)) { - TargetBreakpointSpec spec = (TargetBreakpointSpec) bpt; - boolean enabled = spec.isEnabled(); - Set traceKinds = - TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds()); - recorder.breakpointRecorder.breakpointSpecChanged(spec, enabled, traceKinds); - } - } - - public void attributesChangedBreakpointLocation(TargetObject obj, Map 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 added) { - if (added.containsKey(TargetObject.DISPLAY_ATTRIBUTE_NAME)) { - recorder.memoryRecorder.regionChanged((TargetMemoryRegion) region, region.getDisplay()); - } - } - - public void attributesChangedModule(TargetModule module, Map 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 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 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 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 added) { - TargetObject x = findThreadOrProcess(locationContainer); - if (x != null) { - for (Entry 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 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 added) { - for (Object object : added.values()) { - TargetSection section = (TargetSection) object; - if (!section.isValid()) { - continue; - } - recorder.moduleRecorder.offerProcessModuleSection(section); - } - } - - public void elementsChangedStack(TargetObject stack, Map 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 collectBreakpoints(TargetThread thread) { - return getBreakpoints().stream().collect(Collectors.toList()); - } - - public void onBreakpointContainers(TargetThread thread, - Consumer action) { - if (thread == null) { - objectListener.onProcessBreakpointContainers(action); - } - else { - objectListener.onThreadBreakpointContainers(thread, action); - } - } - - public void onProcessBreakpointContainers( - Consumer 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 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 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 path) { - objects.remove(path); - } - - public void disposeModelListeners() { - eventListener.dispose(); - objectListener.dispose(); - } - - public CompletableFuture flushEvents() { - return eventListener.flushEvents().thenCompose(__ -> { - return objectListener.flushEvents(); - }); - } - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java deleted file mode 100644 index 7781cc8bef..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java +++ /dev/null @@ -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 findObjectInContext(ActionContext context, - Class iface) { - if (context instanceof DebuggerObjectActionContext ctx) { - List 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 findObjectInTrace(ActionContext context, Class 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 findObjectInRecorder(ActionContext context, - Class iface) { - if (!isValid()) { - return null; - } - TargetObject focus = recorder.getFocus(); - if (focus == null) { - return null; - } - return focus.getCachedSuitable(iface); - } - - protected T findObject(ActionContext context, Class 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 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 arguments) { - public boolean requiresPrompt() { - for (ParameterDescription param : method.getParameters().values()) { - if (param.required && !arguments.containsKey(param.name)) { - return true; - } - } - return false; - } - } - - private List 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 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 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 promptArgs(TargetMethod method, Map defaults) { - Map> defs = ValStr.fromPlainMap(defaults); - DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool, - method.getDisplay(), method.getDisplay(), null); - - Map> args = dialog.promptArguments(method.getParameters(), defs, defs); - return args == null ? null : ValStr.toPlainMap(args); - } - - private CompletableFuture invokeMethod(boolean prompt, TargetMethod method, - Map arguments) { - Map 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 arguments) { - return new ActionEntry(method.getDisplay(), null, null, requiresPrompt, - method.getPath().size(), () -> true, prompt -> invokeMethod(prompt, method, arguments)); - } - - @Override - public Map collectAddressActions(ProgramLocationActionContext context) { - Map result = new HashMap<>(); - for (MethodWithArgs mwa : findAddressMethods(context)) { - result.put(mwa.method.getJoinedPath("."), - makeEntry(mwa.requiresPrompt(), mwa.method, mwa.arguments)); - } - return result; - } - - protected Map collectIfaceActions( - ActionContext context, Class iface, String display, ActionName name, - String description, Predicate enabled, Function> 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 Predicate stateNullOr( - Predicate predicate) { - return object -> { - TargetExecutionState state = getStateOf(object); - return state == null || predicate.test(state); - }; - } - - @Override - protected Map 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 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 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 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 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 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 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 collectRefreshActions(ActionContext context) { - // Not necessary to support this here - return Map.of(); - } - - @Override - protected Map 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 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 invalidateMemoryCachesAsync() { - TargetObject target = recorder.getTarget(); - DebuggerObjectModel model = target.getModel(); - model.invalidateAllLocalCaches(); - PathMatcher memMatcher = target.getSchema().searchFor(TargetMemory.class, true); - Collection memories = memMatcher.getCachedSuccessors(target).values(); - CompletableFuture[] requests = memories.stream() - .map(TargetObject::invalidateCaches) - .toArray(CompletableFuture[]::new); - return CompletableFuture.allOf(requests); - } - - @Override - public CompletableFuture 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 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 readRegistersAsync(TracePlatform platform, TraceThread thread, - int frame, Set 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 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 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 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 writeVariableAsync(TracePlatform platform, TraceThread thread, - int frame, Address address, byte[] data) { - return recorder.writeVariable(platform, thread, frame, address, data); - } - - @Override - public CompletableFuture placeBreakpointAsync(AddressRange range, - Set 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 tKinds = - TraceRecorder.traceToTargetBreakpointKinds(kinds); - AddressRange targetRange = recorder.getMemoryMapper().traceToTarget(range); - AsyncFence fence = new AsyncFence(); - for (TargetBreakpointSpecContainer cont : recorder.collectBreakpointContainers(null)) { - Set stKinds = new LinkedHashSet<>(tKinds); - stKinds.retainAll(cont.getSupportedBreakpointKinds()); - fence.include(cont.placeBreakpoint(targetRange, stKinds)); - } - return fence.ready(); - } - - @Override - public Set getSupportedBreakpointKinds() { - return recorder.getSupportedBreakpointKinds(); - } - - @Override - public boolean isBreakpointValid(TraceBreakpoint breakpoint) { - return recorder.getTargetBreakpoint(breakpoint) != null; - } - - @Override - public CompletableFuture 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 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 forceTerminateAsync() { - recorder.stopRecording(); - return AsyncUtils.nil(); - } - - @Override - public CompletableFuture disconnectAsync() { - return recorder.getTarget() - .getModel() - .close() - .orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractRecorderMemory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractRecorderMemory.java deleted file mode 100644 index 6be65ca5d7..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractRecorderMemory.java +++ /dev/null @@ -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 readMemory(Address address, int length); - - public CompletableFuture 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 pred, - DebuggerMemoryMapper memMapper); - - public AddressRange alignAndLimitToFloor(Address targetAddress, int length); -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractTraceRecorder.java deleted file mode 100644 index 70824b6780..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractTraceRecorder.java +++ /dev/null @@ -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(); - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedBreakpointRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedBreakpointRecorder.java deleted file mode 100644 index 8a23fe0ad5..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedBreakpointRecorder.java +++ /dev/null @@ -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 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 kinds); -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedMemoryRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedMemoryRecorder.java deleted file mode 100644 index 31aebbdf8c..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedMemoryRecorder.java +++ /dev/null @@ -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); -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedModuleRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedModuleRecorder.java deleted file mode 100644 index ea6e4df637..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedModuleRecorder.java +++ /dev/null @@ -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); - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedProcessRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedProcessRecorder.java deleted file mode 100644 index adba276f46..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedProcessRecorder.java +++ /dev/null @@ -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(); - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedStackRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedStackRecorder.java deleted file mode 100644 index 37f56a8757..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedStackRecorder.java +++ /dev/null @@ -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); - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedThreadRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedThreadRecorder.java deleted file mode 100644 index 5abae978ba..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedThreadRecorder.java +++ /dev/null @@ -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 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 doFetchAndInitRegMapper(TargetRegisterBank parent); - - public ManagedStackRecorder getStackRecorder(); - -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/DebuggerProgramLaunchOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/DebuggerProgramLaunchOpinion.java deleted file mode 100644 index 2602a5d3f1..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/DebuggerProgramLaunchOpinion.java +++ /dev/null @@ -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 getOffers(Program program, PluginTool tool, - DebuggerModelService service); -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/DataTypeRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/DataTypeRecorder.java deleted file mode 100644 index 3394ffaeac..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/DataTypeRecorder.java +++ /dev/null @@ -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 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 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 captureDataTypes(TargetModule targetModule, - TaskMonitor monitor) { - CompletableFuture> 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(); - }); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/EmptyDebuggerRegisterMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/EmptyDebuggerRegisterMapper.java deleted file mode 100644 index 09160755a3..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/EmptyDebuggerRegisterMapper.java +++ /dev/null @@ -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 getRegistersOnTarget() { - return Set.of(); - } - - @Override - public void targetRegisterAdded(TargetRegister register) { - throw new UnsupportedOperationException(); - } - - @Override - public void targetRegisterRemoved(TargetRegister register) { - throw new UnsupportedOperationException(); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java deleted file mode 100644 index 2d9c30c50d..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java +++ /dev/null @@ -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, TargetMemory> memoriesByTargetSpace = - new HashMap<>(); - protected final Map, 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 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 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 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()); - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java deleted file mode 100644 index 4171a6f4ac..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java +++ /dev/null @@ -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 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 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 removed, - Map 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 removed, - Map 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 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 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 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 captureThreadRegisters( - TracePlatform platform, TraceThread thread, int frameLevel, Set 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 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 writeThreadRegisters(TracePlatform platform, TraceThread thread, - int frameLevel, Map values) { - TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel); - if (regContainer == null) { - return AsyncUtils.nil(); - } - Map> writesByBank = new HashMap<>(); - for (RegisterValue rv : values.values()) { - Register register = rv.getRegister(); - PathMatcher matcher = - platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), register); - Collection 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 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> ent : writesByBank - .entrySet()) { - fence.include(ent.getKey().writeRegisters(ent.getValue())); - } - return fence.ready(); - } - - @Override - public CompletableFuture readMemory(Address start, int length) { - return memoryRecorder.read(start, length); - } - - @Override - public CompletableFuture writeMemory(Address start, byte[] data) { - return memoryRecorder.write(start, data); - } - - @Override - public CompletableFuture readMemoryBlocks(AddressSetView set, TaskMonitor monitor) { - return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor); - } - - @Override - public CompletableFuture captureDataTypes(TraceModule module, TaskMonitor monitor) { - return dataTypeRecorder.captureDataTypes(getTargetModule(module), monitor); - } - - @Override - public CompletableFuture captureDataTypes(TargetDataTypeNamespace namespace, - TaskMonitor monitor) { - return dataTypeRecorder.captureDataTypes(namespace, monitor); - } - - @Override - public CompletableFuture captureSymbols(TraceModule module, TaskMonitor monitor) { - return symbolRecorder.captureSymbols(getTargetModule(module), monitor); - } - - @Override - public CompletableFuture captureSymbols(TargetSymbolNamespace namespace, - TaskMonitor monitor) { - return symbolRecorder.captureSymbols(namespace, monitor); - } - - @Override - public List 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 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 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 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 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 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 -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java deleted file mode 100644 index 00557fdcff..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java +++ /dev/null @@ -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> 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 = objectMap.get(new IDKeyed<>(targetObject)); - return traceObject == null ? null : traceObject.obj; - } - - protected TargetObject toTarget(TraceObject traceObject) { - IDKeyed 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 result = new LinkedHashSet<>(object.getInterfaceNames()); - for (Class 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 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; - 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> 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 removed, - Map added) { - TraceObject traceObject; - Map traceAdded = new HashMap<>(); - synchronized (objectMap) { - traceObject = toTrace(object); - if (traceObject == null) { - Msg.error(this, "Unknown object had attributes changed: " + object); - return; - } - for (Map.Entry entry : added.entrySet()) { - Object value = mapAttribute(entry.getValue()); - if (value == null) { - continue; - } - traceAdded.put(entry.getKey(), value); - } - } - for (Map.Entry 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 removed, - Map added) { - TraceObject traceObject; - Map traceAdded = new HashMap<>(); - synchronized (objectMap) { - traceObject = toTrace(object); - if (traceObject == null) { - Msg.error(this, "Unknown object had attributes changed: " + object); - return; - } - for (Map.Entry entry : added.entrySet()) { - Object value = mapElement(entry.getValue()); - if (value == null) { - continue; - } - traceAdded.put(entry.getKey(), value); - } - } - for (Map.Entry entry : traceAdded.entrySet()) { - traceObject.setElement(Lifespan.nowOn(snap), entry.getKey(), entry.getValue()); - } - } - - protected T getTargetInterface( - TraceUniqueObject traceUnique, Class traceObjectIf, Class targetObjectIf) { - if (!traceObjectIf.isAssignableFrom(traceUnique.getClass())) { - return null; - } - TraceObject traceObject = traceObjectIf.cast(traceUnique).getObject(); - return getTargetInterface(traceObject, targetObjectIf); - } - - protected T getTargetInterface(TraceObject traceObject, - Class targetObjectIf) { - TargetObject targetObject = toTarget(traceObject); - return targetObject == null ? null : targetObject.as(targetObjectIf); - } - - protected I getTraceInterface(TargetObject targetObject, - Class traceObjectIf) { - TraceObject traceObject = toTrace(targetObject); - return traceObject == null ? null : traceObject.queryInterface(traceObjectIf); - } - - protected T getTargetFrameInterface(TraceThread thread, int frameLevel, - Class 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 List collectTargetSuccessors(TargetObject targetSeed, - Class 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()); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java deleted file mode 100644 index e9052ffcd6..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java +++ /dev/null @@ -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 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 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); - }); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/SymbolRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/SymbolRecorder.java deleted file mode 100644 index aecd4abdc3..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/SymbolRecorder.java +++ /dev/null @@ -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 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 captureSymbols(TargetModule targetModule, - TaskMonitor monitor) { - CompletableFuture> 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(); - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/TimeRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/TimeRecorder.java deleted file mode 100644 index 5f276e0efc..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/TimeRecorder.java +++ /dev/null @@ -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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 2319d77722..b6b98a037d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -222,12 +222,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @Override public void targetWithdrawn(Target target) { + Swing.runLater(() -> updateCurrentTarget()); boolean save = isSaveTracesByDefault(); CompletableFuture 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) { diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java index 57e2ffdbe2..aaf0be6733 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginScreenShots.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java index dc22e81743..acbbc298d3 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java @@ -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 values, long timeoutMillis) throws Exception { - TraceThread traceThread = modelService.getTraceThread(bank.getThread()); - assertNotNull(traceThread); - Trace trace = traceThread.getTrace(); - TraceRecorder recorder = modelService.getRecorder(trace); - CompletableFuture 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 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"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java deleted file mode 100644 index 30f10c03cf..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java +++ /dev/null @@ -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 List copyModelData( - RowWrappedEnumeratedColumnTableModel 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 mappingsSettled = mappingService.changesSettled(); - CompletableFuture breakpointsSettled = breakpointService.changesSettled(); - traceManager.openTrace(trace3); - waitForSwing(); - waitOn(mappingsSettled); - waitOn(breakpointsSettled); - waitForSwing(); - - LogicalBreakpointTableModel bptModel = breakpointsProvider.breakpointTableModel; - - List 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 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 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()); - } - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java deleted file mode 100644 index 2885bd4c8a..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java +++ /dev/null @@ -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 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 mappings = - tb.trace.getStaticMappingManager().getAllEntries(); - assertEquals(0, mappings.size()); - - pressButtonByText(propDialog, "OK", true); - waitForDomainObject(tb.trace); - assertEquals(2, mappings.size()); - Iterator 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()))); - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java deleted file mode 100644 index a5b610adaf..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java +++ /dev/null @@ -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 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 modulesDisplayed = - modulesProvider.legacyModulesPanel.moduleTableModel.getModelData(); - assertTrue(modulesDisplayed.isEmpty()); - - List sectionsDisplayed = - modulesProvider.legacySectionsPanel.sectionTableModel.getModelData(); - assertTrue(sectionsDisplayed.isEmpty()); - } - - protected void assertProviderPopulated() { - List 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 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 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 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 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 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 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 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 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 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 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)); - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderLegacyTest.java deleted file mode 100644 index 1f02d1cda5..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderLegacyTest.java +++ /dev/null @@ -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 framesDisplayed = - stackProvider.legacyPanel.stackTableModel.getModelData(); - assertTrue(framesDisplayed.isEmpty()); - } - - protected void assertProviderPopulatedSynthetic() { - List 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java deleted file mode 100644 index d8e77594fa..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java +++ /dev/null @@ -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 threadsDisplayed = - threadsProvider.legacyPanel.threadTableModel.getModelData(); - assertTrue(threadsDisplayed.isEmpty()); - } - - protected void assertThreadsPopulated() { - List 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()); - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java similarity index 99% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java rename to Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java index b86ea8390c..489f43a716 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java index 1951b23f92..3c1fd9873e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java @@ -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 HoverLocation findLocation(ListingPanel panel, Address address, Class locType, Predicate predicate) { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java deleted file mode 100644 index bbc8274cc8..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java +++ /dev/null @@ -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 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 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 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"))); - } -} diff --git a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncPairingQueue.java b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncPairingQueue.java index 9c8eb46bde..42860602a4 100644 --- a/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncPairingQueue.java +++ b/Ghidra/Debug/Framework-AsyncComm/src/main/java/ghidra/async/AsyncPairingQueue.java @@ -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 { return null; }); } + + public boolean isEmpty() { + synchronized (givers) { + return givers.isEmpty() && takers.isEmpty(); + } + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java index af8f8157c7..d2271df762 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelBuilder.java @@ -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; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java index e06e81ee5e..7d5110f24b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java @@ -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)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java index c2ad274f06..dd19a119fb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java @@ -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, diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java index 4d3ac04a73..433e5df88b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java @@ -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 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); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java index fac9db978e..fee2453f98 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java @@ -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; diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java index bf58413e20..cc72a91923 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java @@ -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 action) + throws Throwable { + Map 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 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 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> taskSupplier, + TraceObject process, AddressRange range) throws Exception { + while (!taskSupplier.get().isDone()) { + while (!rmiMethodReadMem.argQueue().isEmpty()) { + handleReadMemInvocation(process, range, () -> null); + } + } + } + + protected void handleAtLeastOneMemReadInv(Supplier> 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 wrapVals(Map 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")); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerObjectRecorderBreakpointsProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerObjectRecorderBreakpointsProviderTest.java deleted file mode 100644 index 24afab15a7..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerObjectRecorderBreakpointsProviderTest.java +++ /dev/null @@ -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(""" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - """, index)); - - try (Transaction tx = tb.startTransaction()) { - tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); - tb.trace.getObjectManager() - .createObject( - TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints")); - } - } -} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointMarkerPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointMarkerPluginTest.java deleted file mode 100644 index 5f9a40e524..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointMarkerPluginTest.java +++ /dev/null @@ -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 { - - @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 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 - } -} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointsProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointsProviderTest.java deleted file mode 100644 index c427007143..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointsProviderTest.java +++ /dev/null @@ -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 { - - @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 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)); - } -} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java index 5c6ed99460..938ed0ec30 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java @@ -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 resume() { - commands.add("resume"); - setState(TargetExecutionState.RUNNING); - return super.resume(); - } - - @Override - public CompletableFuture interrupt() { - commands.add("interrupt"); - setState(TargetExecutionState.STOPPED); - return super.interrupt(); - } - - @Override - public CompletableFuture kill() { - commands.add("kill"); - setState(TargetExecutionState.TERMINATED); - return super.kill(); - } - - @Override - public CompletableFuture step(TargetStepKind kind) { - commands.add("step(" + kind + ")"); - setState(TargetExecutionState.RUNNING); - setState(TargetExecutionState.STOPPED); - return super.step(kind); - } - }; - } - - @Override - public CompletableFuture 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 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 { diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java index e06cd9fdbf..2da6e82135 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java @@ -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 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 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 stepExt() { - commands.add("stepExt"); - return AsyncUtils.nil(); - } - - // Takes a second required non-default parameter - @TargetMethod.Export("AdvanceWithFlag") - public CompletableFuture 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 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 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); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java similarity index 92% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java index 83c9b88f29..341ba69194 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java @@ -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); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java similarity index 90% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index ab75f0e6ce..63d7101fc0 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -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 diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java similarity index 81% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index 45909adbf2..c48e2d171a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -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); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerGuestRecorderRegistersProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerGuestRecorderRegistersProviderTest.java deleted file mode 100644 index 25c82dcf05..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerGuestRecorderRegistersProviderTest.java +++ /dev/null @@ -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.register; - -import java.io.IOException; -import java.util.Set; - -import org.junit.Before; - -import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper; -import ghidra.dbg.target.TargetObject; -import ghidra.debug.api.model.DebuggerTargetTraceMapper; -import ghidra.debug.api.model.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.lang.CompilerSpecID; -import ghidra.program.model.lang.LanguageID; -import ghidra.trace.model.Trace; -import ghidra.trace.model.guest.TracePlatform; -import ghidra.trace.model.thread.TraceThread; - -public class DebuggerGuestRecorderRegistersProviderTest - extends DebuggerRecorderRegistersProviderTest { - - @Override - protected void createTrace() throws IOException { - createTrace("DATA:BE:64:default"); - } - - @Before - @Override - public void setUpRegistersProviderTest() throws Exception { - setUpGuestRegistersProviderTest(); - } - - @Override - protected TracePlatform getPlatform() { - return toy; - } - - @Override - protected void activateThread(TraceThread thread) { - traceManager.activate(traceManager.resolveThread(thread).platform(toy)); - } - - @Override - protected TargetObject chooseTarget() { - return mb.testModel.session; - } - - @Override - protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target) - throws Exception { - return new ObjectBasedDebuggerTargetTraceMapper(target, - new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of()) { - @Override - public TraceRecorder startRecording(PluginTool tool, Trace trace) { - useTrace(trace); - return super.startRecording(tool, trace); - } - }; - } - - @Override - protected TraceRecorder recordAndWaitSync() throws Throwable { - TraceRecorder recorder = super.recordAndWaitSync(); - createToyPlatform(); - return recorder; - } -} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRecorderRegistersProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRecorderRegistersProviderTest.java deleted file mode 100644 index 056d7f1e50..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRecorderRegistersProviderTest.java +++ /dev/null @@ -1,105 +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.register; - -import static org.junit.Assert.*; - -import org.junit.Test; -import org.junit.experimental.categories.Category; - -import generic.test.category.NightlyCategory; -import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; -import ghidra.pcode.utils.Utils; -import ghidra.program.model.data.DoubleDataType; - -@Category(NightlyCategory.class) -public class DebuggerRecorderRegistersProviderTest extends AbstractDebuggerRegistersProviderTest { - - @Test - public void testLiveAddValuesThenActivatePopulatesPanel() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); - waitForSwing(); - - mb.testBank1.writeRegister("pc", new byte[] { 0x00, 0x40, 0x00, 0x00 }); - waitForSwing(); - - activateThread(recorder.getTraceThread(mb.testThread1)); - waitForSwing(); - - assertPCRowValuePopulated(); - } - - @Test - public void testLiveActivateThenAddValuesPopulatesPanel() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); - activateThread(recorder.getTraceThread(mb.testThread1)); - waitForSwing(); - - mb.testBank1.writeRegister("pc", new byte[] { 0x00, 0x40, 0x00, 0x00 }); - waitRecorder(recorder); - - RegisterRow rowL = findRegisterRow(pc); - waitForPass(() -> assertTrue(rowL.isKnown())); - assertPCRowValuePopulated(); - } - - @Test - public void testModifyValueLive() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); - activateThread(recorder.getTraceThread(mb.testThread1)); - waitForSwing(); - - controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); - - assertTrue(registersProvider.actionEnableEdits.isEnabled()); - performAction(registersProvider.actionEnableEdits); - - RegisterRow row = findRegisterRow(r0); - - setRowText(row, "1234"); - waitRecorder(recorder); - assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0x12, 0x34 }, - mb.testBank1.regVals.get("r0")); - } - - @Test - public void testModifyRepresentationLive() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); - activateThread(recorder.getTraceThread(mb.testThread1)); - waitForSwing(); - - controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); - - assertTrue(registersProvider.actionEnableEdits.isEnabled()); - performAction(registersProvider.actionEnableEdits); - - RegisterRow row = findRegisterRow(r0); - assertFalse(row.isRepresentationEditable()); - - row.setDataType(DoubleDataType.dataType); - waitForDomainObject(tb.trace); - - setRowRepr(row, "1234"); - waitRecorder(recorder); - assertEquals(encodeDouble(1234), - Utils.bytesToLong(mb.testBank1.regVals.get("r0"), 8, true)); - } -} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRmiRegistersProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRmiRegistersProviderTest.java index 68a18f7cf7..e1fa0fdacd 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRmiRegistersProviderTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRmiRegistersProviderTest.java @@ -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,15 +15,15 @@ */ package ghidra.app.plugin.core.debug.gui.register; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import java.util.*; +import java.util.Objects; import org.junit.Test; import db.Transaction; import ghidra.debug.api.control.ControlMode; -import ghidra.pcode.utils.Utils; import ghidra.program.model.data.DoubleDataType; import ghidra.trace.model.Lifespan; import ghidra.trace.model.stack.TraceObjectStackFrame; @@ -49,30 +49,12 @@ public class DebuggerRmiRegistersProviderTest extends AbstractDebuggerRegistersP return Objects.requireNonNull(tb.obj("Processes[1]")); } - protected void handleReadRegsInvocation(TraceObject container) throws Throwable { - Map args = rmiMethodReadRegs.expect(); - rmiMethodReadRegs.result(null); - assertEquals(Map.ofEntries( - Map.entry("container", container)), - args); - } - - protected void handleWriteRegInvocation(TraceObjectStackFrame frame, String name, long value) - throws Throwable { - Map args = rmiMethodWriteReg.expect(); - rmiMethodWriteReg.result(null); - assertEquals(Set.of("frame", "name", "value"), args.keySet()); - assertEquals(frame.getObject(), args.get("frame")); - assertEquals("r0", args.get("name")); - assertEquals(value, Utils.bytesToLong((byte[]) args.get("value"), 8, true)); - } - @Test public void testReadLiveValuesOnActivate() throws Throwable { setUpRmiTarget(); waitForSwing(); - handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers")); + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> null); } @Test diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java similarity index 81% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index cec77a7f61..092055c804 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -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. @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.*; import java.util.stream.Collectors; @@ -28,7 +29,7 @@ import db.Transaction; import generic.Unique; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -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.gui.listing.DebuggerListingProvider; import ghidra.app.plugin.core.debug.gui.register.*; @@ -37,9 +38,7 @@ import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.services.DebuggerControlService; import ghidra.dbg.model.TestTargetRegisterBankInThread; -import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; import ghidra.docking.settings.FormatSettingsDefinition; import ghidra.docking.settings.Settings; import ghidra.framework.options.SaveState; @@ -52,14 +51,17 @@ import ghidra.program.model.mem.Memory; import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.Symbol; import ghidra.program.util.ProgramLocation; -import ghidra.trace.model.*; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.Lifespan; import ghidra.trace.model.memory.*; -import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.stack.TraceObjectStackFrame; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; -public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTest { +public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerIntegrationTest { protected static void assertNoErr(DefaultWatchRow row) { Throwable error = row.getError(); @@ -74,14 +76,13 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes protected DebuggerListingProvider listingProvider; protected DebuggerStaticMappingServicePlugin mappingService; protected CodeViewerProvider codeViewerProvider; - protected DebuggerControlService editingService; + protected DebuggerControlService controlService; protected Register r0; protected Register r1; - protected TraceThread thread; + protected TraceObjectThread thread; protected TestTargetRegisterBankInThread bank; - protected TraceRecorder recorder; @Before public void setUpWatchesProviderTest() throws Exception { @@ -94,13 +95,15 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); listingProvider = waitForComponentProvider(DebuggerListingProvider.class); mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); - editingService = addPlugin(tool, DebuggerControlServicePlugin.class); + controlService = addPlugin(tool, DebuggerControlServicePlugin.class); createTrace(); r0 = tb.language.getRegister("r0"); r1 = tb.language.getRegister("r1"); try (Transaction tx = tb.startTransaction()) { - thread = tb.getOrAddThread("Thread1", 0); + tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); + tb.createObjectsProcessAndThreads(); + thread = tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); } // TODO: This seems to hold up the task manager. @@ -117,8 +120,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes } } - private void setRegisterValues(TraceThread thread) { + private void setRegisterValues(TraceObjectThread thread) { try (Transaction tx = tb.startTransaction()) { + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 1); TraceMemorySpace regVals = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); regVals.setValue(0, new RegisterValue(r0, BigInteger.valueOf(0x00400000))); @@ -153,6 +157,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes row.setExpression("r0"); setRegisterValues(thread); + waitForDomainObject(tb.trace); waitForWatches(); assertEquals("0x400000", row.getRawValueString()); @@ -304,46 +309,62 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes @Test public void testLiveCausesReads() throws Throwable { - createTestModel(); - mb.createTestProcessesAndThreads(); - bank = mb.testThread1.addRegisterBank(); + runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone)); - // Write before we record, and verify trace has not recorded it before setting watch - mb.testProcess1.regs.addRegistersFromLanguage(tb.language, Register::isBaseRegister); - bank.writeRegister("r0", tb.arr(0, 0, 0, 0, 0, 0x40, 0, 0)); - mb.testProcess1.addRegion(".header", mb.rng(0, 0x1000), "r"); // Keep the listing away - mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx"); - mb.testProcess1.memory.writeMemory(mb.addr(0x00400000), tb.arr(1, 2, 3, 4)); + createRmiConnection(); + addMemoryMethods(); + addRegisterMethods(); - recorder = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); - waitRecorder(recorder); - Trace trace = recorder.getTrace(); - TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); + try (Transaction tx = tb.startTransaction()) { + tb.trace.getMemoryManager() + .addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L), + tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ, + TraceMemoryFlag.EXECUTE); + tb.createObjectsFramesAndRegs( + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class), + Lifespan.nowOn(0), tb.host, 1); + } + waitForDomainObject(tb.trace); + TraceObject process = tb.obj("Processes[1]"); + rmiCx.publishTarget(tool, tb.trace); - traceManager.openTrace(trace); - traceManager.activateThread(thread); + traceManager.openTrace(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); waitForWatches(); - // Verify no target read has occurred yet - TraceMemorySpace regs = - trace.getMemoryManager().getMemoryRegisterSpace(thread, false); - if (regs != null) { - assertEquals(BigInteger.ZERO, regs.getValue(0, r0).getUnsignedValue()); - } - ByteBuffer buf = ByteBuffer.allocate(4); - assertEquals(4, trace.getMemoryManager().getBytes(0, tb.addr(0x00400000), buf)); - assertArrayEquals(tb.arr(0, 0, 0, 0), buf.array()); + assertTrue(rmiMethodReadRegs.argQueue().isEmpty()); performAction(watchesProvider.actionAdd); DefaultWatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); row.setExpression("*:4 r0"); row.setDataType(LongDataType.dataType); + + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> { + try (Transaction tx = tb.startTransaction()) { + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(tb.reg("r0"), new BigInteger("55550000", 16))); + } + return null; + }); + handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff), () -> { + try (Transaction tx = tb.startTransaction()) { + ByteBuffer buf = ByteBuffer.allocate(0x1000).order(ByteOrder.BIG_ENDIAN); + buf.putInt(0x01020304); + buf.position(0x1000); + buf.flip(); + tb.trace.getMemoryManager().putBytes(0, tb.addr(0x55550000), buf); + } + return null; + }); waitForWatches(); assertNoErr(row); assertEquals("{ 01 02 03 04 }", row.getRawValueString()); assertEquals("1020304h", row.getValueString()); + + rmiCx.withdrawTarget(tool, tb.trace); } protected void runTestIsEditableEmu(String expression, boolean expectWritable) { @@ -356,7 +377,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes assertFalse(row.isRawValueEditable()); traceManager.openTrace(tb.trace); traceManager.activateThread(thread); - editingService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); + controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); waitForWatches(); assertNoErr(row); @@ -390,7 +411,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes traceManager.openTrace(tb.trace); traceManager.activateThread(thread); - editingService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); + controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); waitForWatches(); performAction(watchesProvider.actionEnableEdits); @@ -536,31 +557,36 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes } protected DefaultWatchRow prepareTestEditTarget(String expression) throws Throwable { - createTestModel(); - mb.createTestProcessesAndThreads(); - bank = mb.testThread1.addRegisterBank(); + runSwing(() -> listingProvider.setAutoReadMemorySpec(readNone)); - mb.testProcess1.regs.addRegistersFromLanguage(tb.language, - r -> r != r1 && r.isBaseRegister()); - bank.writeRegister("r0", tb.arr(0, 0, 0, 0, 0, 0x40, 0, 0)); - mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx"); + createRmiConnection(); + addMemoryMethods(); + addRegisterMethods(); - recorder = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); - Trace trace = recorder.getTrace(); - waitRecorder(recorder); - TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); + try (Transaction tx = tb.startTransaction()) { + tb.trace.getMemoryManager() + .addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0L), + tb.range(0x55550000, 0x5555ffff), TraceMemoryFlag.READ, + TraceMemoryFlag.EXECUTE); + tb.createObjectsFramesAndRegs( + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class), + Lifespan.nowOn(0), tb.host, 1); - traceManager.openTrace(trace); - traceManager.activateThread(thread); - editingService.setCurrentMode(trace, ControlMode.RW_TARGET); + tb.obj("Processes[1].Threads[1].Stack[0].Registers[r1]").delete(); + } + waitForDomainObject(tb.trace); + rmiCx.publishTarget(tool, tb.trace); + + traceManager.openTrace(tb.trace); + traceManager.activateTrace(tb.trace); waitForSwing(); + waitForWatches(); performAction(watchesProvider.actionAdd); DefaultWatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); row.setExpression(expression); - waitForWatches(); + controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); performAction(watchesProvider.actionEnableEdits); return row; @@ -570,31 +596,67 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes public void testEditRegisterTarget() throws Throwable { DefaultWatchRow row = prepareTestEditTarget("r0"); + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> { + try (Transaction tx = tb.startTransaction()) { + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(tb.reg("r0"), new BigInteger("55550000", 16))); + } + return null; + }); + waitForWatches(); + runSwing(() -> row.setRawValueString("0x1234")); - retryVoid(() -> { - assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0")); - }, List.of(AssertionError.class)); + + handleWriteRegInvocation( + tb.obj("Processes[1].Threads[1].Stack[0]").queryInterface(TraceObjectStackFrame.class), + "r0", 0x1234); + + rmiCx.withdrawTarget(tool, tb.trace); } @Test public void testEditMemoryTarget() throws Throwable { DefaultWatchRow row = prepareTestEditTarget("*:8 r0"); - // Wait for the async reads to settle. - waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); + TraceObject process = tb.obj("Processes[1]"); + + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> { + try (Transaction tx = tb.startTransaction()) { + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(tb.reg("r0"), new BigInteger("55550000", 16))); + } + return null; + }); + handleReadMemInvocation(process, tb.range(0x55550000, 0x55550fff), () -> { + try (Transaction tx = tb.startTransaction()) { + ByteBuffer buf = ByteBuffer.allocate(0x1000).order(ByteOrder.BIG_ENDIAN); + buf.putInt(0x01020304); + buf.position(0x1000); + buf.flip(); + tb.trace.getMemoryManager().putBytes(0, tb.addr(0x55550000), buf); + } + return null; + }); + waitForWatches(); + assertEquals(tb.addr(0x55550000), row.getAddress()); runSwing(() -> row.setRawValueString("0x1234")); - retryVoid(() -> { - assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), - waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8))); - }, List.of(AssertionError.class)); + + handleWriteMemInvocation(process, tb.addr(0x55550000), + new Bytes(0, 0, 0, 0, 0, 0, 0x12, 0x34)); + + rmiCx.withdrawTarget(tool, tb.trace); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalStateException.class) public void testEditNonMappableRegisterTarget() throws Throwable { DefaultWatchRow row = prepareTestEditTarget("r1"); - TraceThread thread = recorder.getTraceThread(mb.testThread1); - // Sanity check - assertNull(recorder.isRegisterOnTarget(tb.host, thread, 0, r1)); + + // Reads go for all registers, even if requested one is not mapped. (TODO?) + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> null); + assertTrue(rmiMethodReadRegs.argQueue().isEmpty()); + waitForWatches(); assertFalse(row.isRawValueEditable()); runSwingWithException(() -> row.setRawValueString("0x1234")); @@ -603,7 +665,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes protected void setupUnmappedDataSection() throws Throwable { try (Transaction tx = tb.startTransaction()) { TraceMemoryOperations mem = tb.trace.getMemoryManager(); - mem.createRegion("Memory[bin:.data]", 0, tb.range(0x00600000, 0x0060ffff), + mem.createRegion("Processes[1].Memory[bin:.data]", 0, tb.range(0x00600000, 0x0060ffff), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); } waitForDomainObject(tb.trace); @@ -620,7 +682,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes try (Transaction tx = tb.startTransaction()) { TraceMemoryOperations mem = tb.trace.getMemoryManager(); - mem.createRegion("Memory[bin:.data]", 0, tb.range(0x55750000, 0x5575ffff), + mem.createRegion("Processes[1].Memory[bin:.data]", 0, tb.range(0x55750000, 0x5575ffff), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); } waitForDomainObject(tb.trace); @@ -735,6 +797,10 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes traceManager.activateThread(thread); waitForSwing(); + try (Transaction tx = tb.startTransaction()) { + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 1); + } + runSwing(() -> { RegisterRow rowR0 = registersProvider.getRegisterRow(r0); rowR0.setDataType(PointerDataType.dataType); @@ -759,6 +825,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerTes .createLabel(tb.addr(0x00601234), "my_symbol", SourceType.USER_DEFINED); } try (Transaction tx = tb.startTransaction()) { + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 1); TraceMemorySpace regVals = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); regVals.setValue(0, new RegisterValue(r0, BigInteger.valueOf(0x55751234))); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java deleted file mode 100644 index 6684bb0235..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java +++ /dev/null @@ -1,115 +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.breakpoint; - -import java.io.IOException; - -import org.junit.Ignore; - -import db.Transaction; -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; - -@Ignore("Deprecated") -public class DebuggerObjectRecorderLogicalBreakpointServiceTest - extends DebuggerRecorderLogicalBreakpointServiceTest { - - 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(""" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - """, index)); - - try (Transaction tx = tb.startTransaction()) { - tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); - tb.trace.getObjectManager() - .createObject( - TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints")); - } - } -} diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java deleted file mode 100644 index a3e2820e5a..0000000000 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java +++ /dev/null @@ -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.breakpoint; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.Set; - -import org.junit.Before; -import org.junit.Ignore; - -import db.Transaction; -import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; -import ghidra.dbg.model.TestTargetMemoryRegion; -import ghidra.dbg.model.TestTargetProcess; -import ghidra.dbg.target.*; -import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; -import ghidra.debug.api.action.ActionSource; -import ghidra.debug.api.model.TraceRecorder; -import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; -import ghidra.trace.model.DefaultTraceLocation; -import ghidra.trace.model.Trace; -import ghidra.trace.model.breakpoint.TraceBreakpoint; -import ghidra.trace.model.memory.TraceMemoryRegion; -import ghidra.trace.model.modules.TraceStaticMapping; - -@Ignore("Deprecated") -public class DebuggerRecorderLogicalBreakpointServiceTest extends - AbstractDebuggerLogicalBreakpointServiceTest { - - @Before - public void setUpRecorderBreakpointServiceTest() throws Throwable { - // NOTE: Traces derive from recordings, not toy builder - // NOTE: Program must be saved into project so it has a URL for mappings - createTestModel(); - mb.createTestProcessesAndThreads(); - } - - @Override - protected void createTarget1() throws Throwable { - target1 = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); - } - - @Override - protected void createTarget3() throws Throwable { - target3 = modelService.recordTarget(mb.testProcess3, - createTargetTraceMapper(mb.testProcess3), ActionSource.AUTOMATIC); - } - - @Override - protected Trace getTrace(TraceRecorder recorder) { - return recorder.getTrace(); - } - - @Override - protected void simulateTargetStep(TraceRecorder recorder) throws Throwable { - assertEquals(target1, recorder); // In case I ever pass in target3, scream. - mb.testModel.session.simulateStep(mb.testThread1); - waitRecorder(recorder); - } - - @Override - protected long getSnap(TraceRecorder recorder) { - return recorder.getSnap(); - } - - @Override - protected boolean isTargetValid(TraceRecorder recorder) { - return recorder.isRecording(); - } - - protected TestTargetMemoryRegion addTargetTextRegion(TestTargetProcess p, long offset) { - return p.addRegion("bin:.text", mb.rng(offset, offset + 0x0fff), "rx"); - } - - protected TestTargetMemoryRegion addTargetDataRegion(TestTargetProcess p) { - return p.addRegion("bin:.data", mb.rng(0x56550000, 0x5655ffff), "rw"); - } - - protected TestTargetProcess getProcess(TraceRecorder recorder) { - if (recorder == target1) { - return mb.testProcess1; - } - if (recorder == target3) { - return mb.testProcess3; - } - throw new AssertionError(); - } - - @Override - protected TestTargetMemoryRegion addTargetTextRegion(TraceRecorder recorder, long offset) { - return addTargetTextRegion(getProcess(recorder), offset); - } - - @Override - protected TestTargetMemoryRegion addTargetTextRegion(TraceRecorder recorder) { - return addTargetTextRegion(getProcess(recorder), 0x55550000); - } - - @Override - protected TestTargetMemoryRegion addTargetDataRegion(TraceRecorder recorder) throws Throwable { - return addTargetDataRegion(getProcess(recorder)); - } - - @Override - protected void addTextMapping(TraceRecorder r, TestTargetMemoryRegion region, Program p) - throws Throwable { - Trace t = r.getTrace(); - TraceMemoryRegion textRegion = - waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region); - try (Transaction tx = t.openTransaction("Add .text mapping")) { - DebuggerStaticMappingUtils.addMapping( - new DefaultTraceLocation(t, null, textRegion.getLifespan(), - textRegion.getMinAddress()), - new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, - false); - } - waitForDomainObject(t); - } - - @Override - protected void removeTextMapping(TraceRecorder r, Program p) throws Throwable { - Trace t = r.getTrace(); - try (Transaction tx = t.openTransaction("Remove .text mapping")) { - TraceStaticMapping mapping = - t.getStaticMappingManager().findContaining(addr(t, 0x55550000), r.getSnap()); - mapping.delete(); - } - waitForDomainObject(t); - } - - @Override - protected void addTargetAccessBreakpoint(TraceRecorder r, TestTargetMemoryRegion region) - throws Throwable { - TraceMemoryRegion traceRegion = - waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region); - long offset = traceRegion.getMinAddress().getOffset() + 0x0123; - TargetBreakpointSpecContainer cont = getBreakpointContainer(r); - waitOn(cont.placeBreakpoint(mb.addr(offset), - Set.of(TargetBreakpointKind.READ, TargetBreakpointKind.WRITE))); - waitRecorder(r); - } - - @Override - protected void addTargetSoftwareBreakpoint(TraceRecorder r, TestTargetMemoryRegion region) - throws Throwable { - TraceMemoryRegion traceRegion = - waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region); - long offset = traceRegion.getMinAddress().getOffset() + 0x0123; - TargetBreakpointSpecContainer cont = getBreakpointContainer(r); - waitOn(cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))); - waitRecorder(r); - } - - @Override - protected void removeTargetSoftwareBreakpoint(TraceRecorder r) throws Throwable { - TargetBreakpointSpecContainer cont = getBreakpointContainer(r); - waitOn(cont.fetchElements().thenCompose(elements -> { - for (TargetObject obj : elements.values()) { - if (!(obj instanceof TargetBreakpointSpec) || - !(obj instanceof TargetDeletable)) { - continue; - } - TargetBreakpointSpec spec = (TargetBreakpointSpec) obj; - if (!spec.getKinds().contains(TargetBreakpointKind.SW_EXECUTE)) { - continue; - } - TargetDeletable del = (TargetDeletable) obj; - return del.delete(); - } - fail("No deletable software breakpoint spec found"); - throw new AssertionError(); - })); - waitRecorder(r); - } - - @Override - protected void terminateTarget(TraceRecorder recorder) { - recorder.stopRecording(); - } - - @Override - protected TraceBreakpoint findLoc(Set locs, int index) { - return locs.stream() - .filter(b -> b.getName().equals(Integer.toString(index))) - .findAny() - .orElseThrow(); - } - - @Override - protected void handleToggleBreakpointInvocation(TraceRecorder target, - TraceBreakpoint expectedBreakpoint, boolean expectedEnabled) throws Throwable { - // Logic is in the Test model - waitRecorder(target); - } - - @Override - protected void handleDeleteBreakpointInvocation(TraceRecorder target, - TraceBreakpoint expectedBreakpoint) throws Throwable { - // Logic is in the Test model - waitRecorder(target); - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java similarity index 58% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java index b00a328d0a..675ff8b53a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java @@ -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. @@ -16,17 +16,8 @@ package ghidra.app.plugin.core.debug.service.control; import java.io.IOException; -import java.util.Set; import db.Transaction; -import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper; -import ghidra.dbg.target.TargetObject; -import ghidra.debug.api.model.DebuggerTargetTraceMapper; -import ghidra.debug.api.model.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.lang.CompilerSpecID; -import ghidra.program.model.lang.LanguageID; -import ghidra.trace.model.Trace; import ghidra.trace.model.guest.TraceGuestPlatform; import ghidra.trace.model.guest.TracePlatform; @@ -60,29 +51,4 @@ public class DebuggerControlServiceGuestTest extends DebuggerControlServiceTest protected TracePlatform getPlatform() { return platform; } - - @Override - protected TargetObject chooseTarget() { - return mb.testModel.session; - } - - @Override - protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target) - throws Exception { - return new ObjectBasedDebuggerTargetTraceMapper(target, - new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of()) { - @Override - public TraceRecorder startRecording(PluginTool tool, Trace trace) { - useTrace(trace); - return super.startRecording(tool, trace); - } - }; - } - - @Override - protected TraceRecorder recordAndWaitSync() throws Throwable { - TraceRecorder recorder = super.recordAndWaitSync(); - createToyPlatform(); - return recorder; - } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java similarity index 77% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java index ba2cf033aa..235896eb6b 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java @@ -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,31 +17,36 @@ package ghidra.app.plugin.core.debug.service.control; import static org.junit.Assert.*; +import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; import org.junit.Before; import org.junit.Test; import db.Transaction; import ghidra.app.plugin.assembler.*; -import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest; +import ghidra.app.plugin.core.debug.service.MockTarget; import ghidra.app.services.DebuggerControlService; import ghidra.app.services.DebuggerControlService.StateEditor; import ghidra.async.AsyncUtils.TemperamentalRunnable; -import ghidra.dbg.target.TargetRegisterBank; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.trace.model.Lifespan; import ghidra.trace.model.guest.TracePlatform; -import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.stack.TraceObjectStackFrame; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.TraceSchedule; -public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest { +public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerIntegrationTest { protected DebuggerControlService controlService; protected Register r0; @@ -129,6 +134,14 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest }); } + @Override + protected void createAndOpenTrace(String langID) throws IOException { + super.createAndOpenTrace(langID); + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); + } + } + @Test public void testWriteEmuMemory() throws Throwable { createAndOpenTrace(); @@ -136,7 +149,7 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - tb.getOrAddThread("Threads[0]", 0); + tb.createObjectsProcessAndThreads(); } activateTrace(); waitForSwing(); @@ -160,10 +173,11 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest createAndOpenTrace(); controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); - TraceThread thread; + TraceObjectThread thread; try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - thread = tb.getOrAddThread("Threads[0]", 0); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); } activateTrace(); waitForSwing(); @@ -189,9 +203,11 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest createAndOpenTrace(); controlService.setCurrentMode(tb.trace, ControlMode.RW_TRACE); + TraceObjectThread thread; try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - TraceThread thread = tb.getOrAddThread("Threads[0]", 0); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); Assembler asm = Assemblers.getAssembler(getPlatform().getLanguage()); AssemblyBuffer buf = new AssemblyBuffer(asm, tb.addr(getPlatform(), 0x00400000)); buf.assemble("imm r0,#123"); @@ -203,7 +219,7 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); waitForSwing(); - TraceSchedule step1 = TraceSchedule.parse("0:t0-1"); + TraceSchedule step1 = TraceSchedule.snap(0).steppedForward(thread, 1); traceManager.activateTime(step1); waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime())); @@ -227,10 +243,11 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest createAndOpenTrace(); controlService.setCurrentMode(tb.trace, ControlMode.RW_TRACE); - TraceThread thread; + TraceObjectThread thread; try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - thread = tb.getOrAddThread("Threads[0]", 0); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); Assembler asm = Assemblers.getAssembler(getPlatform().getLanguage()); AssemblyBuffer buf = new AssemblyBuffer(asm, tb.addr(getPlatform(), 0x00400000)); buf.assemble("imm r0,#123"); @@ -242,7 +259,7 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); waitForSwing(); - TraceSchedule step1 = TraceSchedule.parse("0:t0-1"); + TraceSchedule step1 = TraceSchedule.snap(0).steppedForward(thread, 1); traceManager.activateTime(step1); waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime())); @@ -269,7 +286,8 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - tb.getOrAddThread("Threads[0]", 0); + TraceObjectThread thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); } activateTrace(); waitForSwing(); @@ -296,10 +314,11 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest createAndOpenTrace(); controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); - TraceThread thread; + TraceObjectThread thread; try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - thread = tb.getOrAddThread("Threads[0]", 0); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); } activateTrace(); waitForSwing(); @@ -366,10 +385,11 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest createAndOpenTrace(); controlService.setCurrentMode(tb.trace, ControlMode.RW_TRACE); - TraceThread thread; + TraceObjectThread thread; try (Transaction tx = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread - thread = tb.getOrAddThread("Threads[0]", 0); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); } activateTrace(); waitForSwing(); @@ -390,88 +410,116 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest assertEquals(rv1234, value); } + protected Target addTarget() throws Throwable { + createRmiConnection(); + addRegisterMethods(); + addMemoryMethods(); + + try (Transaction tx = tb.startTransaction()) { + tb.createObjectsProcessAndThreads(); + TraceObjectThread thread = + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), getPlatform(), 1); + tb.trace.getMemoryManager() + .addRegion("Processes[1].Memory[exe:.text]", Lifespan.nowOn(0), + tb.range(0x00400000, 00401000), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + } + + return rmiCx.publishTarget(tool, tb.trace); + } + @Test public void testWriteTargetMemory() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(tb.trace); - activateTrace(); - traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); + createAndOpenTrace(); + addTarget(); + + TraceObject process = tb.obj("Processes[1]"); + TraceThread thread = + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); + activateTrace(); // platform + traceManager.activateThread(thread); waitForSwing(); - controlService.setCurrentMode(recorder.getTrace(), ControlMode.RW_TARGET); + controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); StateEditor editor = createStateEditor(); assertTrue(editor.isVariableEditable(tb.addr(0x00400000), 4)); - waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); - - assertArrayEquals(mb.arr(1, 2, 3, 4), - waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 4))); + CompletableFuture future = + editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)); + handleWriteMemInvocation(process, tb.addr(0x00400000), new Bytes(1, 2, 3, 4)); + waitOn(future); } @Test public void testWriteTargetRegister() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - TargetRegisterBank bank = - (TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank"); - traceManager.openTrace(tb.trace); - activateTrace(); + createAndOpenTrace(); + addTarget(); + + TraceThread thread = + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); + activateTrace(); // platform + traceManager.activateThread(thread); waitForSwing(); - traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); - waitForSwing(); - controlService.setCurrentMode(recorder.getTrace(), ControlMode.RW_TARGET); + controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); StateEditor editor = createStateEditor(); assertTrue(editor.isRegisterEditable(r0)); - waitOn(editor.setRegister(rv1234)); - - assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0"))); + CompletableFuture future = editor.setRegister(rv1234); + handleWriteRegInvocation( + tb.obj("Processes[1].Threads[1].Stack[0]").queryInterface(TraceObjectStackFrame.class), + "r0", 1234); + waitOn(future); } @Test public void testWriteTargetSubRegister() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - TargetRegisterBank bank = - (TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank"); - traceManager.openTrace(tb.trace); - activateTrace(); - TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); + createAndOpenTrace(); + addTarget(); + + TraceThread thread = + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); + activateTrace(); // platform traceManager.activateThread(thread); waitForSwing(); - controlService.setCurrentMode(recorder.getTrace(), ControlMode.RW_TARGET); + controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); StateEditor editor = createStateEditor(); - waitForPass(() -> assertTrue(editor.isRegisterEditable(r0))); - waitOn(editor.setRegister(rv1234)); - waitForPass(() -> { - TraceMemorySpace regs = - tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false); - assertNotNull(regs); - RegisterValue value = regs.getValue(getPlatform(), traceManager.getCurrentSnap(), r0); - assertEquals(rv1234, value); - }); - waitForPass(() -> assertTrue(editor.isRegisterEditable(r0h))); - waitOn(editor.setRegister(rvHigh1234)); + assertTrue(editor.isRegisterEditable(r0)); + CompletableFuture future = editor.setRegister(rv1234); + handleWriteRegInvocation( + tb.obj("Processes[1].Threads[1].Stack[0]").queryInterface(TraceObjectStackFrame.class), + "r0", 1234); + waitOn(future); - assertArrayEquals(mb.arr(0, 0, 4, 0xd2, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0"))); + assertTrue(editor.isRegisterEditable(r0h)); + CompletableFuture future2 = editor.setRegister(rvHigh1234); + handleWriteRegInvocation( + tb.obj("Processes[1].Threads[1].Stack[0]").queryInterface(TraceObjectStackFrame.class), + "r0h", 1234); + waitOn(future2); } @Test public void testWriteTargetRequiresPresent() throws Throwable { - TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(tb.trace); - activateTrace(); - traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); + createAndOpenTrace(); + Target target = addTarget(); + + TraceThread thread = + tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); + activateTrace(); // platform + traceManager.activateThread(thread); waitForSwing(); controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); waitForSwing(); - assertEquals(recorder.getSnap(), traceManager.getCurrentSnap()); + assertEquals(target.getSnap(), traceManager.getCurrentSnap()); traceManager.activateSnap(traceManager.getCurrentSnap() - 1); waitForSwing(); assertEquals( "Cannot navigate time in Control Target mode. Switch to Trace or Emulate mode first.", tool.getStatusInfo()); - assertEquals(recorder.getSnap(), traceManager.getCurrentSnap()); + assertEquals(target.getSnap(), traceManager.getCurrentSnap()); controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); waitForSwing(); @@ -481,7 +529,7 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest controlService.setCurrentMode(tb.trace, ControlMode.RW_TARGET); waitForSwing(); - assertEquals(recorder.getSnap(), traceManager.getCurrentSnap()); + assertEquals(target.getSnap(), traceManager.getCurrentSnap()); } @Test diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java similarity index 72% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java rename to Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index 2d4199b23e..972664d80b 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -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. @@ -25,30 +25,27 @@ 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.AbstractGhidraHeadedDebuggerIntegrationTest; import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; import ghidra.app.services.DebuggerControlService; -import ghidra.dbg.model.TestTargetStack; -import ghidra.dbg.model.TestTargetStackFrameHasRegisterBank; import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.XmlSchemaContext; -import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainFile; import ghidra.trace.database.target.DBTraceObjectManager; import ghidra.trace.database.target.DBTraceObjectManagerTest; +import ghidra.trace.model.Lifespan; import ghidra.trace.model.Trace; -import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; @Category(NightlyCategory.class) // this may actually be an @PortSensitive test -public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebuggerTest { +public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebuggerIntegrationTest { protected DebuggerControlService editingService; @@ -356,146 +353,143 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge @Test public void testFollowPresent() throws Throwable { - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); - waitRecorder(recorder); - Trace trace = recorder.getTrace(); - - traceManager.openTrace(trace); - traceManager.activateTrace(trace); + createRmiConnection(); + createAndOpenTrace(); + TraceObjectThread thread; + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); + thread = tb.createObjectsProcessAndThreads(); + } + Target target = rmiCx.publishTarget(tool, tb.trace); + traceManager.activateThread(thread); waitForSwing(); - assertEquals(ControlMode.RO_TARGET, editingService.getCurrentMode(trace)); - long initSnap = recorder.getSnap(); + assertEquals(ControlMode.RO_TARGET, editingService.getCurrentMode(tb.trace)); + long initSnap = target.getSnap(); assertEquals(initSnap, traceManager.getCurrentSnap()); - recorder.forceSnapshot(); + rmiCx.setLastSnapshot(tb.trace, initSnap + 1); + rmiCx.synthActivate(thread.getObject()); waitForSwing(); - assertEquals(initSnap + 1, recorder.getSnap()); + assertEquals(initSnap + 1, target.getSnap()); assertEquals(initSnap + 1, traceManager.getCurrentSnap()); - editingService.setCurrentMode(trace, ControlMode.RO_TRACE); + editingService.setCurrentMode(tb.trace, ControlMode.RO_TRACE); - recorder.forceSnapshot(); + rmiCx.setLastSnapshot(tb.trace, initSnap + 2); + rmiCx.synthActivate(thread.getObject()); waitForSwing(); - assertEquals(initSnap + 2, recorder.getSnap()); + assertEquals(initSnap + 2, target.getSnap()); assertEquals(initSnap + 1, traceManager.getCurrentSnap()); - editingService.setCurrentMode(trace, ControlMode.RO_TARGET); + editingService.setCurrentMode(tb.trace, ControlMode.RO_TARGET); waitForSwing(); - assertEquals(initSnap + 2, recorder.getSnap()); + assertEquals(initSnap + 2, target.getSnap()); assertEquals(initSnap + 2, traceManager.getCurrentSnap()); - recorder.forceSnapshot(); + rmiCx.setLastSnapshot(tb.trace, initSnap + 3); + rmiCx.synthActivate(thread.getObject()); waitForSwing(); - assertEquals(initSnap + 3, recorder.getSnap()); + assertEquals(initSnap + 3, target.getSnap()); assertEquals(initSnap + 3, traceManager.getCurrentSnap()); } @Test public void testSynchronizeFocusTraceToModel() throws Throwable { - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); - Trace trace = recorder.getTrace(); - - waitForValue(() -> modelService.getTarget(trace)); - waitRecorder(recorder); - - traceManager.openTrace(trace); + createRmiConnection(); + addActivateMethods(); + createAndOpenTrace(); + TraceObjectThread thread; + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 2); + } + rmiCx.publishTarget(tool, tb.trace); waitForSwing(); - assertNull(mb.testModel.session.getFocus()); + assertTrue(activationMethodsQueuesEmpty()); - traceManager.activateTrace(trace); + traceManager.activateTrace(tb.trace); waitForSwing(); + // TraceManager forces a default thread + assertEquals(thread, traceManager.getCurrentThread()); + assertEquals(Map.ofEntries( + Map.entry("thread", thread.getObject())), + rmiMethodActivateThread.expect()); + assertTrue(activationMethodsQueuesEmpty()); - // No default thread/frame when live with focus support - assertNull(traceManager.getCurrentThread()); - waitForPass(() -> assertEquals(mb.testProcess1, mb.testModel.session.getFocus())); - - TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); traceManager.activateThread(thread); waitForSwing(); + // Focus didn't change, so no activation. + assertTrue(activationMethodsQueuesEmpty()); - waitForPass(() -> assertEquals(mb.testThread1, mb.testModel.session.getFocus())); - - TestTargetStack stack = mb.testThread1.addStack(); - // Note, push simply moves the data, the new frame still has the higher index - TestTargetStackFrameHasRegisterBank frame0 = stack.pushFrameHasBank(mb.addr(0x00400000)); - TestTargetStackFrameHasRegisterBank frame1 = stack.pushFrameHasBank(mb.addr(0x00400100)); - waitForDomainObject(trace); - // Eww. I'm starting to think this could be cheating, considering focus sync at launch - waitForValue(() -> recorder.getTraceStackFrame(frame0)); - waitForValue(() -> recorder.getTraceStackFrame(frame1)); - waitForValue(() -> recorder.getTargetStackFrame(thread, 0)); - waitForValue(() -> recorder.getTargetStackFrame(thread, 1)); + TraceObject frame0 = tb.obj("Processes[1].Threads[1].Stack[0]"); + TraceObject frame1 = tb.obj("Processes[1].Threads[1].Stack[1]"); // Starting with 0 results in no change in coordinates, so ignored traceManager.activateFrame(1); waitForSwing(); - - waitForPass(() -> assertEquals(frame1, mb.testModel.session.getFocus())); + assertEquals(Map.ofEntries( + Map.entry("frame", frame1)), + rmiMethodActivateFrame.expect()); + assertTrue(activationMethodsQueuesEmpty()); traceManager.activateFrame(0); waitForSwing(); - - waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus())); + assertEquals(Map.ofEntries( + Map.entry("frame", frame0)), + rmiMethodActivateFrame.expect()); + assertTrue(activationMethodsQueuesEmpty()); } @Test public void testSynchronizeFocusModelToTrace() throws Throwable { - createTestModel(); - mb.createTestProcessesAndThreads(); - - TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); - waitRecorder(recorder); - Trace trace = recorder.getTrace(); - - traceManager.openTrace(trace); + createRmiConnection(); + addActivateMethods(); + createAndOpenTrace(); + TraceObjectThread thread; + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); + thread = tb.createObjectsProcessAndThreads(); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 2); + } + rmiCx.publishTarget(tool, tb.trace); waitForSwing(); + // Focus should never be reflected back to target + assertTrue(activationMethodsQueuesEmpty()); + assertNull(traceManager.getCurrentTrace()); - waitOn(mb.testModel.session.requestFocus(mb.testProcess1)); + rmiCx.synthActivate(tb.obj("Processes[1]")); + waitForSwing(); - // No default thread/frame when live with focus support - assertNull(traceManager.getCurrentThread()); - waitForPass(() -> assertEquals(recorder.getTrace(), traceManager.getCurrentTrace())); + // TraceManager forces a default thread + assertEquals(tb.trace, traceManager.getCurrentTrace()); + assertEquals(thread, traceManager.getCurrentThread()); - waitOn(mb.testModel.session.requestFocus(mb.testThread1)); + rmiCx.synthActivate(thread.getObject()); + waitForSwing(); + assertEquals(thread, traceManager.getCurrentThread()); - TraceThread thread1 = recorder.getTraceThread(mb.testThread1); - assertNotNull(thread1); - waitForPass(() -> assertEquals(thread1, traceManager.getCurrentThread())); - - TestTargetStack stack = mb.testThread1.addStack(); - // Note, push simply moves the data, the new frame still has the higher index - TestTargetStackFrameHasRegisterBank frame0 = stack.pushFrameHasBank(mb.addr(0x00400000)); - TestTargetStackFrameHasRegisterBank frame1 = stack.pushFrameHasBank(mb.addr(0x00400100)); - waitForPass(() -> { - TraceStack s = trace.getStackManager().getLatestStack(thread1, recorder.getSnap()); - assertNotNull(s); - assertEquals(2, s.getDepth()); - }); + TraceObject frame0 = tb.obj("Processes[1].Threads[1].Stack[0]"); + TraceObject frame1 = tb.obj("Processes[1].Threads[1].Stack[1]"); // Starting with 0 results in no change in coordinates, so ignored - waitOn(mb.testModel.session.requestFocus(frame1)); + rmiCx.synthActivate(frame1); + waitForSwing(); + assertEquals(1, traceManager.getCurrentFrame()); - waitForPass(() -> assertEquals(1, traceManager.getCurrentFrame())); + rmiCx.synthActivate(frame0); + waitForSwing(); + assertEquals(0, traceManager.getCurrentFrame()); - waitOn(mb.testModel.session.requestFocus(frame0)); - - waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame())); + // Focus should never be reflected back to target + assertTrue(activationMethodsQueuesEmpty()); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/pcode/exec/TraceRmiPcodeExecTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/pcode/exec/TraceRmiPcodeExecTest.java new file mode 100644 index 0000000000..372768610c --- /dev/null +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/pcode/exec/TraceRmiPcodeExecTest.java @@ -0,0 +1,157 @@ +/* ### + * 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.assertEquals; + +import java.math.BigInteger; +import java.util.concurrent.CompletableFuture; + +import org.junit.Test; + +import db.Transaction; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.debug.api.target.Target; +import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.stack.TraceObjectStackFrame; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.schedule.TraceSchedule; + +public class TraceRmiPcodeExecTest extends AbstractGhidraHeadedDebuggerIntegrationTest { + + Target target; + TraceObjectThread thread; + SleighLanguage language; + + protected void setupExecTest() throws Throwable { + createRmiConnection(); + addRegisterMethods(); + createTrace(); + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); + tb.createObjectsProcessAndThreads(); + thread = tb.obj("Processes[1].Threads[1]").queryInterface(TraceObjectThread.class); + tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 1); + } + target = rmiCx.publishTarget(tool, tb.trace); + language = (SleighLanguage) tb.trace.getBaseLanguage(); + } + + @Test + public void testExecutorEval() throws Throwable { + setupExecTest(); + + PcodeExpression expr = SleighProgramCompiler.compileExpression(language, "r0 + r1"); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, + DebuggerCoordinates.NOWHERE.target(target).thread(thread)); + + CompletableFuture futResult = + CompletableFuture.supplyAsync(() -> expr.evaluate(executor)); + + long snap = target.getSnap(); + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> { + try (Transaction tx = tb.startTransaction()) { + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(snap, new RegisterValue(tb.reg("r0"), new BigInteger("5"))); + regs.setValue(snap, new RegisterValue(tb.reg("r1"), new BigInteger("6"))); + } + return null; + }); + + byte[] result = waitOn(futResult); + assertEquals(new BigInteger("11"), + executor.getArithmetic().toBigInteger(result, Purpose.INSPECT)); + } + + @Test + public void testExecutorEvalInScratchModeReadsLive() throws Throwable { + setupExecTest(); + + TraceSchedule oneTick = TraceSchedule.snap(target.getSnap()).steppedForward(thread, 1); + try (Transaction tx = tb.trace.openTransaction("Scratch")) { + TraceSnapshot scratch = tb.trace.getTimeManager().getSnapshot(Long.MIN_VALUE, true); + scratch.setSchedule(oneTick); + scratch.setDescription("Faked"); + } + + PcodeExpression expr = SleighProgramCompiler.compileExpression(language, "r0 + r1"); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, + DebuggerCoordinates.NOWHERE.target(target).thread(thread).time(oneTick)); + + CompletableFuture futResult = + CompletableFuture.supplyAsync(() -> expr.evaluate(executor)); + + long snap = target.getSnap(); // should be 0, not the scratch + TraceObject objRegs = tb.obj("Processes[1].Threads[1].Stack[0].Registers"); + handleReadRegsInvocation(objRegs, () -> { + try (Transaction tx = tb.startTransaction()) { + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(snap, new RegisterValue(tb.reg("r0"), new BigInteger("5"))); + regs.setValue(snap, new RegisterValue(tb.reg("r1"), new BigInteger("6"))); + } + return null; + }); + + /** + * TODO: This second handle should not be necessary. The KNOWN ought to carry into the + * scratch snapshot. + */ + handleReadRegsInvocation(objRegs, () -> null); + + byte[] result = waitOn(futResult); + assertEquals(new BigInteger("11"), + executor.getArithmetic().toBigInteger(result, Purpose.INSPECT)); + } + + @Test + public void testExecutorWrite() throws Throwable { + setupExecTest(); + + PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", + "r2 = r0 + r1;", PcodeUseropLibrary.NIL); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, + DebuggerCoordinates.NOWHERE.target(target).thread(thread)); + + CompletableFuture futResult = + CompletableFuture.runAsync(() -> executor.execute(prog, PcodeUseropLibrary.nil())); + + long snap = target.getSnap(); + handleReadRegsInvocation(tb.obj("Processes[1].Threads[1].Stack[0].Registers"), () -> { + try (Transaction tx = tb.startTransaction()) { + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(snap, new RegisterValue(tb.reg("r0"), new BigInteger("5"))); + regs.setValue(snap, new RegisterValue(tb.reg("r1"), new BigInteger("6"))); + } + return null; + }); + + handleWriteRegInvocation( + tb.obj("Processes[1].Threads[1].Stack[0]").queryInterface(TraceObjectStackFrame.class), + "r2", 11); + + waitOn(futResult); + } +}