From 5813548a847a01f2972e1798f563e41f94a974f4 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:43:47 -0500 Subject: [PATCH] GP-1978: Port tests to TraceRmi and delete more stuff. --- .../app/services/DebuggerModelService.java | 372 -------- .../services/DebuggerTraceManagerService.java | 8 +- .../ghidra/debug/api/control/ControlMode.java | 20 +- .../debug/api/model/DebuggerMappingOffer.java | 125 --- .../api/model/DebuggerMappingOpinion.java | 131 --- .../debug/api/model/DebuggerMemoryMapper.java | 68 -- .../api/model/DebuggerProgramLaunchOffer.java | 226 ----- .../api/model/DebuggerRegisterMapper.java | 194 ----- .../api/model/DebuggerTargetTraceMapper.java | 31 - .../ghidra/debug/api/model/TraceRecorder.java | 786 ----------------- .../api/model/TraceRecorderListener.java | 70 -- .../flatapi/FlatDebuggerRecorderAPI.java | 657 -------------- .../launcher/AbstractTraceRmiLaunchOffer.java | 2 +- .../tracermi/AbstractTraceRmiConnection.java | 72 ++ .../service/tracermi/TraceRmiHandler.java | 49 +- .../service/tracermi/TraceRmiTarget.java | 14 +- .../tracermi/TestTraceRmiConnection.java | 53 +- .../MonitorModelEventsScript.java | 60 -- .../RefreshRegistersScript.java | 118 +-- .../images/DebuggerBreakpointMarkerPlugin.png | Bin 74274 -> 73085 bytes .../DebuggerFunctionGraphBreakpointMargin.png | Bin 54692 -> 53680 bytes .../core/debug/gui/DebuggerResources.java | 19 - .../gui/action/DebuggerReadsMemoryTrait.java | 39 +- .../gui/listing/DebuggerListingProvider.java | 6 + .../memory/DebuggerMemoryBytesProvider.java | 6 + .../gui/watch/DebuggerWatchesProvider.java | 41 +- .../core/debug/gui/watch/DefaultWatchRow.java | 402 +++++---- .../mapping/DebuggerPlatformOpinion.java | 1 - .../mapping/DefaultDebuggerMemoryMapper.java | 84 -- .../DefaultDebuggerRegisterMapper.java | 162 ---- .../DefaultDebuggerTargetTraceMapper.java | 104 --- .../ObjectBasedDebuggerMemoryMapper.java | 100 --- .../ObjectBasedDebuggerTargetTraceMapper.java | 55 -- ...ebuggerLogicalBreakpointServicePlugin.java | 8 +- .../service/model/DebuggerConnectDialog.java | 498 ----------- .../model/DebuggerModelServiceInternal.java | 123 --- .../model/DebuggerModelServicePlugin.java | 693 --------------- .../DebuggerModelServiceProxyPlugin.java | 724 ---------------- .../DebuggerSelectMappingOfferDialog.java | 343 -------- .../model/DefaultBreakpointRecorder.java | 219 ----- .../service/model/DefaultMemoryRecorder.java | 162 ---- .../service/model/DefaultModuleRecorder.java | 168 ---- .../service/model/DefaultProcessRecorder.java | 94 -- .../service/model/DefaultStackRecorder.java | 176 ---- .../service/model/DefaultThreadRecorder.java | 592 ------------- .../service/model/DefaultTimeRecorder.java | 66 -- .../service/model/DefaultTraceRecorder.java | 648 -------------- .../model/PermanentTransactionExecutor.java | 101 --- .../RecorderBreakpointLocationResolver.java | 108 --- .../model/RecorderPermanentTransaction.java | 42 - .../service/model/RecorderSimpleMemory.java | 142 --- .../service/model/RecorderThreadMap.java | 73 -- .../service/model/TraceEventListener.java | 269 ------ .../service/model/TraceObjectListener.java | 279 ------ .../service/model/TraceObjectManager.java | 721 ---------------- .../service/model/TraceRecorderTarget.java | 628 -------------- .../interfaces/AbstractRecorderMemory.java | 51 -- .../interfaces/AbstractTraceRecorder.java | 32 - .../interfaces/ManagedBreakpointRecorder.java | 59 -- .../interfaces/ManagedMemoryRecorder.java | 33 - .../interfaces/ManagedModuleRecorder.java | 36 - .../interfaces/ManagedProcessRecorder.java | 23 - .../interfaces/ManagedStackRecorder.java | 36 - .../interfaces/ManagedThreadRecorder.java | 55 -- .../launch/DebuggerProgramLaunchOpinion.java | 30 - .../model/record/DataTypeRecorder.java | 91 -- .../record/EmptyDebuggerRegisterMapper.java | 60 -- .../service/model/record/MemoryRecorder.java | 146 ---- .../record/ObjectBasedTraceRecorder.java | 817 ------------------ .../service/model/record/ObjectRecorder.java | 328 ------- .../service/model/record/RecorderUtils.java | 76 -- .../service/model/record/SymbolRecorder.java | 140 --- .../service/model/record/TimeRecorder.java | 67 -- .../DebuggerTraceManagerServicePlugin.java | 16 +- .../DebuggerCopyActionsPluginScreenShots.java | 6 +- .../gui/AbstractGhidraHeadedDebuggerTest.java | 173 +--- .../DebuggerBreakpointsProviderTest.java | 790 ----------------- .../DebuggerRegionsProviderLegacyTest.java | 397 --------- .../DebuggerModulesProviderLegacyTest.java | 656 -------------- .../DebuggerStackProviderLegacyTest.java | 503 ----------- .../DebuggerThreadsProviderLegacyTest.java | 295 ------- .../service/{control => }/MockTarget.java | 6 +- .../core/debug/stack/StackUnwinderTest.java | 8 +- .../exec/TraceRecorderPcodeExecTest.java | 198 ----- .../java/ghidra/async/AsyncPairingQueue.java | 10 +- .../dbg/model/TestDebuggerModelBuilder.java | 5 +- ...essesReadTracePcodeExecutorStatePiece.java | 7 +- .../trace/database/ToyDBTraceBuilder.java | 4 +- .../exec/AddressesReadPcodeArithmetic.java | 8 +- ...ggerBreakpointMarkerPluginScreenShots.java | 182 ++-- .../DebuggerBreakpointsPluginScreenShots.java | 2 +- ...ctGhidraHeadedDebuggerIntegrationTest.java | 168 +++- ...ObjectRecorderBreakpointsProviderTest.java | 116 --- ...gerRecorderBreakpointMarkerPluginTest.java | 82 -- ...buggerRecorderBreakpointsProviderTest.java | 98 --- .../control/DebuggerControlPluginTest.java | 209 +---- .../DebuggerMethodActionsPluginTest.java | 255 +++--- .../DebuggerCopyActionsPluginTest.java | 76 +- .../listing/DebuggerListingProviderTest.java | 258 +++--- .../DebuggerMemoryBytesProviderTest.java | 408 ++++----- ...gerGuestRecorderRegistersProviderTest.java | 82 -- ...DebuggerRecorderRegistersProviderTest.java | 105 --- .../DebuggerRmiRegistersProviderTest.java | 30 +- .../watch/DebuggerWatchesProviderTest.java | 211 +++-- ...tRecorderLogicalBreakpointServiceTest.java | 115 --- ...rRecorderLogicalBreakpointServiceTest.java | 219 ----- .../DebuggerControlServiceGuestTest.java | 38 +- .../control/DebuggerControlServiceTest.java | 180 ++-- .../DebuggerTraceManagerServiceTest.java | 186 ++-- .../pcode/exec/TraceRmiPcodeExecTest.java | 157 ++++ 110 files changed, 1762 insertions(+), 17529 deletions(-) delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerModelService.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOffer.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMappingOpinion.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerMemoryMapper.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerProgramLaunchOffer.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerRegisterMapper.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/DebuggerTargetTraceMapper.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorder.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/model/TraceRecorderListener.java delete mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerRecorderAPI.java create mode 100644 Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/AbstractTraceRmiConnection.java delete mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/MonitorModelEventsScript.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerRegisterMapper.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerTargetTraceMapper.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerMemoryMapper.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/ObjectBasedDebuggerTargetTraceMapper.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceInternal.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultModuleRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderBreakpointLocationResolver.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderPermanentTransaction.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderSimpleMemory.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderThreadMap.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectListener.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractRecorderMemory.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/AbstractTraceRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedBreakpointRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedMemoryRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedModuleRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedProcessRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedStackRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/interfaces/ManagedThreadRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/DebuggerProgramLaunchOpinion.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/DataTypeRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/EmptyDebuggerRegisterMapper.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/SymbolRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/TimeRecorder.java delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderLegacyTest.java delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java rename Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/{control => }/MockTarget.java (99%) delete mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerObjectRecorderBreakpointsProviderTest.java delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointMarkerPluginTest.java delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerRecorderBreakpointsProviderTest.java rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java (92%) rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java (90%) rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java (81%) delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerGuestRecorderRegistersProviderTest.java delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRecorderRegistersProviderTest.java rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java (81%) delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java delete mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceGuestTest.java (58%) rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java (77%) rename Ghidra/{Debug/Debugger => Test/DebuggerIntegrationTest}/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java (72%) create mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/pcode/exec/TraceRmiPcodeExecTest.java 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 66932c2199462f333d34fba40002324c73908f8d..1f87cffedd6c4235eec6a3aaa58a32fb9e6d4a1b 100644 GIT binary patch delta 53219 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 delta 54424 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 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 ec934cdd0ac8e6d06d8486364b07f51f8f5a36b5..3a95f0545d380078cf7f1fd754424f90b314b428 100644 GIT binary patch literal 53680 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 z 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 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); + } +}