diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/DebuggerMissingModuleActionContext.java similarity index 86% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java rename to Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/DebuggerMissingModuleActionContext.java index ae0191a0f22..345fbc6ebcf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/DebuggerMissingModuleActionContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.debug.gui.modules; +package ghidra.debug.api.modules; import java.util.Objects; @@ -43,10 +43,9 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (!(obj instanceof DebuggerMissingModuleActionContext)) { + if (!(obj instanceof DebuggerMissingModuleActionContext that)) { return false; } - DebuggerMissingModuleActionContext that = (DebuggerMissingModuleActionContext) obj; if (!this.module.equals(that.module)) { return false; } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/DebuggerMissingProgramActionContext.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/DebuggerMissingProgramActionContext.java new file mode 100644 index 00000000000..85a7ae72ac1 --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/DebuggerMissingProgramActionContext.java @@ -0,0 +1,98 @@ +/* ### + * 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.modules; + +import java.util.Objects; + +import docking.DefaultActionContext; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.InstructionIterator; +import ghidra.program.model.listing.Program; +import ghidra.trace.model.Trace; + +public class DebuggerMissingProgramActionContext extends DefaultActionContext { + + public static Address getMappingProbeAddress(Program program) { + if (program == null) { + return null; + } + AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator(); + if (eepi.hasNext()) { + return eepi.next(); + } + InstructionIterator ii = program.getListing().getInstructions(true); + if (ii.hasNext()) { + return ii.next().getAddress(); + } + AddressSetView es = program.getMemory().getExecuteSet(); + if (!es.isEmpty()) { + return es.getMinAddress(); + } + if (!program.getMemory().isEmpty()) { + return program.getMinAddress(); + } + return null; + } + + private final Trace trace; + private final Program program; + private final int hashCode; + + private Address probe; + + public DebuggerMissingProgramActionContext(Trace trace, Program program) { + this.trace = Objects.requireNonNull(trace); + this.program = Objects.requireNonNull(program); + this.hashCode = Objects.hash(getClass(), trace, program); + } + + public Trace getTrace() { + return trace; + } + + public Program getProgram() { + return program; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DebuggerMissingProgramActionContext that)) { + return false; + } + if (!this.trace.equals(that.trace)) { + return false; + } + if (!this.program.equals(that.program)) { + return false; + } + return true; + } + + public Address getMappingProbeAddress() { + if (probe == null) { + probe = getMappingProbeAddress(program); + } + return probe; + } +} 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 2478d173e01..310b51f42cd 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 @@ -43,16 +43,14 @@ import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.ConfigStateField; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.*; -import ghidra.program.model.listing.InstructionIterator; +import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; 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.MessageType; -import ghidra.util.Msg; +import ghidra.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; @@ -169,25 +167,8 @@ public Icon getIcon() { } protected Address getMappingProbeAddress() { - if (program == null) { - return null; - } - AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator(); - if (eepi.hasNext()) { - return eepi.next(); - } - InstructionIterator ii = program.getListing().getInstructions(true); - if (ii.hasNext()) { - return ii.next().getAddress(); - } - AddressSetView es = program.getMemory().getExecuteSet(); - if (!es.isEmpty()) { - return es.getMinAddress(); - } - if (!program.getMemory().isEmpty()) { - return program.getMinAddress(); - } - return null; // I guess we won't wait for a mapping, then + // May be null, in which case, we won't wait for a mapping + return DebuggerMissingProgramActionContext.getMappingProbeAddress(program); } protected CompletableFuture listenForMapping(DebuggerStaticMappingService mappingService, @@ -641,6 +622,22 @@ public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator config } return new LaunchResult(program, Map.of(), null, null, null, lastExc); } + catch (NoStaticMappingException e) { + DebuggerConsoleService consoleService = + tool.getService(DebuggerConsoleService.class); + if (consoleService == null) { + Msg.error(this, e.getMessage()); + } + else { + consoleService.log(DebuggerResources.ICON_MODULES, + "The trace %s has no mapping to its program %s" + .formatted( + HTMLUtilities.escapeHTML(trace.getDomainFile().getName()), + HTMLUtilities.escapeHTML(program.getDomainFile().getName())), + new DebuggerMissingProgramActionContext(trace, program)); + } + return new LaunchResult(program, sessions, acceptor, connection, trace, e); + } catch (Exception e) { DebuggerConsoleService consoleService = tool.getService(DebuggerConsoleService.class); diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html index 4902cf2d027..b0844ab1fa2 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html @@ -152,14 +152,51 @@

Map Section to Current Block

Import Missing Module

-

This action is offered to resolve a "missing module" console message. It is equivalent to Import From File System on the missing module.

+

This action is offered to resolve a "missing module" console message. Such a message is + reported in the Debug + Console when the cursor in the Dynamic Listing cannot be + synchronized to the static listing for lack of a module mapping. This action is equivalent to + Import From File System on the missing module.

Map Missing Module

-

This action is offered to resolve a "missing module" console message. It is equivalent to Map Module To on the missing module.

+

This action is offered to resolve a "missing module" console message. Such a message is + reported in the Debug + Console when the cursor in the Dynamic Listing cannot be + synchronized to the static listing for lack of a module mapping. This action is equivalent to + Map Module To on the missing module.

+ +

Retry Map + Missing Program

+ +

This action is offered to resolve a "missing program" console message. Such a message is + reported in the Debug + Console when the launcher fails to map the current program database to the launched trace. + This action is equivalent to Map Modules, but considering only the + missing program and launched trace.

+ +

Map + Missing Program to Current Module

+ +

This action is offered to resolve a "missing program" console message. Such a message is + reported in the Debug + Console when the launcher fails to map the current program database to the launched trace. + This action is only available when the current trace is the launched trace. It finds the module + containing the cursor in the Dynamic Listing and proposes + to map it to the missing program.

+ +

Map Missing Program Identically

+ +

This action is offered to resolve a "missing program" console message. Such a message is + reported in the Debug + Console when the launcher fails to map the current program database to the launched trace. + This action is equivalent to Map Identically, but for the + missing program and launched trace.

Show Sections Table

@@ -179,12 +216,15 @@

Dynamic + Listing contained by the selected modules or sections.

Select Rows

-

This action is available when the dynamic listing's cursor is at a valid location. It - selects the module and section, if applicable, containing that cursor. If the dynamic listing - has a selection, it selects all modules and sections intersecting that selection.

+

This action is available when the Dynamic Listing's cursor is + at a valid location. It selects the module and section, if applicable, containing that cursor. + If the dynamic listing has a selection, it selects all modules and sections intersecting that + selection.

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 057d532cb30..f8a96384b78 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 @@ -56,7 +56,6 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction; import ghidra.app.plugin.core.debug.gui.action.*; -import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.gui.thread.DebuggerTraceFileActionContext; import ghidra.app.plugin.core.debug.gui.trace.DebuggerTraceTabPanel; import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; @@ -74,6 +73,7 @@ import ghidra.debug.api.action.LocationTrackingSpec; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.listing.MultiBlendedListingBackgroundColorModel; +import ghidra.debug.api.modules.DebuggerMissingModuleActionContext; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainFile; 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 a63bfa59b24..1c741db78c9 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 @@ -20,6 +20,7 @@ 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.services.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; @@ -37,6 +38,7 @@ ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class, TraceActivatedPluginEvent.class, + TraceClosedPluginEvent.class, }, servicesRequired = { DebuggerStaticMappingService.class, @@ -81,6 +83,9 @@ else if (event instanceof ProgramClosedPluginEvent ev) { else if (event instanceof TraceActivatedPluginEvent ev) { provider.coordinatesActivated(ev.getActiveCoordinates()); } + else if (event instanceof TraceClosedPluginEvent ev) { + provider.traceClosed(ev.getTrace()); + } } @Override 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 3b5b709449b..657c1e9a5f3 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 @@ -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; @@ -67,15 +67,29 @@ import ghidra.program.util.ProgramSelection; import ghidra.trace.model.*; import ghidra.trace.model.modules.*; +import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.util.TraceEvent; import ghidra.trace.util.TraceEvents; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; +import ghidra.util.*; public class DebuggerModulesProvider extends ComponentProviderAdapter { protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = AutoConfigState.wireHandler(DebuggerModulesProvider.class, MethodHandles.lookup()); + protected static final String NO_MODULES_PROPOSAL_SEL = """ + Could not formulate a proposal for any selected module. \ + You may need to import and/or open the destination images first.\ + """; + + protected static final String FMT_NO_MODULES_PROPOSAL_RETRY = """ + Could not formulate a proposal for program '%s' to trace '%s'. \ + The module may not be loaded yet, or the chosen image could be wrong.\ + """; + + protected static final String FMT_NO_MODULES_PROPOSAL_CURRENT = """ + Could not formulate a proposal from module '%s' to program '%s'.\ + """; + protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { if (!Objects.equals(a.getTrace(), b.getTrace())) { return false; @@ -253,6 +267,57 @@ static ActionBuilder builder(Plugin owner) { } } + interface MapMissingProgramRetryAction { + String NAME = "Retry Map Missing Program"; + String DESCRIPTION = "Retry mapping the missing program by finding its module"; + Icon ICON = DebuggerResources.ICON_MAP_AUTO; + String HELP_ANCHOR = "map_missing_program_retry"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .popupMenuIcon(ICON) + .popupMenuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface MapMissingProgramToCurrentAction { + String NAME = "Map Missing Program to Current Module"; + String DESCRIPTION = "Map the missing program to the current module"; + Icon ICON = DebuggerResources.ICON_MAP_MODULES; + String HELP_ANCHOR = "map_missing_program_current"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .popupMenuIcon(ICON) + .popupMenuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface MapMissingProgramIdenticallyAction { + String NAME = "Map Missing Program Identically"; + String DESCRIPTION = "Map the missing program to its trace identically"; + Icon ICON = DebuggerResources.ICON_MAP_IDENTICALLY; + String HELP_ANCHOR = "map_missing_program_identically"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .popupMenuIcon(ICON) + .popupMenuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + interface ShowSectionsTableAction { String NAME = "Show Sections Table"; Icon ICON = new GIcon("icon.debugger.modules.table.sections"); @@ -404,9 +469,17 @@ public boolean isEnabledForContext(ActionContext context) { } } + protected class ForCleanupMappingChangeListener + implements DebuggerStaticMappingChangeListener { + @Override + public void mappingsChanged(Set affectedTraces, Set affectedPrograms) { + Swing.runIfSwingOrRunLater(() -> cleanMissingProgramMessages(null, null)); + } + } + final DebuggerModulesPlugin plugin; - @AutoServiceConsumed + //@AutoServiceConsumed via method private DebuggerStaticMappingService staticMappingService; @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @@ -467,6 +540,13 @@ public boolean isEnabledForContext(ActionContext context) { DockingAction actionImportMissingModule; DockingAction actionMapMissingModule; + DockingAction actionMapMissingProgramRetry; + DockingAction actionMapMissingProgramToCurrent; + DockingAction actionMapMissingProgramIdentically; + + protected final ForCleanupMappingChangeListener mappingChangeListener = + new ForCleanupMappingChangeListener(); + SelectAddressesAction actionSelectAddresses; ImportFromFileSystemAction actionImportFromFileSystem; ToggleDockingAction actionShowSectionsTable; @@ -507,26 +587,37 @@ private void importModuleFromFileSystem(TraceModule module) { importerService.importFile(root, file); } + void addResolutionActionMaybe(DebuggerConsoleService consoleService, DockingActionIf action) { + if (action != null) { + consoleService.addResolutionAction(action); + } + } + + void removeResolutionActionMaybe(DebuggerConsoleService consoleService, + DockingActionIf action) { + if (action != null) { + consoleService.removeResolutionAction(action); + } + } + @AutoServiceConsumed private void setConsoleService(DebuggerConsoleService consoleService) { if (consoleService != null) { - if (actionImportMissingModule != null) { - consoleService.addResolutionAction(actionImportMissingModule); - } - if (actionMapMissingModule != null) { - consoleService.addResolutionAction(actionMapMissingModule); - } + addResolutionActionMaybe(consoleService, actionImportMissingModule); + addResolutionActionMaybe(consoleService, actionMapMissingModule); + addResolutionActionMaybe(consoleService, actionMapMissingProgramRetry); + addResolutionActionMaybe(consoleService, actionMapMissingProgramToCurrent); + addResolutionActionMaybe(consoleService, actionMapMissingProgramIdentically); } } protected void dispose() { if (consoleService != null) { - if (actionImportMissingModule != null) { - consoleService.removeResolutionAction(actionImportMissingModule); - } - if (actionMapMissingModule != null) { - consoleService.removeResolutionAction(actionMapMissingModule); - } + removeResolutionActionMaybe(consoleService, actionImportMissingModule); + removeResolutionActionMaybe(consoleService, actionMapMissingModule); + removeResolutionActionMaybe(consoleService, actionMapMissingProgramRetry); + removeResolutionActionMaybe(consoleService, actionMapMissingProgramToCurrent); + removeResolutionActionMaybe(consoleService, actionMapMissingProgramIdentically); } blockChooserDialog.dispose(); @@ -637,6 +728,20 @@ protected void createActions() { .onAction(this::activatedMapMissingModule) .build(); + actionMapMissingProgramRetry = MapMissingProgramRetryAction.builder(plugin) + .withContext(DebuggerMissingProgramActionContext.class) + .onAction(this::activatedMapMissingProgramRetry) + .build(); + actionMapMissingProgramToCurrent = MapMissingProgramToCurrentAction.builder(plugin) + .withContext(DebuggerMissingProgramActionContext.class) + .enabledWhen(this::isEnabledMapMissingProgramToCurrent) + .onAction(this::activatedMapMissingProgramToCurrent) + .build(); + actionMapMissingProgramIdentically = MapMissingProgramIdenticallyAction.builder(plugin) + .withContext(DebuggerMissingProgramActionContext.class) + .onAction(this::activatedMapMissingProgramIdentically) + .build(); + actionSelectAddresses = new SelectAddressesAction(); actionImportFromFileSystem = new ImportFromFileSystemAction(); actionShowSectionsTable = ShowSectionsTableAction.builder(plugin) @@ -799,6 +904,78 @@ private void activatedMapMissingModule(DebuggerMissingModuleActionContext contex mapModuleTo(context.getModule()); } + private void activatedMapMissingProgramRetry(DebuggerMissingProgramActionContext context) { + if (staticMappingService == null) { + return; + } + Program program = context.getProgram(); + Trace trace = context.getTrace(); + Map map = staticMappingService.proposeModuleMaps( + trace.getModuleManager().getAllModules(), List.of(program)); + Collection proposal = MapProposal.flatten(map.values()); + promptModuleProposal(proposal, FMT_NO_MODULES_PROPOSAL_RETRY.formatted( + trace.getDomainFile().getName(), program.getDomainFile().getName())); + } + + private boolean isEnabledMapMissingProgramToCurrent( + DebuggerMissingProgramActionContext context) { + if (staticMappingService == null || traceManager == null || listingService == null) { + return false; + } + ProgramLocation loc = listingService.getCurrentLocation(); + if (loc == null) { + return false; + } + if (!(loc.getProgram() instanceof TraceProgramView view)) { + return false; + } + Trace trace = context.getTrace(); + if (view.getTrace() != trace) { + return false; + } + + long snap = traceManager.getCurrentFor(trace).getSnap(); + Address address = loc.getAddress(); + return !trace.getModuleManager().getModulesAt(snap, address).isEmpty(); + } + + private void activatedMapMissingProgramToCurrent(DebuggerMissingProgramActionContext context) { + if (staticMappingService == null || traceManager == null || listingService == null) { + return; + } + + Trace trace = context.getTrace(); + long snap = traceManager.getCurrentFor(trace).getSnap(); + Address address = listingService.getCurrentLocation().getAddress(); + + TraceModule module = trace.getModuleManager().getModulesAt(snap, address).iterator().next(); + + Program program = context.getProgram(); + ModuleMapProposal proposal = + staticMappingService.proposeModuleMap(module, program); + Map map = proposal.computeMap(); + promptModuleProposal(map.values(), FMT_NO_MODULES_PROPOSAL_CURRENT.formatted( + module.getName(), program.getDomainFile().getName())); + } + + private void activatedMapMissingProgramIdentically( + DebuggerMissingProgramActionContext context) { + if (staticMappingService == null) { + return; + } + + Trace trace = context.getTrace(); + long snap = traceManager == null ? 0 : traceManager.getCurrentFor(trace).getSnap(); + + try { + staticMappingService.addIdentityMapping(trace, context.getProgram(), + Lifespan.nowOn(snap), true); + } + catch (TraceConflictedMappingException e) { + Msg.showError(this, null, "Map Identically", e.getMessage()); + } + } + private void toggledShowSectionsTable(ActionContext ignored) { setShowSectionsTable(actionShowSectionsTable.isSelected()); } @@ -903,11 +1080,9 @@ private void activatedSelectCurrent(ActionContext ignored) { } } - protected void promptModuleProposal(Collection proposal) { + protected void promptModuleProposal(Collection proposal, String emptyMsg) { if (proposal.isEmpty()) { - Msg.showInfo(this, getComponent(), "Map Modules", - "Could not formulate a proposal for any selected module." + - " You may need to import and/or open the destination images first."); + Msg.showInfo(this, getComponent(), "Map Modules", emptyMsg); return; } Collection adjusted = @@ -926,7 +1101,7 @@ protected void mapModules(Set modules) { Map map = staticMappingService.proposeModuleMaps(modules, List.of(programManager.getAllOpenPrograms())); Collection proposal = MapProposal.flatten(map.values()); - promptModuleProposal(proposal); + promptModuleProposal(proposal, NO_MODULES_PROPOSAL_SEL); } protected void mapModuleTo(TraceModule module) { @@ -939,7 +1114,7 @@ protected void mapModuleTo(TraceModule module) { } ModuleMapProposal proposal = staticMappingService.proposeModuleMap(module, program); Map map = proposal.computeMap(); - promptModuleProposal(map.values()); + promptModuleProposal(map.values(), NO_MODULES_PROPOSAL_SEL); } protected void promptSectionProposal(Collection proposal) { @@ -1090,6 +1265,11 @@ public void programClosed(Program program) { if (currentProgram == program) { currentProgram = null; } + cleanMissingProgramMessages(null, program); + } + + public void traceClosed(Trace trace) { + cleanMissingProgramMessages(trace, null); } protected void addNewTraceListener() { @@ -1232,4 +1412,62 @@ public void readConfigState(SaveState saveState) { doSetFilterSectionsByModules(filterSectionsByModules); doSetShowSectionsTable(showSectionsTable); } + + protected boolean shouldKeepMessage(DebuggerMissingProgramActionContext ctx, Trace closedTrace, + Program closedProgram) { + Trace trace = ctx.getTrace(); + if (trace == closedTrace) { + return false; + } + if (!traceManager.getOpenTraces().contains(trace)) { + return false; + } + Program program = ctx.getProgram(); + if (program == closedProgram) { + return false; + } + if (programManager != null && + !Arrays.asList(programManager.getAllOpenPrograms()).contains(program)) { + return false; + } + + // Only do mapping probe on mapping changed events + if (closedTrace != null || closedProgram != null) { + return true; + } + + TraceProgramView view = traceManager.getCurrentFor(trace).getView(); + Address probe = ctx.getMappingProbeAddress(); + ProgramLocation dyn = staticMappingService.getDynamicLocationFromStatic(view, + new ProgramLocation(program, probe)); + if (dyn != null) { + return false; + } + return true; + } + + protected void cleanMissingProgramMessages(Trace closedTrace, Program closedProgram) { + if (traceManager == null) { + return; + } + for (ActionContext ctx : consoleService.getActionContexts()) { + if (!(ctx instanceof DebuggerMissingProgramActionContext mpCtx)) { + continue; + } + if (!shouldKeepMessage(mpCtx, closedTrace, closedProgram)) { + consoleService.removeFromLog(mpCtx); + } + } + } + + @AutoServiceConsumed + private void setStaticMappingService(DebuggerStaticMappingService staticMappingService) { + if (this.staticMappingService != null) { + this.staticMappingService.removeChangeListener(mappingChangeListener); + } + this.staticMappingService = staticMappingService; + if (this.staticMappingService != null) { + this.staticMappingService.addChangeListener(mappingChangeListener); + } + } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index d3fe71ee2f5..6cbff18673c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -47,13 +47,13 @@ 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.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.async.SwingExecutorService; import ghidra.debug.api.model.TraceRecorder; +import ghidra.debug.api.modules.DebuggerMissingModuleActionContext; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.*; import ghidra.plugin.importer.ImporterPlugin;