diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/build.gradle b/Ghidra/Debug/Debugger-agent-dbgeng/build.gradle index 162c3089f0..2560c95650 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/build.gradle +++ b/Ghidra/Debug/Debugger-agent-dbgeng/build.gradle @@ -54,7 +54,7 @@ jar { task configureNodepJar { doLast { - configurations.runtimeOnly.files.forEach { + configurations.default.files.forEach { if (filterJar(it)) { nodepJar.from(zipTree(it)) } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgConsoleExecCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgConsoleExecCommand.java index a0c1b43962..ff59b5c2af 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgConsoleExecCommand.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgConsoleExecCommand.java @@ -15,10 +15,10 @@ */ package agent.dbgeng.manager.cmd; +import agent.dbgeng.dbgeng.DebugControl; import agent.dbgeng.manager.DbgEvent; import agent.dbgeng.manager.DbgManager; -import agent.dbgeng.manager.evt.AbstractDbgCompletedCommandEvent; -import agent.dbgeng.manager.evt.DbgConsoleOutputEvent; +import agent.dbgeng.manager.evt.*; import agent.dbgeng.manager.impl.DbgManagerImpl; /** @@ -63,6 +63,8 @@ public class DbgConsoleExecCommand extends AbstractDbgCommand { @Override public void invoke() { - manager.getControl().execute(command); + DebugControl control = manager.getControl(); + control.execute(command); + manager.processEvent(new DbgPromptChangedEvent(control.getPromptText())); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/evt/DbgPromptChangedEvent.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/evt/DbgPromptChangedEvent.java new file mode 100644 index 0000000000..7b538a7e30 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/evt/DbgPromptChangedEvent.java @@ -0,0 +1,26 @@ +/* ### + * 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 agent.dbgeng.manager.evt; + +public class DbgPromptChangedEvent extends AbstractDbgEvent { + public DbgPromptChangedEvent(String prompt) { + super(prompt); + } + + public String getPrompt() { + return getInfo(); + } +} diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java index 93f8dc21e6..3d13198ac9 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java @@ -41,7 +41,8 @@ import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo; import agent.dbgeng.manager.breakpoint.DbgBreakpointType; import agent.dbgeng.manager.cmd.*; import agent.dbgeng.manager.evt.*; -import agent.dbgeng.model.iface1.*; +import agent.dbgeng.model.iface1.DbgModelTargetActiveScope; +import agent.dbgeng.model.iface1.DbgModelTargetFocusScope; import agent.dbgeng.model.iface2.DbgModelTargetObject; import agent.dbgeng.model.iface2.DbgModelTargetThread; import ghidra.async.*; @@ -597,6 +598,7 @@ public class DbgManagerImpl implements DbgManager { handlerMap.putVoid(DbgStoppedEvent.class, this::processDefault); handlerMap.putVoid(DbgRunningEvent.class, this::processDefault); handlerMap.putVoid(DbgConsoleOutputEvent.class, this::processConsoleOutput); + handlerMap.putVoid(DbgPromptChangedEvent.class, this::processPromptChanged); handlerMap.putVoid(DbgBreakpointCreatedEvent.class, this::processBreakpointCreated); handlerMap.putVoid(DbgBreakpointModifiedEvent.class, this::processBreakpointModified); handlerMap.putVoid(DbgBreakpointDeletedEvent.class, this::processBreakpointDeleted); @@ -925,6 +927,7 @@ public class DbgManagerImpl implements DbgManager { dbgState = DbgState.STOPPED; //System.err.println("STOPPED " + id); processEvent(new DbgStoppedEvent(eventThread.getId())); + processEvent(new DbgPromptChangedEvent(getControl().getPromptText())); } if (status.threadState.equals(ExecutionState.RUNNING)) { //System.err.println("RUNNING " + id); @@ -949,6 +952,7 @@ public class DbgManagerImpl implements DbgManager { if (process != null) { processEvent(new DbgProcessSelectedEvent(process)); } + processEvent(new DbgPromptChangedEvent(getControl().getPromptText())); return DebugStatus.BREAK; } if (status.equals(DebugStatus.GO)) { @@ -972,6 +976,7 @@ public class DbgManagerImpl implements DbgManager { if (thread != null) { getEventListeners().fire.threadSelected(thread, null, evt.getCause()); } + processEvent(new DbgPromptChangedEvent(getControl().getPromptText())); break; } } @@ -1033,6 +1038,10 @@ public class DbgManagerImpl implements DbgManager { getEventListeners().fire.consoleOutput(evt.getInfo(), evt.getMask()); } + protected void processPromptChanged(DbgPromptChangedEvent evt, Void v) { + getEventListeners().fire.promptChanged(evt.getPrompt()); + } + /** * Handler for breakpoint-created event * @@ -1480,8 +1489,8 @@ public class DbgManagerImpl implements DbgManager { @Override public CompletableFuture console(String command) { if (continuation != null) { - String prompt = command.equals("") ? DbgModelTargetInterpreter.DBG_PROMPT : ">>>"; - getEventListeners().fire.promptChanged(prompt); + //String prompt = command.equals("") ? DbgModelTargetInterpreter.DBG_PROMPT : ">>>"; + //getEventListeners().fire.promptChanged(prompt); continuation.complete(command); setContinuation(null); return AsyncUtils.NIL; diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/build.gradle b/Ghidra/Debug/Debugger-agent-dbgmodel/build.gradle index 1d7de0d1cc..008eb22639 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/build.gradle +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/build.gradle @@ -50,7 +50,7 @@ jar { task configureNodepJar { doLast { - configurations.runtimeOnly.files.forEach { + configurations.default.files.forEach { if (filterJar(it)) { nodepJar.from(zipTree(it)) } diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelSetContextMWETest.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelSetContextMWETest.java index ff93b1f388..e1dec4da49 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelSetContextMWETest.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelSetContextMWETest.java @@ -18,6 +18,7 @@ package agent.dbgmodel.dbgmodel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.io.*; import java.util.*; import org.junit.Before; @@ -44,7 +45,7 @@ public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegration } @Test - public void testMWE() { + public void testMWE() throws IOException { HostDataModelAccess access = DbgModel.debugCreate(); DebugClient client = access.getClient(); DebugControl control = client.getControl(); @@ -272,12 +273,6 @@ public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegration DebugStatus status = super.exitThread(exitCode); return status; } - - @Override - public DebugStatus changeSymbolState(BitmaskSet flags, - long argument) { - return defaultStatus; - } }; try (ProcMaker maker = new ProcMaker(client, "C:\\Software\\Winmine__XP.exe")) { @@ -317,6 +312,17 @@ public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegration } cb.dumpFrame0ViaDX(); + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + System.err.print(control.getPromptText()); + //control.prompt(BitmaskSet.of(), "Hello?>"); + String cmd = in.readLine(); + control.execute(cmd); + if (control.getExecutionStatus().shouldWait) { + control.waitForEvent(); + } + } + /** * TODO: Didn't finish because the SetContext failed issue turned out to be mixed and/or * broken DLLs. diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelTest.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelTest.java index 859cc3ed26..f4c615f5f8 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/test/java/agent/dbgmodel/dbgmodel/DbgModelTest.java @@ -1124,4 +1124,9 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest { } } */ + + @Test + public void testPrompt() throws Exception { + + } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/build.gradle b/Ghidra/Debug/Debugger-agent-gdb/build.gradle index ddbf7d277b..99e10ed498 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/build.gradle +++ b/Ghidra/Debug/Debugger-agent-gdb/build.gradle @@ -52,7 +52,7 @@ jar { task configureNodepJar { doLast { - configurations.runtimeOnly.files.forEach { + configurations.default.files.forEach { if (filterJar(it)) { nodepJar.from(zipTree(it)) } diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 9f50cd3e65..21cd0f0421 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -132,6 +132,7 @@ src/main/resources/images/breakpoint-set.png||GHIDRA||||END| src/main/resources/images/breakpoints-clear-all.png||GHIDRA||||END| src/main/resources/images/breakpoints-disable-all.png||GHIDRA||||END| src/main/resources/images/breakpoints-enable-all.png||GHIDRA||||END| +src/main/resources/images/breakpoints-make-effective.png||GHIDRA||||END| src/main/resources/images/breakpoints.png||GHIDRA||||END| src/main/resources/images/closedFolder.png||Modified Nuvola Icons - LGPL 2.1||||END| src/main/resources/images/connect.png||GHIDRA||||END| @@ -179,6 +180,7 @@ src/main/svg/breakpoint-set.svg||GHIDRA||||END| src/main/svg/breakpoints-clear-all.svg||GHIDRA||||END| src/main/svg/breakpoints-disable-all.svg||GHIDRA||||END| src/main/svg/breakpoints-enable-all.svg||GHIDRA||||END| +src/main/svg/breakpoints-make-effective.svg||GHIDRA||||END| src/main/svg/breakpoints.svg||GHIDRA||||END| src/main/svg/connect.svg||GHIDRA||||END| src/main/svg/console.svg||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerModuleMapProposalDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerModuleMapProposalDialog.png index 5b9873de52..66a259c156 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerModuleMapProposalDialog.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerModuleMapProposalDialog.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerSectionMapProposalDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerSectionMapProposalDialog.png index 831ea6d4a1..da54d2e0cb 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerSectionMapProposalDialog.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/images/DebuggerSectionMapProposalDialog.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 13a613fac6..175c099d1f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -99,6 +99,8 @@ public interface DebuggerResources { ResourceManager.loadImage("images/breakpoints-disable-all.png"); ImageIcon ICON_CLEAR_ALL_BREAKPOINTS = ResourceManager.loadImage("images/breakpoints-clear-all.png"); + ImageIcon ICON_MAKE_BREAKPOINTS_EFFECTIVE = + ResourceManager.loadImage("images/breakpoints-make-effective.png"); // TODO: Some overlay to indicate dynamic, or new icon altogether ImageIcon ICON_LISTING = ResourceManager.loadImage("images/Browser.gif"); @@ -986,13 +988,29 @@ public interface DebuggerResources { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) .menuGroup(GROUP) .menuPath(NAME) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } + interface OpenProgramAction { + String NAME = "Open Program"; + Icon ICON = ICON_PROGRAM; + String DESCRIPTION = "Open the program"; + String HELP_ANCHOR = "open_program"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + abstract class AbstractToggleBreakpointAction extends DockingAction { public static final String NAME = "Toggle Breakpoint"; // TODO: A "toggle breakpoint" icon @@ -1142,6 +1160,18 @@ public interface DebuggerResources { } } + abstract class AbstractMakeBreakpointsEffectiveAction extends DockingAction { + public static final String NAME = "Make Breakpoints Effective"; + public static final Icon ICON = ICON_MAKE_BREAKPOINTS_EFFECTIVE; + public static final String HELP_ANCHOR = "make_breakpoints_effective"; + + public AbstractMakeBreakpointsEffectiveAction(Plugin owner) { + super(NAME, owner.getName()); + setDescription("Place enabled but ineffective breakpoints where possible"); + setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); + } + } + interface MapModulesAction { String NAME = "Map Modules"; String DESCRIPTION = "Map selected modules to program images"; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java index 9bb1c881ff..301f7d70c5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java @@ -22,7 +22,6 @@ import ghidra.dbg.target.TargetBreakpointLocation; import ghidra.program.model.address.Address; import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.thread.TraceThread; -import ghidra.util.Msg; import ghidra.util.database.UndoableTransaction; public class BreakpointLocationRow { @@ -43,17 +42,18 @@ public class BreakpointLocationRow { } public void setEnabled(boolean enabled) { + // TODO: Make this toggle the individual location, if possible, not the whole spec. TraceRecorder recorder = provider.modelService.getRecorder(loc.getTrace()); TargetBreakpointLocation bpt = recorder.getTargetBreakpoint(loc); if (enabled) { bpt.getSpecification().enable().exceptionally(ex -> { - Msg.showError(this, null, "Toggle breakpoint", "Could not enable breakpoint", ex); + provider.breakpointError("Toggle breakpoint", "Could not enable breakpoint", ex); return null; }); } else { bpt.getSpecification().disable().exceptionally(ex -> { - Msg.showError(this, null, "Toggle breakpoint", "Could not disable breakpoint", ex); + provider.breakpointError("Toggle breakpoint", "Could not disable breakpoint", ex); return null; }); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java index 4ba11b3cca..e25d603d85 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java @@ -532,7 +532,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (bs == null || bs.isEmpty()) { Set supported = getSupportedKindsFromContext(context); if (supported.isEmpty()) { - Msg.showError(this, null, NAME, + breakpointError(NAME, "It seems this target does not support breakpoints."); return; } @@ -545,13 +545,13 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin Trace trace = getTraceFromContext(context); // OK if null - means all traces if (en.enabled) { breakpointService.disableAll(bs, trace).exceptionally(ex -> { - Msg.showError(this, null, NAME, "Could not disable breakpoints", ex); + breakpointError(NAME, "Could not disable breakpoints", ex); return null; }); } else { breakpointService.enableAll(bs, trace).exceptionally(ex -> { - Msg.showError(this, null, NAME, "Could not enable breakpoints", ex); + breakpointError(NAME, "Could not enable breakpoints", ex); return null; }); } @@ -628,7 +628,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin ProgramLocation location = getLocationFromContext(context); Set col = breakpointService.getBreakpointsAt(location); breakpointService.enableAll(col, getTraceFromContext(context)).exceptionally(ex -> { - Msg.showError(this, null, NAME, "Could not enable breakpoint", ex); + breakpointError(NAME, "Could not enable breakpoint", ex); return null; }); } @@ -665,7 +665,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin ProgramLocation location = getLocationFromContext(context); Set col = breakpointService.getBreakpointsAt(location); breakpointService.disableAll(col, getTraceFromContext(context)).exceptionally(ex -> { - Msg.showError(this, null, NAME, "Could not disable breakpoint", ex); + breakpointError(NAME, "Could not disable breakpoint", ex); return null; }); } @@ -704,7 +704,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin ProgramLocation location = getLocationFromContext(context); Set col = breakpointService.getBreakpointsAt(location); breakpointService.deleteAll(col, getTraceFromContext(context)).exceptionally(ex -> { - Msg.showError(this, null, NAME, "Could not delete breakpoint", ex); + breakpointError(NAME, "Could not delete breakpoint", ex); return null; }); } @@ -733,6 +733,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin private DebuggerStaticMappingService mappingService; @AutoServiceConsumed private DebuggerTraceManagerService traceManager; + @AutoServiceConsumed + private DebuggerConsoleService consoleService; @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; @@ -1084,4 +1086,21 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin } } } + + protected void breakpointError(String title, String message) { + if (consoleService == null) { + Msg.showError(this, null, title, message); + return; + } + consoleService.log(DebuggerResources.ICON_LOG_ERROR, message); + } + + protected void breakpointError(String title, String message, Throwable ex) { + if (consoleService == null) { + Msg.showError(this, null, title, message, ex); + return; + } + Msg.error(this, message, ex); + consoleService.log(DebuggerResources.ICON_LOG_ERROR, message + " (" + ex + ")"); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java index 493a9e9f2d..3dcf0ca4b5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java @@ -25,19 +25,20 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @PluginInfo( // - shortDescription = "Debugger breakpoints manager", // - description = "GUI to manage breakpoints", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.RELEASED, // - servicesRequired = { // - DebuggerLogicalBreakpointService.class, // - DebuggerModelService.class, // - }, eventsConsumed = { - TraceOpenedPluginEvent.class, // - TraceClosedPluginEvent.class, // - TraceActivatedPluginEvent.class, // - } // + shortDescription = "Debugger breakpoints manager", // + description = "GUI to manage breakpoints", // + category = PluginCategoryNames.DEBUGGER, // + packageName = DebuggerPluginPackage.NAME, // + status = PluginStatus.RELEASED, // + servicesRequired = { // + DebuggerLogicalBreakpointService.class, // + DebuggerModelService.class, // + }, + eventsConsumed = { + TraceOpenedPluginEvent.class, // + TraceClosedPluginEvent.class, // + TraceActivatedPluginEvent.class, // + } // ) public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin { protected DebuggerBreakpointsProvider provider; @@ -54,6 +55,7 @@ public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin { @Override protected void dispose() { + provider.dispose(); tool.removeComponentProvider(provider); super.dispose(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java index d5bfae71ef..3a613f554d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java @@ -254,8 +254,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter (DebuggerLogicalBreakpointsActionContext) context; Collection sel = ctx.getSelection(); breakpointService.enableAll(sel, null).exceptionally(ex -> { - Msg.showError(this, getComponent(), "Enable Breakpoints", - "Could not enable breakpoints", ex); + breakpointError("Enable Breakpoints", "Could not enable breakpoints", ex); return null; }); } @@ -286,8 +285,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter public void actionPerformed(ActionContext context) { Set all = breakpointService.getAllBreakpoints(); breakpointService.enableAll(all, null).exceptionally(ex -> { - Msg.showError(this, getComponent(), "Enable All Breakpoints", - "Could not enable breakpoints", ex); + breakpointError("Enable All Breakpoints", "Could not enable breakpoints", ex); return null; }); } @@ -320,8 +318,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter (DebuggerLogicalBreakpointsActionContext) context; Collection sel = ctx.getSelection(); breakpointService.disableAll(sel, null).exceptionally(ex -> { - Msg.showError(this, getComponent(), "Disable Breakpoints", - "Could not disable breakpoints", ex); + breakpointError("Disable Breakpoints", "Could not disable breakpoints", ex); return null; }); } @@ -352,8 +349,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter public void actionPerformed(ActionContext context) { Set all = breakpointService.getAllBreakpoints(); breakpointService.disableAll(all, null).exceptionally(ex -> { - Msg.showError(this, getComponent(), "Disable All Breakpoints", - "Could not disable breakpoints", ex); + breakpointError("Disable All Breakpoints", "Could not disable breakpoints", ex); return null; }); } @@ -365,7 +361,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } protected class ClearSelectedBreakpointsAction extends AbstractClearSelectedBreakpointsAction { - public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; + public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS + "Clear"; public ClearSelectedBreakpointsAction() { super(plugin); @@ -382,8 +378,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter (DebuggerLogicalBreakpointsActionContext) context; Collection sel = ctx.getSelection(); breakpointService.deleteAll(sel, null).exceptionally(ex -> { - Msg.showError(this, getComponent(), "Clear Breakpoints", - "Could not clear breakpoints", ex); + breakpointError("Clear Breakpoints", "Could not clear breakpoints", ex); return null; }); } @@ -401,7 +396,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } protected class ClearAllBreakpointsAction extends AbstractClearAllBreakpointsAction { - public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; + public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS + "Clear"; public ClearAllBreakpointsAction() { super(plugin); @@ -414,8 +409,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter public void actionPerformed(ActionContext context) { Set all = breakpointService.getAllBreakpoints(); breakpointService.deleteAll(all, null).exceptionally(ex -> { - Msg.showError(this, getComponent(), "Clear All Breakpoints", - "Could not clear breakpoints", ex); + breakpointError("Clear All Breakpoints", "Could not clear breakpoints", ex); return null; }); } @@ -426,6 +420,64 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } } + protected abstract class CommonMakeBreakpointsEffectiveAction + extends AbstractMakeBreakpointsEffectiveAction { + public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; + + public CommonMakeBreakpointsEffectiveAction() { + super(plugin); + setToolBarData(new ToolBarData(ICON, GROUP)); + } + + @Override + public void actionPerformed(ActionContext context) { + Set all = breakpointService.getAllBreakpoints(); + for (LogicalBreakpoint lb : all) { + if (lb.computeEnablement() != Enablement.INEFFECTIVE_ENABLED) { + continue; + } + if (lb.getMappedTraces().isEmpty()) { + continue; + } + lb.enable(); + } + } + } + + protected class MakeBreakpointsEffectiveAction extends CommonMakeBreakpointsEffectiveAction { + public MakeBreakpointsEffectiveAction() { + super(); + addLocalAction(this); + setEnabled(false); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (breakpointService == null) { + return false; + } + Set all = breakpointService.getAllBreakpoints(); + for (LogicalBreakpoint lb : all) { + if (lb.computeEnablement() != Enablement.INEFFECTIVE_ENABLED) { + continue; + } + if (lb.getMappedTraces().isEmpty()) { + continue; + } + return true; + } + return false; + } + } + + protected class MakeBreakpointsEffectiveResolutionAction + extends CommonMakeBreakpointsEffectiveAction { + @Override + public boolean isValidContext(ActionContext context) { + return context instanceof DebuggerMakeBreakpointsEffectiveActionContext; + } + } + class LocationsBySelectedBreakpointsTableFilter implements TableFilter { @Override public boolean acceptsRow(BreakpointLocationRow locationRow) { @@ -540,13 +592,15 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter // @AutoServiceConsumed via method private DebuggerLogicalBreakpointService breakpointService; - // @AutoServiceConsumed via method + // @AutoServiceConsumed via method, package access for BreakpointLogicalRow DebuggerModelService modelService; @AutoServiceConsumed private DebuggerListingService listingService; @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @AutoServiceConsumed + private DebuggerConsoleService consoleService; + @AutoServiceConsumed private GoToService goToService; @SuppressWarnings("unused") private AutoService.Wiring autoServiceWiring; @@ -571,13 +625,18 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter private ActionContext myActionContext; + private final DebuggerMakeBreakpointsEffectiveActionContext makeEffectiveResolutionContext = + new DebuggerMakeBreakpointsEffectiveActionContext(); + // package access for testing - EnableSelectedBreakpointsAction actionEnableSelectedBreakpointsAction; - EnableAllBreakpointsAction actionEnableAllBreakpointsAction; - DisableSelectedBreakpointsAction actionDisableSelectedBreakpointsAction; - DisableAllBreakpointsAction actionDisableAllBreakpointsAction; - ClearSelectedBreakpointsAction actionClearSelectedBreakpointsAction; - ClearAllBreakpointsAction actionClearAllBreakpointsAction; + EnableSelectedBreakpointsAction actionEnableSelectedBreakpoints; + EnableAllBreakpointsAction actionEnableAllBreakpoints; + DisableSelectedBreakpointsAction actionDisableSelectedBreakpoints; + DisableAllBreakpointsAction actionDisableAllBreakpoints; + ClearSelectedBreakpointsAction actionClearSelectedBreakpoints; + ClearAllBreakpointsAction actionClearAllBreakpoints; + MakeBreakpointsEffectiveAction actionMakeBreakpointsEffective; + MakeBreakpointsEffectiveResolutionAction actionMakeBreakpointsEffectiveResolution; ToggleDockingAction actionFilterByCurrentTrace; ToggleDockingAction actionFilterLocationsByBreakpoints; @@ -598,6 +657,35 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter createActions(); } + protected void dispose() { + if (consoleService != null) { + if (actionMakeBreakpointsEffectiveResolution != null) { + consoleService.removeResolutionAction(actionMakeBreakpointsEffectiveResolution); + } + } + } + + @Override + public void contextChanged() { + super.contextChanged(); + if (consoleService == null) { + return; + } + // TODO: This should probably check for its existence first + // Kind of a hack, but it works. + if (actionMakeBreakpointsEffective != null && + actionMakeBreakpointsEffective.isEnabledForContext(myActionContext)) { + if (!consoleService.logContains(makeEffectiveResolutionContext)) { + consoleService.log(DebuggerResources.ICON_PROVIDER_BREAKPOINTS, + "There are ineffective breakpoints that can be placed", + makeEffectiveResolutionContext); + } + } + else { + consoleService.removeFromLog(makeEffectiveResolutionContext); + } + } + @AutoServiceConsumed private void setBreakpointService(DebuggerLogicalBreakpointService breakpointService) { if (this.breakpointService != null) { @@ -623,6 +711,15 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } } + @AutoServiceConsumed + private void setConsoleService(DebuggerConsoleService consoleService) { + if (consoleService != null) { + if (actionMakeBreakpointsEffectiveResolution != null) { + consoleService.addResolutionAction(actionMakeBreakpointsEffectiveResolution); + } + } + } + protected void loadBreakpoints() { Set all = breakpointService.getAllBreakpoints(); breakpointTableModel.addAllItems(all); @@ -921,12 +1018,13 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } protected void createActions() { - actionEnableSelectedBreakpointsAction = new EnableSelectedBreakpointsAction(); - actionEnableAllBreakpointsAction = new EnableAllBreakpointsAction(); - actionDisableSelectedBreakpointsAction = new DisableSelectedBreakpointsAction(); - actionDisableAllBreakpointsAction = new DisableAllBreakpointsAction(); - actionClearSelectedBreakpointsAction = new ClearSelectedBreakpointsAction(); - actionClearAllBreakpointsAction = new ClearAllBreakpointsAction(); + actionEnableSelectedBreakpoints = new EnableSelectedBreakpointsAction(); + actionEnableAllBreakpoints = new EnableAllBreakpointsAction(); + actionDisableSelectedBreakpoints = new DisableSelectedBreakpointsAction(); + actionDisableAllBreakpoints = new DisableAllBreakpointsAction(); + actionClearSelectedBreakpoints = new ClearSelectedBreakpointsAction(); + actionClearAllBreakpoints = new ClearAllBreakpointsAction(); + actionMakeBreakpointsEffective = new MakeBreakpointsEffectiveAction(); actionFilterByCurrentTrace = FilterAction.builder(plugin) .toolBarIcon(DebuggerResources.ICON_TRACE) .description("Filter locations to those in current trace") @@ -938,6 +1036,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter .helpLocation(new HelpLocation(plugin.getName(), "filter_by_logical")) .onAction(this::toggledFilterLocationsByBreakpoints) .buildAndInstallLocal(this); + + actionMakeBreakpointsEffectiveResolution = new MakeBreakpointsEffectiveResolutionAction(); } private void toggledFilterByCurrentTrace(ActionContext ignored) { @@ -979,4 +1079,13 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter DebuggerResources.setSelectedRows(sel, locationTableModel::getRow, locationTable, locationTableModel, locationFilterPanel); } + + protected void breakpointError(String title, String message, Throwable ex) { + if (consoleService == null) { + Msg.showError(this, null, title, message, ex); + return; + } + Msg.error(this, message, ex); + consoleService.log(DebuggerResources.ICON_LOG_ERROR, message + " (" + ex + ")"); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerMakeBreakpointsEffectiveActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerMakeBreakpointsEffectiveActionContext.java new file mode 100644 index 0000000000..e6c14bc6f0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerMakeBreakpointsEffectiveActionContext.java @@ -0,0 +1,23 @@ +/* ### + * 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 docking.ActionContext; + +// TODO: Any granularity, or just one suggested action on the global tool? +public class DebuggerMakeBreakpointsEffectiveActionContext extends ActionContext { + // Nothing to add +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java index 486f245cde..2b774d3130 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java @@ -24,7 +24,6 @@ import ghidra.framework.model.DomainObject; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet; -import ghidra.util.Msg; public class LogicalBreakpointRow { private final DebuggerBreakpointsProvider provider; @@ -64,7 +63,7 @@ public class LogicalBreakpointRow { ? lb.enableForTrace(provider.currentTrace) : lb.enable(); future.exceptionally(ex -> { - Msg.showError(this, null, "Toggle Breakpoint", "Could not enable breakpoint", ex); + provider.breakpointError("Toggle Breakpoint", "Could not enable breakpoint", ex); return null; }); } @@ -73,7 +72,7 @@ public class LogicalBreakpointRow { ? lb.disableForTrace(provider.currentTrace) : lb.disable(); future.exceptionally(ex -> { - Msg.showError(this, null, "Toggle Breakpoint", "Could not disable breakpoint", ex); + provider.breakpointError("Toggle Breakpoint", "Could not disable breakpoint", ex); return null; }); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java index 5fd781ca61..eef802210f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java @@ -29,6 +29,7 @@ import docking.ActionContext; import docking.action.DockingActionIf; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow; import ghidra.app.services.DebuggerConsoleService; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -99,14 +100,24 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ super.dispose(); } + @Override + public void log(Icon icon, String message) { + provider.log(icon, message); + } + @Override public void log(Icon icon, String message, ActionContext context) { provider.log(icon, message, context); } @Override - public void remove(ActionContext context) { - provider.remove(context); + public void removeFromLog(ActionContext context) { + provider.removeFromLog(context); + } + + @Override + public boolean logContains(ActionContext context) { + return provider.logContains(context); } @Override @@ -127,4 +138,14 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ public long getRowCount(Class ctxCls) { return provider.getRowCount(ctxCls); } + + /** + * For testing: to verify the contents of a message delivered to the console log + * + * @param ctx the context + * @return the the log entry + */ + public LogRow getLogRow(ActionContext ctx) { + return provider.getLogRow(ctx); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java index 6c0828b153..9fb446e832 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java @@ -56,7 +56,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter static final int ACTION_BUTTON_SIZE = 32; static final Dimension ACTION_BUTTON_DIM = new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE); - static final int MAX_ROW_HEIGHT = 300; + static final int MIN_ROW_HEIGHT = 16; protected enum LogTableColumns implements EnumeratedTableColumn { LEVEL("Level", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false), @@ -109,9 +109,15 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter } } - protected static class BoundAction { - protected final DockingActionIf action; - protected final ActionContext context; + /** + * An action bound to a context + * + *

+ * This class is public for access by test cases only. + */ + public static class BoundAction { + public final DockingActionIf action; + public final ActionContext context; public BoundAction(DockingActionIf action, ActionContext context) { this.action = action; @@ -144,10 +150,22 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter } } - protected static class ActionList extends ArrayList { + /** + * A list of bound actions + * + *

+ * This class is public for access by test cases only. + */ + public static class ActionList extends ArrayList { } - protected static class LogRow { + /** + * An entry in the console's log + * + *

+ * This class is public for access by test cases only. + */ + public static class LogRow { private final Icon icon; private final String message; private final Date date; @@ -224,7 +242,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter int rows = model.getRowCount(); int cols = getColumnCount(); for (int r = 0; r < rows; r++) { - int height = 0; + int height = MIN_ROW_HEIGHT; for (int c = 0; c < cols; c++) { height = Math.max(height, computePreferredHeight(r, c)); } @@ -382,6 +400,10 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter } } + protected void log(Icon icon, String message) { + log(icon, message, new LogRowConsoleActionContext()); + } + protected void log(Icon icon, String message, ActionContext context) { logRow(new LogRow(icon, message, new Date(), context, computeToolbarActions(context))); } @@ -419,13 +441,19 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter new Date(event.getTimeMillis()), context, computeToolbarActions(context))); } - protected void remove(ActionContext context) { + protected void removeFromLog(ActionContext context) { synchronized (buffer) { LogRow r = logTableModel.deleteKey(context); buffer.remove(r); } } + protected boolean logContains(ActionContext context) { + synchronized (buffer) { + return logTableModel.getMap().containsKey(context); + } + } + protected void addResolutionAction(DockingActionIf action) { DockingActionIf replaced = actionsByOwnerThenName.computeIfAbsent(action.getOwner(), o -> new LinkedHashMap<>()) @@ -479,9 +507,17 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter } protected long getRowCount(Class ctxCls) { - return logTableModel.getModelData() - .stream() - .filter(r -> ctxCls.isInstance(r.context)) - .count(); + synchronized (buffer) { + return logTableModel.getModelData() + .stream() + .filter(r -> ctxCls.isInstance(r.context)) + .count(); + } + } + + public LogRow getLogRow(ActionContext ctx) { + synchronized (buffer) { + return logTableModel.getMap().get(ctx); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index 39dd501520..9f1a6184bf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -65,6 +65,7 @@ import utilities.util.SuppressableCallback.Suppression; eventsConsumed = { // ProgramSelectionPluginEvent.class, // TODO: Later or remove // ProgramHighlightPluginEvent.class, // TODO: Later or remove + ProgramOpenedPluginEvent.class, // For auto-open log cleanup ProgramClosedPluginEvent.class, // For marker set cleanup ProgramLocationPluginEvent.class, // For static listing sync TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking @@ -275,6 +276,10 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger } }); } + if (event instanceof ProgramOpenedPluginEvent) { + ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event; + allProviders(p -> p.programOpened(ev.getProgram())); + } if (event instanceof ProgramClosedPluginEvent) { ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event; allProviders(p -> p.programClosed(ev.getProgram())); 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 dcf3f6799a..5353d98231 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 @@ -50,8 +50,7 @@ import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; -import ghidra.app.plugin.core.debug.utils.BackgroundUtils; -import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; +import ghidra.app.plugin.core.debug.utils.*; import ghidra.app.plugin.core.exporter.ExporterDialog; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.*; @@ -86,6 +85,9 @@ import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.util.TraceAddressSpace; import ghidra.util.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.*; import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback.Suppression; @@ -353,6 +355,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi protected FollowsCurrentThreadAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; protected DockingAction actionExportView; + protected DockingAction actionOpenProgram; protected final DebuggerGoToDialog goToDialog; @@ -586,6 +589,15 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } + @AutoServiceConsumed + private void setConsoleService(DebuggerConsoleService consoleService) { + if (consoleService != null) { + if (actionOpenProgram != null) { + consoleService.addResolutionAction(actionOpenProgram); + } + } + } + protected void markTrackedStaticLocation(ProgramLocation location) { Swing.runIfSwingOrRunLater(() -> { if (location == null) { @@ -607,6 +619,17 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi }); } + public void programOpened(Program program) { + if (!isMainListing()) { + return; + } + DomainFile df = program.getDomainFile(); + DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df); + if (consoleService != null) { + consoleService.removeFromLog(ctx); + } + } + public void programClosed(Program program) { if (program == markedProgram) { removeOldStaticTrackingMarker(); @@ -784,6 +807,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi .onAction(this::activatedExportView) .buildAndInstallLocal(this); + actionOpenProgram = OpenProgramAction.builder(plugin) + .withContext(DebuggerOpenProgramActionContext.class) + .onAction(this::activatedOpenProgram) + .build(); + contextChanged(); } @@ -813,6 +841,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi tool.showDialog(dialog); } + private void activatedOpenProgram(DebuggerOpenProgramActionContext context) { + programManager.openProgram(context.getDomainFile(), DomainFile.DEFAULT_VERSION, + ProgramManager.OPEN_CURRENT); + } + protected void activatedLocationTracking(ActionContext ctx) { doTrackSpec(); } @@ -901,7 +934,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi public void programLocationChanged(ProgramLocation location, EventTrigger trigger) { updateLocationLabel(); if (traceManager != null) { - location = traceManager.fixLocation(location, false); + location = ProgramLocationUtils.fixLocation(location, false); } super.programLocationChanged(location, trigger); if (trigger == EventTrigger.GUI_ACTION) { @@ -935,7 +968,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } protected void doSyncToStatic(ProgramLocation location) { - if (syncToStaticListing && location != null) { + if (isSyncToStaticListing() && location != null) { ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location); if (staticLoc != null) { Swing.runIfSwingOrRunLater(() -> plugin.fireStaticLocationEvent(staticLoc)); @@ -943,8 +976,58 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } + protected void doTryOpenProgram(DomainFile df, int version, int state) { + DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df); + if (consoleService != null && consoleService.logContains(ctx)) { + return; + } + if (df.canRecover()) { + if (consoleService != null) { + consoleService.log(DebuggerResources.ICON_MODULES, "Program " + + HTMLUtilities.escapeHTML(df.getPathname()) + + " has recovery data. It must be opened manually.", ctx); + } + return; + } + new TaskLauncher(new Task("Open " + df, true, false, false) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + Program program = null; + try { + program = (Program) df.getDomainObject(this, false, false, monitor); + programManager.openProgram(program, state); + } + catch (VersionException e) { + if (consoleService != null) { + consoleService.log(DebuggerResources.ICON_MODULES, "Program " + + HTMLUtilities.escapeHTML(df.getPathname()) + + " was created with a different version of Ghidra." + + " It must be opened manually.", ctx); + } + return; + } + catch (Exception e) { + if (consoleService != null) { + consoleService.log(DebuggerResources.ICON_LOG_ERROR, "Program " + + HTMLUtilities.escapeHTML(df.getPathname()) + + " could not be opened: " + e + ". Try opening it manually.", + ctx); + } + return; + } + finally { + if (program != null) { + program.release(this); + } + } + } + }, tool.getToolFrame()); + } + protected void doCheckCurrentModuleMissing() { - if (importerService == null || consoleService == null) { + // Is there any reason to try to open the module if we're not syncing listings? + // I don't think so. + if (!isSyncToStaticListing()) { return; } Trace trace = current.getTrace(); @@ -968,9 +1051,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi DomainFile df = ProgramURLUtils.getFileForHackedUpGhidraURL(tool.getProject(), mapping.getStaticProgramURL()); if (df != null) { - // We're almost certainly preparing to goTo, so make it current - programManager.openProgram(df, DomainFile.DEFAULT_VERSION, - ProgramManager.OPEN_CURRENT); + doTryOpenProgram(df, DomainFile.DEFAULT_VERSION, ProgramManager.OPEN_CURRENT); } } @@ -996,14 +1077,19 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi if (programManager != null && !toOpen.isEmpty()) { for (DomainFile df : toOpen) { // Do not presume a goTo is about to happen. There are no mappings, yet. - programManager.openProgram(df, DomainFile.DEFAULT_VERSION, + doTryOpenProgram(df, DomainFile.DEFAULT_VERSION, ProgramManager.OPEN_VISIBLE); } } + + if (importerService == null || consoleService == null) { + return; + } + for (TraceModule mod : missing) { consoleService.log(DebuggerResources.ICON_LOG_ERROR, "The module " + HTMLUtilities.escapeHTML(mod.getName()) + - " was not found in the project", + " was not found in the project", new DebuggerMissingModuleActionContext(mod)); } /** @@ -1145,6 +1231,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi public void dispose() { super.dispose(); removeOldListeners(); + if (consoleService != null) { + if (actionOpenProgram != null) { + consoleService.removeResolutionAction(actionOpenProgram); + } + } } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerOpenProgramActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerOpenProgramActionContext.java new file mode 100644 index 0000000000..8da682cdf8 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerOpenProgramActionContext.java @@ -0,0 +1,55 @@ +/* ### + * 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.listing; + +import java.util.Objects; + +import docking.ActionContext; +import ghidra.framework.model.DomainFile; + +public class DebuggerOpenProgramActionContext extends ActionContext { + private final DomainFile df; + private final int hashCode; + + public DebuggerOpenProgramActionContext(DomainFile df) { + this.df = df; + this.hashCode = Objects.hash(getClass(), df); + } + + public DomainFile getDomainFile() { + return df; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DebuggerOpenProgramActionContext)) { + return false; + } + DebuggerOpenProgramActionContext that = (DebuggerOpenProgramActionContext) obj; + if (!this.df.equals(that.df)) { + return false; + } + return true; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java index 6b201f6226..5a59e66f05 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.modules; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; @@ -34,18 +35,17 @@ import ghidra.util.Swing; public class DebuggerModuleMapProposalDialog extends AbstractDebuggerMapProposalDialog { - static final String BLANK = ""; static final int BUTTON_SIZE = 32; protected enum ModuleMapTableColumns implements EnumeratedTableColumn { - REMOVE("Remove", String.class, e -> BLANK, (e, v) -> nop()), + REMOVE("Remove", String.class, e -> "Remove Proposed Entry", (e, v) -> nop()), MODULE_NAME("Module", String.class, e -> e.getModule().getName()), DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getModule().getBase()), + CHOOSE("Choose", String.class, e -> "Choose Program", (e, v) -> nop()), PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()), STATIC_BASE("Static Base", Address.class, e -> e.getProgram().getImageBase()), - SIZE("Size", Long.class, e -> e.getModuleRange().getLength()), - CHOOSE("Choose", String.class, e -> BLANK, (e, v) -> nop()); + SIZE("Size", Long.class, e -> e.getModuleRange().getLength()); private final String header; private final Class cls; @@ -94,6 +94,19 @@ public class DebuggerModuleMapProposalDialog } } + protected static class ModuleMapPropsalTableModel extends + DefaultEnumeratedColumnTableModel { + + public ModuleMapPropsalTableModel() { + super("Module Map", ModuleMapTableColumns.class); + } + + @Override + public List defaultSortOrder() { + return List.of(ModuleMapTableColumns.MODULE_NAME); + } + } + private final DebuggerModulesProvider provider; protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) { @@ -102,8 +115,8 @@ public class DebuggerModuleMapProposalDialog } @Override - protected EnumeratedColumnTableModel createTableModel() { - return new DefaultEnumeratedColumnTableModel<>("Module Map", ModuleMapTableColumns.class); + protected ModuleMapPropsalTableModel createTableModel() { + return new ModuleMapPropsalTableModel(); } @Override @@ -117,9 +130,19 @@ public class DebuggerModuleMapProposalDialog CellEditorUtils.installButton(table, filterPanel, removeCol, DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeEntry); + TableColumn dynBaseCol = + columnModel.getColumn(ModuleMapTableColumns.DYNAMIC_BASE.ordinal()); + dynBaseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); + TableColumn chooseCol = columnModel.getColumn(ModuleMapTableColumns.CHOOSE.ordinal()); CellEditorUtils.installButton(table, filterPanel, chooseCol, DebuggerResources.ICON_PROGRAM, BUTTON_SIZE, this::chooseAndSetProgram); + + TableColumn stBaseCol = columnModel.getColumn(ModuleMapTableColumns.STATIC_BASE.ordinal()); + stBaseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); + + TableColumn sizeCol = columnModel.getColumn(ModuleMapTableColumns.SIZE.ordinal()); + sizeCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX); } private void chooseAndSetProgram(ModuleMapEntry entry) { 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 b463b4c270..c647351cd5 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 @@ -867,12 +867,12 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { Msg.error(this, "Import service is not present"); } importModuleFromFileSystem(context.getModule()); - consoleService.remove(context); // TODO: Should remove when mapping is created + consoleService.removeFromLog(context); // TODO: Should remove when mapping is created } private void activatedMapMissingModule(DebuggerMissingModuleActionContext context) { mapModuleTo(context.getModule()); - consoleService.remove(context); // TODO: Should remove when mapping is created + consoleService.removeFromLog(context); // TODO: Should remove when mapping is created } private void toggledFilter(ActionContext ignored) { @@ -972,12 +972,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } protected void promptModuleProposal(Collection proposal) { + 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."); + return; + } Collection adjusted = moduleProposalDialog.adjustCollection(getTool(), proposal); - if (adjusted != null && staticMappingService != null) { - tool.executeBackgroundCommand( - new MapModulesBackgroundCommand(staticMappingService, adjusted), currentTrace); + if (adjusted == null || staticMappingService == null) { + return; } + tool.executeBackgroundCommand( + new MapModulesBackgroundCommand(staticMappingService, adjusted), currentTrace); } protected void mapModules(Set modules) { @@ -1004,12 +1011,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { } protected void promptSectionProposal(Collection proposal) { + if (proposal.isEmpty()) { + Msg.showInfo(this, getComponent(), "Map Sections", + "Could not formulate a proposal for any selected section." + + " You may need to import and/or open the destination images first."); + return; + } Collection adjusted = sectionProposalDialog.adjustCollection(getTool(), proposal); - if (adjusted != null && staticMappingService != null) { - tool.executeBackgroundCommand( - new MapSectionsBackgroundCommand(staticMappingService, adjusted), currentTrace); + if (adjusted == null || staticMappingService == null) { + return; } + tool.executeBackgroundCommand( + new MapSectionsBackgroundCommand(staticMappingService, adjusted), currentTrace); } protected void mapSections(Set sections) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java index 04ee111009..717823c458 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.modules; +import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Function; @@ -35,20 +36,19 @@ import ghidra.util.Swing; public class DebuggerSectionMapProposalDialog extends AbstractDebuggerMapProposalDialog { - static final String BLANK = ""; static final int BUTTON_SIZE = 32; protected enum SectionMapTableColumns implements EnumeratedTableColumn { - REMOVE("Remove", String.class, e -> BLANK, (e, v) -> nop()), + REMOVE("Remove", String.class, e -> "Remove Proposed Entry", (e, v) -> nop()), MODULE_NAME("Module", String.class, e -> e.getModule().getName()), SECTION_NAME("Section", String.class, e -> e.getSection().getName()), DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getSection().getStart()), + CHOOSE("Choose", String.class, e -> "Choose Block", (e, s) -> nop()), PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()), BLOCK_NAME("Block", String.class, e -> e.getBlock().getName()), STATIC_BASE("Static Base", Address.class, e -> e.getBlock().getStart()), - SIZE("Size", Long.class, e -> e.getLength()), - CHOOSE("Choose", String.class, e -> BLANK, (e, s) -> nop()); + SIZE("Size", Long.class, e -> e.getLength()); private final String header; private final Class cls; @@ -98,6 +98,19 @@ public class DebuggerSectionMapProposalDialog } } + protected static class SectionMapPropsalTableModel extends + DefaultEnumeratedColumnTableModel { + + public SectionMapPropsalTableModel() { + super("Section Map", SectionMapTableColumns.class); + } + + @Override + public List defaultSortOrder() { + return List.of(SectionMapTableColumns.MODULE_NAME, SectionMapTableColumns.SECTION_NAME); + } + } + private final DebuggerModulesProvider provider; public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) { @@ -106,8 +119,8 @@ public class DebuggerSectionMapProposalDialog } @Override - protected EnumeratedColumnTableModel createTableModel() { - return new DefaultEnumeratedColumnTableModel<>("Section Map", SectionMapTableColumns.class); + protected SectionMapPropsalTableModel createTableModel() { + return new SectionMapPropsalTableModel(); } @Override @@ -121,9 +134,19 @@ public class DebuggerSectionMapProposalDialog CellEditorUtils.installButton(table, filterPanel, removeCol, DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeEntry); + TableColumn dynBaseCol = + columnModel.getColumn(SectionMapTableColumns.DYNAMIC_BASE.ordinal()); + dynBaseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); + TableColumn chooseCol = columnModel.getColumn(SectionMapTableColumns.CHOOSE.ordinal()); CellEditorUtils.installButton(table, filterPanel, chooseCol, DebuggerResources.ICON_PROGRAM, BUTTON_SIZE, this::chooseAndSetBlock); + + TableColumn stBaseCol = columnModel.getColumn(SectionMapTableColumns.STATIC_BASE.ordinal()); + stBaseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); + + TableColumn sizeCol = columnModel.getColumn(SectionMapTableColumns.SIZE.ordinal()); + sizeCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX); } private void chooseAndSetBlock(SectionMapEntry entry) { 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 a639ab81a8..db35964926 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 @@ -1118,7 +1118,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin @Override public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) { - loc = traceManager.fixLocation(loc, true); + loc = ProgramLocationUtils.fixLocation(loc, true); TraceProgramView view = (TraceProgramView) loc.getProgram(); Trace trace = view.getTrace(); TraceLocation tloc = new DefaultTraceLocation(trace, 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 8935725d04..cf3d8695b2 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 @@ -31,7 +31,6 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; -import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.services.*; import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec; import ghidra.async.AsyncReference; @@ -47,8 +46,6 @@ import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.lifecycle.Internal; -import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TraceThreadChangeType; import ghidra.trace.model.TraceDomainObjectListener; @@ -1147,23 +1144,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin autoCloseOnTerminate.removeChangeListener(listener); } - @Override - // TODO: Move this into some static util, now that canonical view is a trace concept - public ProgramLocation fixLocation(ProgramLocation location, boolean matchSnap) { - Program program = location.getProgram(); - if (!(program instanceof TraceProgramView)) { - return location; - } - TraceProgramView itsView = (TraceProgramView) program; - Trace trace = itsView.getTrace(); - TraceProgramView canonicalView = trace.getProgramView(); - if (canonicalView == itsView || - (matchSnap && canonicalView.getSnap() != itsView.getSnap())) { - return location; - } - return ProgramLocationUtils.replaceProgram(location, canonicalView); - } - @Override public boolean canClose() { if (isSaveTracesByDefault()) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramLocationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramLocationUtils.java index c641d70aa4..dadbdd614b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramLocationUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramLocationUtils.java @@ -18,20 +18,48 @@ package ghidra.app.plugin.core.debug.utils; import ghidra.framework.options.SaveState; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; +import ghidra.program.util.BytesFieldLocation; import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.Trace; +import ghidra.trace.model.program.TraceProgramView; public enum ProgramLocationUtils { ; public static ProgramLocation replaceAddress(ProgramLocation loc, Program program, Address address) { - // HACK: ... and a half - SaveState state = new SaveState("LOC"); - loc.saveState(state); - state.putString("_ADDRESS", address.toString()); - state.putString("_BYTE_ADDR", - address.add(loc.getByteAddress().subtract(loc.getAddress())).toString()); - return ProgramLocation.getLocation(program, state); + // Outside of byte fields, I really don't care + if (loc instanceof BytesFieldLocation) { + return new BytesFieldLocation(program, address); + } + return new ProgramLocation(program, address); + } + + /** + * Swap out the trace view of a {@link ProgramLocation} if it is not the canonical view + * + *

+ * If the program location is not associated with a trace, the same location is returned. + * Otherwise, this ensures that the given view is the canonical one for the same trace. If + * matchSnap is true, the view is only replaced when the replacement shares the same snap. + * + * @param location a location possibly in a trace view + * @param matchSnap true to only replace is snap matches, false to always replace + * @return the adjusted location + */ + public static ProgramLocation fixLocation(ProgramLocation loc, boolean matchSnap) { + Program program = loc.getProgram(); + if (!(program instanceof TraceProgramView)) { + return loc; + } + TraceProgramView itsView = (TraceProgramView) program; + Trace trace = itsView.getTrace(); + TraceProgramView canonicalView = trace.getProgramView(); + if (canonicalView == itsView || + (matchSnap && canonicalView.getSnap() != itsView.getSnap())) { + return loc; + } + return replaceProgram(loc, canonicalView); } public static ProgramLocation replaceProgram(ProgramLocation loc, Program program) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java index ad1654599b..beb7713279 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java @@ -27,6 +27,17 @@ import ghidra.util.HTMLUtilities; @ServiceInfo(defaultProvider = DebuggerConsolePlugin.class) public interface DebuggerConsoleService extends DebuggerConsoleLogger { + /** + * Log a message to the console + * + *

+ * WARNING: See {@link #log(Icon, String, ActionContext)} regarding HTML. + * + * @param icon an icon for the message + * @param message the HTML-formatted message + */ + void log(Icon icon, String message); + /** * Log an actionable message to the console * @@ -51,7 +62,15 @@ public interface DebuggerConsoleService extends DebuggerConsoleLogger { * * @param context the context of the entry to remove */ - void remove(ActionContext context); + void removeFromLog(ActionContext context); + + /** + * Check if the console contains an actionable message for the given context + * + * @param context the context to check for + * @return true if present, false if absent + */ + boolean logContains(ActionContext context); /** * Add an action which might be applied to an actionable log message diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index 722912e048..7ef5c3d2aa 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -22,7 +22,6 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.framework.model.DomainFile; import ghidra.framework.plugintool.ServiceInfo; -import ghidra.program.util.ProgramLocation; import ghidra.trace.model.Trace; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.thread.TraceThread; @@ -195,21 +194,6 @@ public interface DebuggerTraceManagerService { void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); - /** - * Swap out the trace view of a {@link ProgramLocation} if it is not the debugger's view - * - *

- * If the program location is not associated with a trace, the same location is returned. - * Otherwise, this ensures that the given view is the one found in the debugger plugin for the - * same trace. If matchSnap is true, the view is only replaced when the replacement shares the - * same snap. - * - * @param location a location possibly in a trace view - * @param matchSnap true to only replace is snap matches, false to always replace - * @return the adjusted location - */ - ProgramLocation fixLocation(ProgramLocation location, boolean matchSnap); - /** * Fill in an incomplete coordinate specification, using the manager's "best judgement" * diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/breakpoints-make-effective.png b/Ghidra/Debug/Debugger/src/main/resources/images/breakpoints-make-effective.png new file mode 100644 index 0000000000..b1e1ab4ede Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/breakpoints-make-effective.png differ diff --git a/Ghidra/Debug/Debugger/src/main/svg/breakpoints-make-effective.svg b/Ghidra/Debug/Debugger/src/main/svg/breakpoints-make-effective.svg new file mode 100644 index 0000000000..cfb79a2e09 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/svg/breakpoints-make-effective.svg @@ -0,0 +1,64 @@ + + + + + + + + image/svg+xml + + + + + + + + + + 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 index 235171dc9a..afa7879316 100644 --- 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 @@ -31,6 +31,7 @@ import generic.Unique; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; 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.services.*; import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.async.AsyncTestUtils; @@ -260,12 +261,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge programManager.openProgram(program); waitForSwing(); - assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); addStaticMemoryAndBreakpoint(); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); LogicalBreakpointRow row = Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); @@ -274,28 +275,28 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge waitForSwing(); assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); - performAction(breakpointsProvider.actionEnableSelectedBreakpointsAction); + performAction(breakpointsProvider.actionEnableSelectedBreakpoints); assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); breakpointsProvider.breakpointTable.clearSelection(); waitForSwing(); - assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); waitForSwing(); - assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); // Bookmark part should actually be synchronous. waitOn(row.getLogicalBreakpoint().delete()); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled()); } @Test @@ -304,12 +305,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge programManager.openProgram(program); waitForSwing(); - assertFalse(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled()); addStaticMemoryAndBreakpoint(); waitForDomainObject(program); - assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled()); LogicalBreakpointRow row = Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); @@ -317,18 +318,18 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge waitForSwing(); assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled()); - performAction(breakpointsProvider.actionEnableAllBreakpointsAction); + performAction(breakpointsProvider.actionEnableAllBreakpoints); assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled()); // Bookmark part should actually be synchronous. row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled()); } @Test @@ -337,12 +338,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge programManager.openProgram(program); waitForSwing(); - assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); addStaticMemoryAndBreakpoint(); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); LogicalBreakpointRow row = Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); @@ -350,28 +351,28 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge waitForSwing(); assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); - performAction(breakpointsProvider.actionDisableSelectedBreakpointsAction); + performAction(breakpointsProvider.actionDisableSelectedBreakpoints); assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); breakpointsProvider.breakpointTable.clearSelection(); waitForSwing(); - assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); waitForSwing(); - assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); // Bookmark part should actually be synchronous. waitOn(row.getLogicalBreakpoint().delete()); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled()); } @Test @@ -380,27 +381,27 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge programManager.openProgram(program); waitForSwing(); - assertFalse(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled()); addStaticMemoryAndBreakpoint(); waitForDomainObject(program); - assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled()); LogicalBreakpointRow row = Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled()); - performAction(breakpointsProvider.actionDisableAllBreakpointsAction); + performAction(breakpointsProvider.actionDisableAllBreakpoints); assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement()); - assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled()); // Bookmark part should actually be synchronous. row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled()); } @Test @@ -409,34 +410,34 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge programManager.openProgram(program); waitForSwing(); - assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled()); addStaticMemoryAndBreakpoint(); waitForDomainObject(program); - assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled()); LogicalBreakpointRow row = Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); waitForSwing(); - assertTrue(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled()); breakpointsProvider.breakpointTable.clearSelection(); waitForSwing(); - assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled()); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); waitForSwing(); - assertTrue(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled()); - performAction(breakpointsProvider.actionClearSelectedBreakpointsAction); + performAction(breakpointsProvider.actionClearSelectedBreakpoints); assertProviderEmpty(); - assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled()); } @Test @@ -445,17 +446,54 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge programManager.openProgram(program); waitForSwing(); - assertFalse(breakpointsProvider.actionClearAllBreakpointsAction.isEnabled()); + assertFalse(breakpointsProvider.actionClearAllBreakpoints.isEnabled()); addStaticMemoryAndBreakpoint(); waitForDomainObject(program); - assertTrue(breakpointsProvider.actionClearAllBreakpointsAction.isEnabled()); + assertTrue(breakpointsProvider.actionClearAllBreakpoints.isEnabled()); - performAction(breakpointsProvider.actionClearAllBreakpointsAction); + performAction(breakpointsProvider.actionClearAllBreakpoints); assertProviderEmpty(); - assertFalse(breakpointsProvider.actionClearAllBreakpointsAction.isEnabled()); + 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, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + 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)); + }); } @Test 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 902cc2fadd..fc80668f95 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 @@ -19,6 +19,7 @@ import static ghidra.lifecycle.Unfinished.TODO; import static org.junit.Assert.*; import java.awt.*; +import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Arrays; @@ -37,10 +38,13 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; 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.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.async.SwingExecutorService; +import ghidra.framework.model.*; import ghidra.plugin.importer.ImporterPlugin; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; @@ -57,6 +61,9 @@ import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest { static LocationTrackingSpec getLocationTrackingSpec(String name) { @@ -1351,4 +1358,127 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress()); } + + @Test + public void testSyncToStaticListingOpensModule() throws Exception { + DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); + + createAndOpenTrace(); + createAndOpenProgramFromTrace(); + intoProject(tb.trace); + intoProject(program); + + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) { + program.getMemory() + .createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0, + monitor, false); + } + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + TraceLocation from = + new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); + ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); + mappingService.addMapping(from, to, 0x8000, false); + } + waitForProgram(program); + waitForDomainObject(tb.trace); + + programManager.closeAllPrograms(true); + waitForPass(() -> assertEquals(0, programManager.getAllOpenPrograms().length)); + + traceManager.activateTrace(tb.trace); + waitForSwing(); + + listingProvider.getListingPanel() + .setCursorPosition( + new ProgramLocation(tb.trace.getProgramView(), tb.addr(0x00401234)), + EventTrigger.GUI_ACTION); + waitForSwing(); + + waitForPass(() -> assertEquals(1, programManager.getAllOpenPrograms().length)); + assertTrue(java.util.List.of(programManager.getAllOpenPrograms()).contains(program)); + + assertFalse(consolePlugin + .logContains(new DebuggerOpenProgramActionContext(program.getDomainFile()))); + } + + @Test + public void testSyncToStaticLogsRecoverableProgram() throws Exception { + DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); + + TestDummyDomainFolder root = new TestDummyDomainFolder(null, "root"); + DomainFile df = new TestDummyDomainFile(root, "dummyFile") { + @Override + public boolean canRecover() { + return true; + } + }; + + listingProvider.doTryOpenProgram(df, DomainFile.DEFAULT_VERSION, + ProgramManager.OPEN_CURRENT); + waitForSwing(); + + DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df); + waitForPass(() -> assertTrue(consolePlugin.logContains(ctx))); + assertTrue(consolePlugin.getLogRow(ctx).getMessage().contains("recovery")); + } + + @Test + public void testSyncToStaticLogsUpgradeableProgram() throws Exception { + DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); + + TestDummyDomainFolder root = new TestDummyDomainFolder(null, "root"); + DomainFile df = new TestDummyDomainFile(root, "dummyFile") { + @Override + public boolean canRecover() { + return false; + } + + @Override + public DomainObject getDomainObject(Object consumer, boolean okToUpgrade, + boolean okToRecover, TaskMonitor monitor) + throws VersionException, IOException, CancelledException { + throw new VersionException(); + } + }; + + listingProvider.doTryOpenProgram(df, DomainFile.DEFAULT_VERSION, + ProgramManager.OPEN_CURRENT); + waitForSwing(); + + DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df); + waitForPass(() -> assertTrue(consolePlugin.logContains(ctx))); + assertTrue(consolePlugin.getLogRow(ctx).getMessage().contains("version")); + } + + @Test + public void testActionOpenProgram() throws Exception { + DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); + + createProgram(); + intoProject(program); + + assertEquals(0, programManager.getAllOpenPrograms().length); + + DebuggerOpenProgramActionContext ctx = + new DebuggerOpenProgramActionContext(program.getDomainFile()); + consolePlugin.log(DebuggerResources.ICON_MODULES, "Test resolution", ctx); + waitForSwing(); + + LogRow row = consolePlugin.getLogRow(ctx); + assertEquals(1, row.getActions().size()); + BoundAction boundAction = row.getActions().get(0); + assertEquals(listingProvider.actionOpenProgram, boundAction.action); + + boundAction.perform(); + waitForSwing(); + + waitForPass(() -> assertEquals(1, programManager.getAllOpenPrograms().length)); + assertTrue(java.util.List.of(programManager.getAllOpenPrograms()).contains(program)); + // TODO: Test this independent of this particular action? + assertNull(consolePlugin.getLogRow(ctx)); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellEditor.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellEditor.java index 1ddcd6de77..983f753c6e 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellEditor.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellEditor.java @@ -51,6 +51,7 @@ public class IconButtonTableCellEditor extends AbstractCellEditor public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { this.row = filterPanel.getRowObject(row); + button.setToolTipText(value.toString()); return button; } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellRenderer.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellRenderer.java index bba951ea83..d471463b68 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellRenderer.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/IconButtonTableCellRenderer.java @@ -15,27 +15,31 @@ */ package docking.widgets.table; -import java.awt.Component; -import java.awt.Dimension; +import java.awt.*; -import javax.swing.Icon; -import javax.swing.JButton; +import javax.swing.*; import ghidra.docking.settings.Settings; import ghidra.util.table.column.AbstractGhidraColumnRenderer; public class IconButtonTableCellRenderer extends AbstractGhidraColumnRenderer { + protected final JPanel panel = new JPanel(); protected final JButton button = new JButton(""); public IconButtonTableCellRenderer(Icon icon, int buttonSize) { button.setIcon(icon); button.setMinimumSize(new Dimension(buttonSize, buttonSize)); + panel.setMinimumSize(new Dimension(buttonSize, buttonSize)); + panel.setLayout(new BorderLayout()); + panel.add(button); } @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { - return button; + super.getTableCellRendererComponent(data); // Waste, but sets background + panel.setBackground(getBackground()); + return panel; } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/ByteBufferUtils.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/ByteBufferUtils.java new file mode 100644 index 0000000000..9f1f97e9c7 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/ByteBufferUtils.java @@ -0,0 +1,94 @@ +/* ### + * 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.util; + +import java.nio.ByteBuffer; + +/** + * Some utilities for manipulating a {@link ByteBuffer} + */ +public interface ByteBufferUtils { + /** + * Resize a write-mode buffer + * + *

+ * This preserves the buffer contents + * + * @param buf the buffer + * @param capacity the new capacity, greater or equal to the buffer's limit + * @return the new buffer + */ + public static ByteBuffer resize(ByteBuffer buf, int capacity) { + if (capacity < buf.limit()) { + throw new IllegalArgumentException("New capacity must fit current contents"); + } + buf.flip(); + ByteBuffer resized = ByteBuffer.allocate(capacity); + resized.put(buf); + return resized; + } + + /** + * Resize a write-mode buffer to twice its current capacity + * + *

+ * This preserves the buffer contents + * + * @param buf the buffer + * @return the new buffer + */ + public static ByteBuffer upsize(ByteBuffer buf) { + return resize(buf, buf.capacity() * 2); + } + + /** + * Checks for equality, with a mask applied + * + *

+ * This considers the entire contents of both buffers without regard for position or limit. Both + * buffers must have equal capacities to be considered equal. The mask, if given, must have + * capacity equal to that of the first buffer {@code a} or an exception is thrown. + * + * @param mask a buffer containing the mask, or null to match all bytes exactly + * @param a the first buffer + * @param b the second buffer + * @return true if matches, false otherwise + * @throws IllegalArgumentException if {@code mask} and {@code a} have unequal capacities + */ + public static boolean maskedEquals(ByteBuffer mask, ByteBuffer a, ByteBuffer b) { + int len = a.capacity(); + if (mask != null && mask.capacity() != len) { + throw new IllegalArgumentException("mask and a must have equal capacities"); + } + if (len != a.capacity()) { + return false; + } + if (mask != null) { + for (int i = 0; i < len; i++) { + if ((a.get(i) & mask.get(i)) != (b.get(i) & mask.get(i))) { + return false; + } + } + return true; + } + for (int i = 0; i < len; i++) { + if (a.get(i) != b.get(i)) { + return false; + } + } + return true; + } +}