From d95200b1667287fd999493b01c3718f9ea3fece8 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:44:42 -0400 Subject: [PATCH] GP-4713: Launch mapping uses AutoMapSpec. Fix StaticMappingService. --- .../services/DebuggerAutoMappingService.java | 42 ++++ .../ghidra/debug/api}/action/AutoMapSpec.java | 55 ++++-- .../debug/api/action/InstanceUtils.java | 41 ++++ .../launcher/AbstractTraceRmiLaunchOffer.java | 56 ++++-- .../debug/gui/action/AutoReadMemorySpec.java | 8 +- .../debug/gui/action/ByModuleAutoMapSpec.java | 46 ++++- .../debug/gui/action/ByRegionAutoMapSpec.java | 46 ++++- .../gui/action/BySectionAutoMapSpec.java | 42 +++- .../debug/gui/action/NoneAutoMapSpec.java | 40 +++- .../debug/gui/action/OneToOneAutoMapSpec.java | 52 +++-- .../gui/modules/DebuggerModulesPlugin.java | 42 ++-- .../gui/modules/DebuggerModulesProvider.java | 183 ++++++++++++------ .../DebuggerStaticMappingServicePlugin.java | 120 ++++++++++-- .../core/debug/utils/MiscellaneousUtils.java | 26 +-- .../DebuggerModulesProviderLegacyTest.java | 6 +- .../database/memory/DBTraceMemorySpace.java | 4 +- .../database/target/DBTraceObjectManager.java | 7 +- 17 files changed, 623 insertions(+), 193 deletions(-) create mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerAutoMappingService.java rename Ghidra/Debug/{Debugger/src/main/java/ghidra/app/plugin/core/debug/gui => Debugger-api/src/main/java/ghidra/debug/api}/action/AutoMapSpec.java (70%) create mode 100644 Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/InstanceUtils.java diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerAutoMappingService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerAutoMappingService.java new file mode 100644 index 0000000000..112f00b3a5 --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerAutoMappingService.java @@ -0,0 +1,42 @@ +/* ### + * 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 ghidra.debug.api.action.AutoMapSpec; +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.trace.model.Trace; + +/** + * The service to query auto-map settings + */ +@ServiceInfo(defaultProviderName = "ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesPlugin") +public interface DebuggerAutoMappingService { + /** + * Get the auto-map setting currently active in the Modules provider + * + * @return the current setting + */ + AutoMapSpec getAutoMapSpec(); + + /** + * Get the current auto-map setting for the given trace + * + * @param trace the trace + * @return the auto-map setting for the trace, or the setting in the Modules provider, if the + * trace does not have its own setting. + */ + AutoMapSpec getAutoMapSpec(Trace trace); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/AutoMapSpec.java similarity index 70% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java rename to Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/AutoMapSpec.java index 4aaeb15c52..663d21669b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/AutoMapSpec.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.gui.action; +package ghidra.debug.api.action; import java.util.*; @@ -21,15 +21,15 @@ import javax.swing.Icon; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; @@ -50,7 +50,7 @@ public interface AutoMapSpec extends ExtensionPoint { } private synchronized void classesChanged(ChangeEvent evt) { - MiscellaneousUtils.collectUniqueInstances(AutoMapSpec.class, specsByName, + InstanceUtils.collectUniqueInstances(AutoMapSpec.class, specsByName, AutoMapSpec::getConfigName); } } @@ -87,12 +87,18 @@ public interface AutoMapSpec extends ExtensionPoint { String getMenuName(); - default Icon getMenuIcon() { - return DebuggerResources.ICON_CONFIG; - } + Icon getMenuIcon(); Collection> getChangeTypes(); + boolean objectHasType(TraceObjectValue value); + + String getInfoForObjects(Trace trace); + + default boolean hasTask() { + return true; + } + default String getTaskTitle() { return getMenuName(); } @@ -119,6 +125,33 @@ public interface AutoMapSpec extends ExtensionPoint { tool.executeBackgroundCommand(cmd, trace); } - void performMapping(DebuggerStaticMappingService mappingService, Trace trace, - ProgramManager programManager, TaskMonitor monitor) throws CancelledException; + List programs(ProgramManager programManager); + + /** + * Perform the actual mapping + * + * @param mappingService the mapping service + * @param trace the trace + * @param programs the programs to consider + * @param monitor a task monitor + * @return true if any mappings were added + * @throws CancelledException if the task monitor cancelled the task + */ + boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + List programs, TaskMonitor monitor) throws CancelledException; + + /** + * Perform the actual mapping + * + * @param mappingService the mapping service + * @param trace the trace + * @param programManager the program manager + * @param monitor a task monitor + * @return true if any mappings were added + * @throws CancelledException if the task monitor cancelled the task + */ + default boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + ProgramManager programManager, TaskMonitor monitor) throws CancelledException { + return performMapping(mappingService, trace, programs(programManager), monitor); + } } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/InstanceUtils.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/InstanceUtils.java new file mode 100644 index 0000000000..4b1ff2a2ac --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/action/InstanceUtils.java @@ -0,0 +1,41 @@ +/* ### + * 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.action; + +import java.util.Map; +import java.util.function.Function; + +import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; + +public class InstanceUtils { + public static void collectUniqueInstances(Class cls, Map map, + Function keyFunc) { + // This is wasteful. Existing instances will be re-instantiated and thrown away + for (T t : ClassSearcher.getInstances(cls)) { + String key = keyFunc.apply(t); + T exists = map.get(key); + if (exists != null) { + if (exists.getClass().equals(t.getClass())) { + continue; + } + Msg.error(LocationTrackingSpec.class, + cls.getSimpleName() + " conflict over key: " + key); + } + map.put(key, t); + } + } +} 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 97b6fab7ac..a8e113c610 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 @@ -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. @@ -38,8 +38,9 @@ import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.async.AsyncUtils; import ghidra.dbg.target.TargetMethod.ParameterDescription; import ghidra.dbg.util.ShellUtils; -import ghidra.debug.api.modules.*; -import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; +import ghidra.debug.api.action.AutoMapSpec; +import ghidra.debug.api.modules.DebuggerMissingProgramActionContext; +import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; import ghidra.debug.api.tracermi.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.*; @@ -50,7 +51,6 @@ import ghidra.program.util.ProgramLocation; import ghidra.pty.*; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceLocation; -import ghidra.trace.model.modules.TraceModule; import ghidra.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; @@ -185,16 +185,31 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer return result; } - protected Collection invokeMapper(TaskMonitor monitor, - DebuggerStaticMappingService mappingService, Trace trace) throws CancelledException { + protected boolean invokeMapper(TaskMonitor monitor, DebuggerStaticMappingService mappingService, + TraceRmiConnection connection, Trace trace, AutoMapSpec spec) + throws CancelledException { if (program == null) { - return List.of(); + return false; } - Map map = mappingService - .proposeModuleMaps(trace.getModuleManager().getAllModules(), List.of(program)); - Collection proposal = MapProposal.flatten(map.values()); - mappingService.addModuleMappings(proposal, monitor, true); - return proposal; + + if (spec.performMapping(mappingService, trace, List.of(program), monitor)) { + return true; + } + + try { + mappingService.changesSettled().get(1, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + // Whatever, just check for the mapping + } + + Address probeAddress = getMappingProbeAddress(); + if (probeAddress == null) { + return true; // Probably shouldn't happen, but if it does, say "success" + } + ProgramLocation probe = new ProgramLocation(program, probeAddress); + long snap = connection.getLastSnapshot(trace); + return mappingService.getOpenMappedLocation(trace, probe, snap) != null; } protected SaveState saveLauncherArgsToState(Map args, @@ -543,7 +558,9 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer } protected void initializeMonitor(TaskMonitor monitor) { - if (requiresImage()) { + DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class); + AutoMapSpec spec = auto.getAutoMapSpec(); + if (requiresImage() && spec.hasTask()) { monitor.setMaximum(6); } else { @@ -557,6 +574,11 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer if (!requiresImage()) { return; } + DebuggerAutoMappingService auto = tool.getService(DebuggerAutoMappingService.class); + AutoMapSpec spec = auto.getAutoMapSpec(trace); + if (!spec.hasTask()) { + return; + } DebuggerStaticMappingService mappingService = tool.getService(DebuggerStaticMappingService.class); monitor.setMessage("Waiting for module mapping"); @@ -567,14 +589,14 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer catch (TimeoutException e) { monitor.setMessage( "Timed out waiting for module mapping. Invoking the mapper."); - Collection mapped; + boolean mapped; try { - mapped = invokeMapper(monitor, mappingService, trace); + mapped = invokeMapper(monitor, mappingService, connection, trace, spec); } catch (CancelledException ce) { throw new CancellationException(e.getMessage()); } - if (mapped.isEmpty()) { + if (!mapped) { throw new NoStaticMappingException( "The resulting target process has no mapping to the static image."); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoReadMemorySpec.java index 8a7ef06588..e56606fb5f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoReadMemorySpec.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,7 +25,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import ghidra.app.plugin.core.debug.gui.control.TargetActionTask; -import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; +import ghidra.debug.api.action.InstanceUtils; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec; @@ -49,7 +49,7 @@ public interface AutoReadMemorySpec extends ExtensionPoint { } private synchronized void classesChanged(ChangeEvent evt) { - MiscellaneousUtils.collectUniqueInstances(AutoReadMemorySpec.class, specsByName, + InstanceUtils.collectUniqueInstances(AutoReadMemorySpec.class, specsByName, AutoReadMemorySpec::getConfigName); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java index 8489a7984a..eeae1b1771 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByModuleAutoMapSpec.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,14 +16,22 @@ package ghidra.app.plugin.core.debug.gui.action; import java.util.*; +import java.util.stream.Collectors; +import javax.swing.Icon; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; +import ghidra.debug.api.action.AutoMapSpec; import ghidra.debug.api.modules.MapProposal; import ghidra.debug.api.modules.ModuleMapProposal; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceObjectMemoryRegion; +import ghidra.trace.model.modules.TraceObjectModule; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.trace.util.TraceEvents; import ghidra.util.exception.CancelledException; @@ -42,6 +50,11 @@ public class ByModuleAutoMapSpec implements AutoMapSpec { return "Auto-Map by Module"; } + @Override + public Icon getMenuIcon() { + return DebuggerResources.ICON_CONFIG; + } + @Override public Collection> getChangeTypes() { return List.of( @@ -50,13 +63,36 @@ public class ByModuleAutoMapSpec implements AutoMapSpec { } @Override - public void performMapping(DebuggerStaticMappingService mappingService, Trace trace, - ProgramManager programManager, TaskMonitor monitor) throws CancelledException { - List programs = Arrays.asList(programManager.getAllOpenPrograms()); + public boolean objectHasType(TraceObjectValue value) { + return value.getParent().queryInterface(TraceObjectModule.class) != null || + value.getParent().queryInterface(TraceObjectMemoryRegion.class) != null; + } + + @Override + public String getInfoForObjects(Trace trace) { + String modPart = trace.getModuleManager() + .getAllModules() + .stream() + .map(m -> m.getName() + ":" + m.getBase()) + .sorted() + .collect(Collectors.joining(",")); + String regPart = ByRegionAutoMapSpec.getInfoForRegions(trace); + return modPart + ";" + regPart; + } + + @Override + public List programs(ProgramManager programManager) { + return Arrays.asList(programManager.getAllOpenPrograms()); + } + + @Override + public boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + List programs, TaskMonitor monitor) throws CancelledException { Map maps = mappingService .proposeModuleMaps(trace.getModuleManager().getAllModules(), programs); Collection entries = MapProposal.flatten(maps.values()); entries = MapProposal.removeOverlapping(entries); mappingService.addModuleMappings(entries, monitor, false); + return !entries.isEmpty(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByRegionAutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByRegionAutoMapSpec.java index 5537d26492..5703a508b3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByRegionAutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/ByRegionAutoMapSpec.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,14 +16,21 @@ package ghidra.app.plugin.core.debug.gui.action; import java.util.*; +import java.util.stream.Collectors; +import javax.swing.Icon; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; +import ghidra.debug.api.action.AutoMapSpec; import ghidra.debug.api.modules.MapProposal; import ghidra.debug.api.modules.RegionMapProposal; import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry; import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceObjectMemoryRegion; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.trace.util.TraceEvents; import ghidra.util.exception.CancelledException; @@ -42,19 +49,48 @@ public class ByRegionAutoMapSpec implements AutoMapSpec { return "Auto-Map by Region"; } + @Override + public Icon getMenuIcon() { + return DebuggerResources.ICON_CONFIG; + } + @Override public Collection> getChangeTypes() { return List.of(TraceEvents.REGION_ADDED); } @Override - public void performMapping(DebuggerStaticMappingService mappingService, Trace trace, - ProgramManager programManager, TaskMonitor monitor) throws CancelledException { - List programs = Arrays.asList(programManager.getAllOpenPrograms()); + public boolean objectHasType(TraceObjectValue value) { + return value.getParent().queryInterface(TraceObjectMemoryRegion.class) != null; + } + + static String getInfoForRegions(Trace trace) { + return trace.getMemoryManager() + .getAllRegions() + .stream() + .map(r -> r.getName() + ":" + r.getMinAddress()) + .sorted() + .collect(Collectors.joining(",")); + } + + @Override + public String getInfoForObjects(Trace trace) { + return getInfoForRegions(trace); + } + + @Override + public List programs(ProgramManager programManager) { + return Arrays.asList(programManager.getAllOpenPrograms()); + } + + @Override + public boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + List programs, TaskMonitor monitor) throws CancelledException { Map maps = mappingService .proposeRegionMaps(trace.getMemoryManager().getAllRegions(), programs); Collection entries = MapProposal.flatten(maps.values()); entries = MapProposal.removeOverlapping(entries); mappingService.addRegionMappings(entries, monitor, false); + return !entries.isEmpty(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/BySectionAutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/BySectionAutoMapSpec.java index b06e192f25..dfa3f28261 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/BySectionAutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/BySectionAutoMapSpec.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,14 +16,21 @@ package ghidra.app.plugin.core.debug.gui.action; import java.util.*; +import java.util.stream.Collectors; +import javax.swing.Icon; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; +import ghidra.debug.api.action.AutoMapSpec; import ghidra.debug.api.modules.MapProposal; import ghidra.debug.api.modules.SectionMapProposal; import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry; import ghidra.program.model.listing.Program; +import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.trace.util.TraceEvents; import ghidra.util.exception.CancelledException; @@ -42,19 +49,44 @@ public class BySectionAutoMapSpec implements AutoMapSpec { return "Auto-Map by Section"; } + @Override + public Icon getMenuIcon() { + return DebuggerResources.ICON_CONFIG; + } + @Override public Collection> getChangeTypes() { return List.of(TraceEvents.SECTION_ADDED); } @Override - public void performMapping(DebuggerStaticMappingService mappingService, Trace trace, - ProgramManager programManager, TaskMonitor monitor) throws CancelledException { - List programs = Arrays.asList(programManager.getAllOpenPrograms()); + public boolean objectHasType(TraceObjectValue value) { + return value.getParent().queryInterface(TraceObjectSection.class) != null; + } + + @Override + public String getInfoForObjects(Trace trace) { + return trace.getModuleManager() + .getAllSections() + .stream() + .map(s -> s.getName() + ":" + s.getStart()) + .sorted() + .collect(Collectors.joining(",")); + } + + @Override + public List programs(ProgramManager programManager) { + return Arrays.asList(programManager.getAllOpenPrograms()); + } + + @Override + public boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + List programs, TaskMonitor monitor) throws CancelledException { Map maps = mappingService .proposeSectionMaps(trace.getModuleManager().getAllModules(), programs); Collection entries = MapProposal.flatten(maps.values()); entries = MapProposal.removeOverlapping(entries); mappingService.addSectionMappings(entries, monitor, false); + return !entries.isEmpty(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoMapSpec.java index 2408449f4e..dfcebbfeba 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoMapSpec.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. @@ -18,10 +18,16 @@ package ghidra.app.plugin.core.debug.gui.action; import java.util.Collection; import java.util.List; +import javax.swing.Icon; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; +import ghidra.debug.api.action.AutoMapSpec; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -39,18 +45,44 @@ public class NoneAutoMapSpec implements AutoMapSpec { return "Do Not Auto-Map"; } + @Override + public Icon getMenuIcon() { + return DebuggerResources.ICON_CONFIG; + } + @Override public Collection> getChangeTypes() { return List.of(); } + @Override + public boolean objectHasType(TraceObjectValue value) { + return false; + } + + @Override + public String getInfoForObjects(Trace trace) { + return ""; + } + + @Override + public boolean hasTask() { + return false; + } + @Override public void runTask(PluginTool tool, Trace trace) { // Don't bother launching a task that does nothing } @Override - public void performMapping(DebuggerStaticMappingService mappingService, Trace trace, - ProgramManager programManager, TaskMonitor monitor) throws CancelledException { + public List programs(ProgramManager programManager) { + return List.of(); + } + + @Override + public boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + List programs, TaskMonitor monitor) throws CancelledException { + return false; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/OneToOneAutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/OneToOneAutoMapSpec.java index 636472b38d..cb5be8234d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/OneToOneAutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/OneToOneAutoMapSpec.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. @@ -18,12 +18,17 @@ package ghidra.app.plugin.core.debug.gui.action; import java.util.Collection; import java.util.List; +import javax.swing.Icon; + +import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; +import ghidra.debug.api.action.AutoMapSpec; import ghidra.program.model.listing.Program; import ghidra.trace.model.Lifespan; import ghidra.trace.model.Trace; import ghidra.trace.model.modules.TraceConflictedMappingException; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -41,24 +46,49 @@ public class OneToOneAutoMapSpec implements AutoMapSpec { return "Auto-Map Identically (1-to-1)"; } + @Override + public Icon getMenuIcon() { + return DebuggerResources.ICON_CONFIG; + } + @Override public Collection> getChangeTypes() { return List.of(); } @Override - public void performMapping(DebuggerStaticMappingService mappingService, Trace trace, - ProgramManager programManager, TaskMonitor monitor) throws CancelledException { + public boolean objectHasType(TraceObjectValue value) { + return false; + } + + @Override + public String getInfoForObjects(Trace trace) { + return ""; + } + + @Override + public List programs(ProgramManager programManager) { Program program = programManager.getCurrentProgram(); if (program == null) { - return; + return List.of(); } - try { - mappingService.addIdentityMapping(trace, program, - Lifespan.nowOn(trace.getProgramView().getSnap()), false); - } - catch (TraceConflictedMappingException e) { - // aww well + return List.of(program); + } + + @Override + public boolean performMapping(DebuggerStaticMappingService mappingService, Trace trace, + List programs, TaskMonitor monitor) throws CancelledException { + boolean result = false; + for (Program program : programs) { + try { + mappingService.addIdentityMapping(trace, program, + Lifespan.nowOn(trace.getProgramView().getSnap()), false); + result = true; + } + catch (TraceConflictedMappingException e) { + // aww well + } } + return result; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java index 1c741db78c..6d9a235c7a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.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,11 +19,11 @@ import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; -import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; -import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; +import ghidra.app.plugin.core.debug.event.*; import ghidra.app.services.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoServiceProvided; import ghidra.framework.plugintool.util.PluginStatus; @PluginInfo( @@ -37,6 +37,7 @@ import ghidra.framework.plugintool.util.PluginStatus; ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class, + TraceOpenedPluginEvent.class, TraceActivatedPluginEvent.class, TraceClosedPluginEvent.class, }, @@ -44,8 +45,13 @@ import ghidra.framework.plugintool.util.PluginStatus; DebuggerStaticMappingService.class, DebuggerTraceManagerService.class, ProgramManager.class, + }, + servicesProvided = { + DebuggerAutoMappingService.class, }) public class DebuggerModulesPlugin extends AbstractDebuggerPlugin { + + @AutoServiceProvided(iface = DebuggerAutoMappingService.class) protected DebuggerModulesProvider provider; public DebuggerModulesPlugin(PluginTool tool) { @@ -68,23 +74,17 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin { @Override public void processEvent(PluginEvent event) { super.processEvent(event); - if (event instanceof ProgramOpenedPluginEvent ev) { - provider.programOpened(ev.getProgram()); - } - else if (event instanceof ProgramActivatedPluginEvent ev) { - provider.setProgram(ev.getActiveProgram()); - } - else if (event instanceof ProgramLocationPluginEvent ev) { - provider.setLocation(ev.getLocation()); - } - else if (event instanceof ProgramClosedPluginEvent ev) { - provider.programClosed(ev.getProgram()); - } - else if (event instanceof TraceActivatedPluginEvent ev) { - provider.coordinatesActivated(ev.getActiveCoordinates()); - } - else if (event instanceof TraceClosedPluginEvent ev) { - provider.traceClosed(ev.getTrace()); + switch (event) { + case ProgramOpenedPluginEvent ev -> provider.programOpened(ev.getProgram()); + case ProgramActivatedPluginEvent ev -> provider.setProgram(ev.getActiveProgram()); + case ProgramLocationPluginEvent ev -> provider.setLocation(ev.getLocation()); + case ProgramClosedPluginEvent ev -> provider.programClosed(ev.getProgram()); + case TraceOpenedPluginEvent ev -> provider.traceOpened(ev.getTrace()); + case TraceActivatedPluginEvent ev -> provider + .coordinatesActivated(ev.getActiveCoordinates()); + case TraceClosedPluginEvent ev -> provider.traceClosed(ev.getTrace()); + default -> { + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index a2074f9bf1..491a1591c4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.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,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.modules; -import static ghidra.framework.main.DataTreeDialogType.*; +import static ghidra.framework.main.DataTreeDialogType.OPEN; import java.awt.event.MouseEvent; import java.io.File; @@ -40,18 +40,18 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; -import ghidra.app.plugin.core.debug.gui.action.AutoMapSpec; -import ghidra.app.plugin.core.debug.gui.action.AutoMapSpec.AutoMapSpecConfigFieldCodec; import ghidra.app.plugin.core.debug.gui.action.ByModuleAutoMapSpec; -import ghidra.app.plugin.core.debug.service.model.TraceRecorderTarget; import ghidra.app.plugin.core.debug.service.modules.MapModulesBackgroundCommand; import ghidra.app.plugin.core.debug.service.modules.MapSectionsBackgroundCommand; import ghidra.app.services.*; +import ghidra.debug.api.action.AutoMapSpec; +import ghidra.debug.api.action.AutoMapSpec.AutoMapSpecConfigFieldCodec; import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.modules.*; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry; import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.framework.data.DomainObjectAdapterDB; import ghidra.framework.main.AppInfo; import ghidra.framework.main.DataTreeDialog; import ghidra.framework.model.*; @@ -68,11 +68,14 @@ import ghidra.program.util.ProgramSelection; import ghidra.trace.model.*; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.util.TraceEvent; import ghidra.trace.util.TraceEvents; import ghidra.util.*; -public class DebuggerModulesProvider extends ComponentProviderAdapter { +public class DebuggerModulesProvider extends ComponentProviderAdapter + implements DebuggerAutoMappingService { + protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = AutoConfigState.wireHandler(DebuggerModulesProvider.class, MethodHandles.lookup()); @@ -336,27 +339,82 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } } - protected class ForMappingTraceListener extends TraceDomainObjectListener { - public ForMappingTraceListener(AutoMapSpec spec) { + protected static class AutoMapState extends TraceDomainObjectListener + implements TransactionListener { + + private final PluginTool tool; + private final Trace trace; + private final AutoMapSpec spec; + private volatile boolean couldHaveChanged = true; + private volatile String infosLastTime = ""; + + public AutoMapState(PluginTool tool, Trace trace, AutoMapSpec spec) { + this.tool = tool; + this.trace = trace; + this.spec = spec; for (TraceEvent type : spec.getChangeTypes()) { listenFor(type, this::changed); } - // Delete this if/when TraceRecorderTarget is removed - listenFor(TraceEvents.BYTES_CHANGED, this::memoryChanged); + listenFor(TraceEvents.VALUE_CREATED, this::valueCreated); + listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored); + + trace.addListener(this); + trace.addTransactionListener(this); + } + + public void dispose() { + trace.removeListener(this); + trace.removeTransactionListener(this); } private void changed() { - cueAutoMap = true; + couldHaveChanged = true; } - private void memoryChanged(TraceAddressSnapRange range) { - if (range.getRange().getAddressSpace().isRegisterSpace()) { + private void valueCreated(TraceObjectValue value) { + couldHaveChanged = true; + } + + private void objectRestored(DomainObjectChangeRecord rec) { + couldHaveChanged = true; + } + + @Override + public void transactionStarted(DomainObjectAdapterDB domainObj, TransactionInfo tx) { + } + + @Override + public void transactionEnded(DomainObjectAdapterDB domainObj) { + checkAutoMap(); + } + + @Override + public void undoStackChanged(DomainObjectAdapterDB domainObj) { + } + + @Override + public void undoRedoOccurred(DomainObjectAdapterDB domainObj) { + } + + private void checkAutoMap() { + if (!couldHaveChanged) { return; } - if (current.getTarget() instanceof TraceRecorderTarget) { - doCuedAutoMap(); + couldHaveChanged = false; + String infosThisTime = spec.getInfoForObjects(trace); + if (Objects.equals(infosThisTime, infosLastTime)) { + return; } + infosLastTime = infosThisTime; + + spec.runTask(tool, trace); + } + + public void forceMap() { + couldHaveChanged = true; + infosLastTime = ""; + checkAutoMap(); } } @@ -534,8 +592,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { @AutoConfigStateField boolean filterSectionsByModules = false; - boolean cueAutoMap; - private ForMappingTraceListener forMappingListener; + private final Map autoMapStateByTrace = new WeakHashMap<>(); + private AutoMapState forMappingListener; DockingAction actionImportMissingModule; DockingAction actionMapMissingModule; @@ -612,6 +670,10 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } protected void dispose() { + for (AutoMapState state : autoMapStateByTrace.values()) { + state.dispose(); + } + if (consoleService != null) { removeResolutionActionMaybe(consoleService, actionImportMissingModule); removeResolutionActionMaybe(consoleService, actionMapMissingModule); @@ -885,12 +947,20 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } private void doSetAutoMapSpec(AutoMapSpec autoMapSpec) { - if (this.autoMapSpec == autoMapSpec) { + this.autoMapSpec = autoMapSpec; + + Trace trace = current.getTrace(); + if (trace == null) { return; } - removeOldTraceListener(); - this.autoMapSpec = autoMapSpec; - addNewTraceListener(); + AutoMapState state = autoMapStateByTrace.remove(trace); + if (state != null && state.spec.equals(autoMapSpec)) { + autoMapStateByTrace.put(trace, state); + } + else { + state.dispose(); + autoMapStateByTrace.put(trace, new AutoMapState(tool, trace, autoMapSpec)); + } } private void activatedImportMissingModule(DebuggerMissingModuleActionContext context) { @@ -1256,9 +1326,14 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } public void programOpened(Program program) { + AutoMapState mapState = autoMapStateByTrace.get(current.getTrace()); + // TODO: All open traces, or just the current one? + if (mapState == null) { + // Could be, e.g., current is NOWHERE + return; + } // TODO: Debounce this? - cueAutoMap = true; - doCuedAutoMap(); + mapState.forceMap(); } public void programClosed(Program program) { @@ -1268,43 +1343,38 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { cleanMissingProgramMessages(null, program); } + public void traceOpened(Trace trace) { + autoMapStateByTrace.computeIfAbsent(trace, t -> new AutoMapState(tool, trace, autoMapSpec)); + } + public void traceClosed(Trace trace) { + AutoMapState state = autoMapStateByTrace.remove(trace); + if (state != null) { + state.dispose(); + } cleanMissingProgramMessages(trace, null); } - protected void addNewTraceListener() { - if (current.getTrace() != null && autoMapSpec != null) { - forMappingListener = new ForMappingTraceListener(autoMapSpec); - current.getTrace().addListener(forMappingListener); - } - } - - protected void removeOldTraceListener() { - if (forMappingListener != null) { - if (current.getTrace() != null) { - current.getTrace().removeListener(forMappingListener); - } - forMappingListener = null; - } - } - public void coordinatesActivated(DebuggerCoordinates coordinates) { if (sameCoordinates(current, coordinates)) { current = coordinates; return; } - boolean changeTrace = current.getTrace() != coordinates.getTrace(); + Trace newTrace = coordinates.getTrace(); + boolean changeTrace = current.getTrace() != newTrace; if (changeTrace) { myActionContext = null; - removeOldTraceListener(); } current = coordinates; - if (changeTrace) { - addNewTraceListener(); + + AutoMapState amState = autoMapStateByTrace.get(newTrace); + if (amState != null) { + // Can't just set field directly. Want GUI update. + setAutoMapSpec(amState.spec); } - if (Trace.isLegacy(coordinates.getTrace())) { + if (Trace.isLegacy(newTrace)) { modulesPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE); sectionsPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE); legacyModulesPanel.coordinatesActivated(coordinates); @@ -1332,22 +1402,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } contextChanged(); - - if (coordinates.getTarget() instanceof TraceRecorderTarget) { - // HACK while TraceRecorderTarget is still around - cueAutoMap = true; - } - doCuedAutoMap(); - } - - private void doCuedAutoMap() { - if (cueAutoMap) { - cueAutoMap = false; - Trace trace = current.getTrace(); - if (autoMapSpec != null && trace != null) { - autoMapSpec.runTask(tool, trace); - } - } } public void setSelectedModules(Set sel) { @@ -1398,10 +1452,17 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { actionAutoMap.setCurrentActionStateByUserData(spec); } + @Override public AutoMapSpec getAutoMapSpec() { return autoMapSpec; } + @Override + public AutoMapSpec getAutoMapSpec(Trace trace) { + AutoMapState state = autoMapStateByTrace.get(trace); + return state == null ? autoMapSpec : state.spec; + } + public void writeConfigState(SaveState saveState) { CONFIG_STATE_HANDLER.writeConfigState(this, saveState); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index f4be7da5fb..1680bc1ede 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.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. @@ -253,12 +253,15 @@ public class DebuggerStaticMappingServicePlugin extends Plugin private void objectRestored() { synchronized (lock) { + Set fromClosure = collectAffectedByTrace(trace); var old = Map.copyOf(outbound); - outbound.clear(); - loadOutboundEntries(); // Also places/updates corresponding inbound entries + clearOutboundEntries(); + loadOutboundEntries(); // Also places corresponding inbound entries if (!old.equals(outbound)) { - // TODO: What about removed corresponding inbound entries? - doAffectedByTraceClosed(trace); + if (!fromClosure.isEmpty()) { + traceAffected(trace); + } + programsAffected(fromClosure); doAffectedByTraceOpened(trace); } } @@ -309,9 +312,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin destInfo.inbound.put(me, me.getStaticAddress()); } - protected void removeOutboundAndInboundEntries(MappingEntry me) { - outbound.remove(me.getTraceAddressSnapRange()); - + protected void removeInboundEntryFor(MappingEntry me) { InfoPerProgram destInfo = trackedProgramInfo.get(me.getStaticProgramURL()); if (destInfo == null) { return; // Not opened @@ -319,6 +320,11 @@ public class DebuggerStaticMappingServicePlugin extends Plugin destInfo.inbound.remove(me); } + protected void removeOutboundAndInboundEntries(MappingEntry me) { + outbound.remove(me.getTraceAddressSnapRange()); + removeInboundEntryFor(me); + } + protected void loadOutboundEntries() { TraceStaticMappingManager manager = trace.getStaticMappingManager(); for (TraceStaticMapping mapping : manager.getAllEntries()) { @@ -326,6 +332,13 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } } + protected void clearOutboundEntries() { + for (MappingEntry me : outbound.values()) { + removeInboundEntryFor(me); + } + outbound.clear(); + } + public boolean programOpened(Program other, InfoPerProgram otherInfo) { boolean result = false; for (MappingEntry me : outbound.values()) { @@ -347,8 +360,14 @@ public class DebuggerStaticMappingServicePlugin extends Plugin public Set getOpenMappedProgramsAtSnap(long snap) { Set result = new HashSet<>(); + Set toClean = new HashSet<>(); for (Entry out : outbound.entrySet()) { MappingEntry me = out.getValue(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } if (!me.isStaticProgramOpen()) { continue; } @@ -357,27 +376,42 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } result.add(me.program); } + outbound.values().removeAll(toClean); return result; } public ProgramLocation getOpenMappedLocations(Address address, Lifespan span) { TraceAddressSnapRange at = new ImmutableTraceAddressSnapRange(address, span); + Set toClean = new HashSet<>(); for (Entry out : outbound.entrySet()) { if (out.getKey().intersects(at)) { MappingEntry me = out.getValue(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } if (me.isStaticProgramOpen()) { + outbound.values().removeAll(toClean); return me.mapTraceAddressToProgramLocation(address); } } } + outbound.values().removeAll(toClean); return null; } protected void collectOpenMappedPrograms(AddressRange rng, Lifespan span, Map> result) { TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); + Set toClean = new HashSet<>(); for (Entry out : outbound.entrySet()) { MappingEntry me = out.getValue(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } if (me.program == null) { continue; } @@ -389,6 +423,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin result.computeIfAbsent(me.program, p -> new TreeSet<>()) .add(new MappedAddressRange(srcRng, dstRng)); } + outbound.values().removeAll(toClean); } public Map> getOpenMappedViews(AddressSetView set, @@ -402,14 +437,21 @@ public class DebuggerStaticMappingServicePlugin extends Plugin protected void collectMappedProgramURLsInView(AddressRange rng, Lifespan span, Set result) { + Set toClean = new HashSet<>(); TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); for (Entry out : outbound.entrySet()) { + MappingEntry me = out.getValue(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } if (!out.getKey().intersects(tatr)) { continue; } - MappingEntry me = out.getValue(); result.add(me.getStaticProgramURL()); } + outbound.values().removeAll(toClean); } public Set getMappedProgramURLsInView(AddressSetView set, Lifespan span) { @@ -455,11 +497,19 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } public boolean isMappedInTrace(Trace trace) { + Set toClean = new HashSet<>(); for (MappingEntry me : inbound.keySet()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } if (Objects.equals(trace, me.getTrace())) { + inbound.keySet().removeAll(toClean); return true; } } + inbound.keySet().removeAll(toClean); return false; } @@ -476,30 +526,43 @@ public class DebuggerStaticMappingServicePlugin extends Plugin public Set getOpenMappedTraceLocations(Address address) { Set result = new HashSet<>(); + Set toClean = new HashSet<>(); for (Entry inPreceding : inbound.headMapByValue(address, true) .entrySet()) { + MappingEntry me = inPreceding.getKey(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } Address start = inPreceding.getValue(); if (start == null) { continue; } - MappingEntry me = inPreceding.getKey(); if (!me.isInProgramRange(address)) { continue; } result.add(me.mapProgramAddressToTraceLocation(address)); } + inbound.keySet().removeAll(toClean); return result; } public TraceLocation getOpenMappedTraceLocation(Trace trace, Address address, long snap) { // TODO: Map by trace? + Set toClean = new HashSet<>(); for (Entry inPreceding : inbound.headMapByValue(address, true) .entrySet()) { + MappingEntry me = inPreceding.getKey(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } Address start = inPreceding.getValue(); if (start == null) { continue; } - MappingEntry me = inPreceding.getKey(); if (me.getTrace() != trace) { continue; } @@ -509,21 +572,29 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (!me.isInTraceLifespan(snap)) { continue; } + inbound.keySet().removeAll(toClean); return me.mapProgramAddressToTraceLocation(address); } + inbound.keySet().removeAll(toClean); return null; } protected void collectOpenMappedViews(AddressRange rng, Map> result) { + Set toClean = new HashSet<>(); for (Entry inPreceeding : inbound .headMapByValue(rng.getMaxAddress(), true) .entrySet()) { + MappingEntry me = inPreceeding.getKey(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + toClean.add(me); + continue; + } Address start = inPreceeding.getValue(); if (start == null) { continue; } - MappingEntry me = inPreceeding.getKey(); if (!me.isInProgramRange(rng)) { continue; } @@ -533,6 +604,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>()) .add(new MappedAddressRange(srcRange, dstRange)); } + inbound.keySet().removeAll(toClean); } public Map> getOpenMappedViews( @@ -609,6 +681,13 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } } + private void programsAffected(Collection programs) { + synchronized (affectedTraces) { + affectedPrograms.addAll(programs); + changeDebouncer.contact(null); + } + } + @Override public void addChangeListener(DebuggerStaticMappingChangeListener l) { changeListeners.add(l); @@ -706,13 +785,22 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } } - private void doAffectedByTraceOpened(Trace trace) { + private Set collectAffectedByTrace(Trace trace) { + Set set = new HashSet<>(); for (InfoPerProgram info : trackedProgramInfo.values()) { if (info.isMappedInTrace(trace)) { - traceAffected(trace); - programAffected(info.program); + set.add(info.program); } } + return set; + } + + private void doAffectedByTraceOpened(Trace trace) { + Set set = collectAffectedByTrace(trace); + if (!set.isEmpty()) { + traceAffected(trace); + programsAffected(set); + } } private void traceOpened(Trace trace) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java index 16abd5392c..82b159ab51 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.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,15 +19,10 @@ import java.awt.Component; import java.awt.event.*; import java.beans.PropertyEditor; import java.math.BigInteger; -import java.util.Map; -import java.util.function.Function; -import ghidra.debug.api.action.LocationTrackingSpec; import ghidra.framework.options.*; import ghidra.program.model.address.AddressRange; import ghidra.util.MathUtilities; -import ghidra.util.Msg; -import ghidra.util.classfinder.ClassSearcher; public enum MiscellaneousUtils { ; @@ -87,23 +82,6 @@ public enum MiscellaneousUtils { }); } - public static void collectUniqueInstances(Class cls, Map map, - Function keyFunc) { - // This is wasteful. Existing instances will be re-instantiated and thrown away - for (T t : ClassSearcher.getInstances(cls)) { - String key = keyFunc.apply(t); - T exists = map.get(key); - if (exists != null) { - if (exists.getClass().equals(t.getClass())) { - continue; - } - Msg.error(LocationTrackingSpec.class, - cls.getSimpleName() + " conflict over key: " + key); - } - map.put(key, t); - } - } - public static long lengthMin(long a, long b) { if (a == 0) { return b; 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 index a5b1a8b299..a5b610adaf 100644 --- 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 @@ -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. @@ -32,7 +32,6 @@ 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.AutoMapSpec; 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; @@ -42,6 +41,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSecti 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; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java index 05a4dd7fe3..33702a5c95 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.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. diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index 0485bf7f8f..a71e5bfd05 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.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. @@ -148,8 +148,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } record ObjectsContainingKey(long snap, Address address, String key, - Class iface) { - } + Class iface) {} protected final ReadWriteLock lock; protected final DBTrace trace;