Merge remote-tracking branch 'origin/debugger'

This commit is contained in:
ghidra1
2021-05-19 13:40:17 -04:00
38 changed files with 1044 additions and 202 deletions
@@ -54,7 +54,7 @@ jar {
task configureNodepJar { task configureNodepJar {
doLast { doLast {
configurations.runtimeOnly.files.forEach { configurations.default.files.forEach {
if (filterJar(it)) { if (filterJar(it)) {
nodepJar.from(zipTree(it)) nodepJar.from(zipTree(it))
} }
@@ -15,10 +15,10 @@
*/ */
package agent.dbgeng.manager.cmd; package agent.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.manager.DbgEvent; import agent.dbgeng.manager.DbgEvent;
import agent.dbgeng.manager.DbgManager; import agent.dbgeng.manager.DbgManager;
import agent.dbgeng.manager.evt.AbstractDbgCompletedCommandEvent; import agent.dbgeng.manager.evt.*;
import agent.dbgeng.manager.evt.DbgConsoleOutputEvent;
import agent.dbgeng.manager.impl.DbgManagerImpl; import agent.dbgeng.manager.impl.DbgManagerImpl;
/** /**
@@ -63,6 +63,8 @@ public class DbgConsoleExecCommand extends AbstractDbgCommand<String> {
@Override @Override
public void invoke() { public void invoke() {
manager.getControl().execute(command); DebugControl control = manager.getControl();
control.execute(command);
manager.processEvent(new DbgPromptChangedEvent(control.getPromptText()));
} }
} }
@@ -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<String> {
public DbgPromptChangedEvent(String prompt) {
super(prompt);
}
public String getPrompt() {
return getInfo();
}
}
@@ -41,7 +41,8 @@ import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo;
import agent.dbgeng.manager.breakpoint.DbgBreakpointType; import agent.dbgeng.manager.breakpoint.DbgBreakpointType;
import agent.dbgeng.manager.cmd.*; import agent.dbgeng.manager.cmd.*;
import agent.dbgeng.manager.evt.*; 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.DbgModelTargetObject;
import agent.dbgeng.model.iface2.DbgModelTargetThread; import agent.dbgeng.model.iface2.DbgModelTargetThread;
import ghidra.async.*; import ghidra.async.*;
@@ -597,6 +598,7 @@ public class DbgManagerImpl implements DbgManager {
handlerMap.putVoid(DbgStoppedEvent.class, this::processDefault); handlerMap.putVoid(DbgStoppedEvent.class, this::processDefault);
handlerMap.putVoid(DbgRunningEvent.class, this::processDefault); handlerMap.putVoid(DbgRunningEvent.class, this::processDefault);
handlerMap.putVoid(DbgConsoleOutputEvent.class, this::processConsoleOutput); handlerMap.putVoid(DbgConsoleOutputEvent.class, this::processConsoleOutput);
handlerMap.putVoid(DbgPromptChangedEvent.class, this::processPromptChanged);
handlerMap.putVoid(DbgBreakpointCreatedEvent.class, this::processBreakpointCreated); handlerMap.putVoid(DbgBreakpointCreatedEvent.class, this::processBreakpointCreated);
handlerMap.putVoid(DbgBreakpointModifiedEvent.class, this::processBreakpointModified); handlerMap.putVoid(DbgBreakpointModifiedEvent.class, this::processBreakpointModified);
handlerMap.putVoid(DbgBreakpointDeletedEvent.class, this::processBreakpointDeleted); handlerMap.putVoid(DbgBreakpointDeletedEvent.class, this::processBreakpointDeleted);
@@ -925,6 +927,7 @@ public class DbgManagerImpl implements DbgManager {
dbgState = DbgState.STOPPED; dbgState = DbgState.STOPPED;
//System.err.println("STOPPED " + id); //System.err.println("STOPPED " + id);
processEvent(new DbgStoppedEvent(eventThread.getId())); processEvent(new DbgStoppedEvent(eventThread.getId()));
processEvent(new DbgPromptChangedEvent(getControl().getPromptText()));
} }
if (status.threadState.equals(ExecutionState.RUNNING)) { if (status.threadState.equals(ExecutionState.RUNNING)) {
//System.err.println("RUNNING " + id); //System.err.println("RUNNING " + id);
@@ -949,6 +952,7 @@ public class DbgManagerImpl implements DbgManager {
if (process != null) { if (process != null) {
processEvent(new DbgProcessSelectedEvent(process)); processEvent(new DbgProcessSelectedEvent(process));
} }
processEvent(new DbgPromptChangedEvent(getControl().getPromptText()));
return DebugStatus.BREAK; return DebugStatus.BREAK;
} }
if (status.equals(DebugStatus.GO)) { if (status.equals(DebugStatus.GO)) {
@@ -972,6 +976,7 @@ public class DbgManagerImpl implements DbgManager {
if (thread != null) { if (thread != null) {
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().fire.threadSelected(thread, null, evt.getCause());
} }
processEvent(new DbgPromptChangedEvent(getControl().getPromptText()));
break; break;
} }
} }
@@ -1033,6 +1038,10 @@ public class DbgManagerImpl implements DbgManager {
getEventListeners().fire.consoleOutput(evt.getInfo(), evt.getMask()); getEventListeners().fire.consoleOutput(evt.getInfo(), evt.getMask());
} }
protected void processPromptChanged(DbgPromptChangedEvent evt, Void v) {
getEventListeners().fire.promptChanged(evt.getPrompt());
}
/** /**
* Handler for breakpoint-created event * Handler for breakpoint-created event
* *
@@ -1480,8 +1489,8 @@ public class DbgManagerImpl implements DbgManager {
@Override @Override
public CompletableFuture<Void> console(String command) { public CompletableFuture<Void> console(String command) {
if (continuation != null) { if (continuation != null) {
String prompt = command.equals("") ? DbgModelTargetInterpreter.DBG_PROMPT : ">>>"; //String prompt = command.equals("") ? DbgModelTargetInterpreter.DBG_PROMPT : ">>>";
getEventListeners().fire.promptChanged(prompt); //getEventListeners().fire.promptChanged(prompt);
continuation.complete(command); continuation.complete(command);
setContinuation(null); setContinuation(null);
return AsyncUtils.NIL; return AsyncUtils.NIL;
@@ -50,7 +50,7 @@ jar {
task configureNodepJar { task configureNodepJar {
doLast { doLast {
configurations.runtimeOnly.files.forEach { configurations.default.files.forEach {
if (filterJar(it)) { if (filterJar(it)) {
nodepJar.from(zipTree(it)) nodepJar.from(zipTree(it))
} }
@@ -18,6 +18,7 @@ package agent.dbgmodel.dbgmodel;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import java.io.*;
import java.util.*; import java.util.*;
import org.junit.Before; import org.junit.Before;
@@ -44,7 +45,7 @@ public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegration
} }
@Test @Test
public void testMWE() { public void testMWE() throws IOException {
HostDataModelAccess access = DbgModel.debugCreate(); HostDataModelAccess access = DbgModel.debugCreate();
DebugClient client = access.getClient(); DebugClient client = access.getClient();
DebugControl control = client.getControl(); DebugControl control = client.getControl();
@@ -272,12 +273,6 @@ public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegration
DebugStatus status = super.exitThread(exitCode); DebugStatus status = super.exitThread(exitCode);
return status; return status;
} }
@Override
public DebugStatus changeSymbolState(BitmaskSet<ChangeSymbolState> flags,
long argument) {
return defaultStatus;
}
}; };
try (ProcMaker maker = new ProcMaker(client, "C:\\Software\\Winmine__XP.exe")) { try (ProcMaker maker = new ProcMaker(client, "C:\\Software\\Winmine__XP.exe")) {
@@ -317,6 +312,17 @@ public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegration
} }
cb.dumpFrame0ViaDX(); 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 * TODO: Didn't finish because the SetContext failed issue turned out to be mixed and/or
* broken DLLs. * broken DLLs.
@@ -1124,4 +1124,9 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
} }
} }
*/ */
@Test
public void testPrompt() throws Exception {
}
} }
+1 -1
View File
@@ -52,7 +52,7 @@ jar {
task configureNodepJar { task configureNodepJar {
doLast { doLast {
configurations.runtimeOnly.files.forEach { configurations.default.files.forEach {
if (filterJar(it)) { if (filterJar(it)) {
nodepJar.from(zipTree(it)) nodepJar.from(zipTree(it))
} }
@@ -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-clear-all.png||GHIDRA||||END|
src/main/resources/images/breakpoints-disable-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-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/breakpoints.png||GHIDRA||||END|
src/main/resources/images/closedFolder.png||Modified Nuvola Icons - LGPL 2.1||||END| src/main/resources/images/closedFolder.png||Modified Nuvola Icons - LGPL 2.1||||END|
src/main/resources/images/connect.png||GHIDRA||||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-clear-all.svg||GHIDRA||||END|
src/main/svg/breakpoints-disable-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-enable-all.svg||GHIDRA||||END|
src/main/svg/breakpoints-make-effective.svg||GHIDRA||||END|
src/main/svg/breakpoints.svg||GHIDRA||||END| src/main/svg/breakpoints.svg||GHIDRA||||END|
src/main/svg/connect.svg||GHIDRA||||END| src/main/svg/connect.svg||GHIDRA||||END|
src/main/svg/console.svg||GHIDRA||||END| src/main/svg/console.svg||GHIDRA||||END|
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -99,6 +99,8 @@ public interface DebuggerResources {
ResourceManager.loadImage("images/breakpoints-disable-all.png"); ResourceManager.loadImage("images/breakpoints-disable-all.png");
ImageIcon ICON_CLEAR_ALL_BREAKPOINTS = ImageIcon ICON_CLEAR_ALL_BREAKPOINTS =
ResourceManager.loadImage("images/breakpoints-clear-all.png"); 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 // TODO: Some overlay to indicate dynamic, or new icon altogether
ImageIcon ICON_LISTING = ResourceManager.loadImage("images/Browser.gif"); ImageIcon ICON_LISTING = ResourceManager.loadImage("images/Browser.gif");
@@ -986,13 +988,29 @@ public interface DebuggerResources {
static ActionBuilder builder(Plugin owner) { static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName(); String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuGroup(GROUP) .menuGroup(GROUP)
.menuPath(NAME) .menuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); .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 { abstract class AbstractToggleBreakpointAction extends DockingAction {
public static final String NAME = "Toggle Breakpoint"; public static final String NAME = "Toggle Breakpoint";
// TODO: A "toggle breakpoint" icon // 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 { interface MapModulesAction {
String NAME = "Map Modules"; String NAME = "Map Modules";
String DESCRIPTION = "Map selected modules to program images"; String DESCRIPTION = "Map selected modules to program images";
@@ -22,7 +22,6 @@ import ghidra.dbg.target.TargetBreakpointLocation;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
public class BreakpointLocationRow { public class BreakpointLocationRow {
@@ -43,17 +42,18 @@ public class BreakpointLocationRow {
} }
public void setEnabled(boolean enabled) { 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()); TraceRecorder recorder = provider.modelService.getRecorder(loc.getTrace());
TargetBreakpointLocation bpt = recorder.getTargetBreakpoint(loc); TargetBreakpointLocation bpt = recorder.getTargetBreakpoint(loc);
if (enabled) { if (enabled) {
bpt.getSpecification().enable().exceptionally(ex -> { 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; return null;
}); });
} }
else { else {
bpt.getSpecification().disable().exceptionally(ex -> { 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; return null;
}); });
} }
@@ -532,7 +532,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (bs == null || bs.isEmpty()) { if (bs == null || bs.isEmpty()) {
Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context); Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context);
if (supported.isEmpty()) { if (supported.isEmpty()) {
Msg.showError(this, null, NAME, breakpointError(NAME,
"It seems this target does not support breakpoints."); "It seems this target does not support breakpoints.");
return; return;
} }
@@ -545,13 +545,13 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
Trace trace = getTraceFromContext(context); // OK if null - means all traces Trace trace = getTraceFromContext(context); // OK if null - means all traces
if (en.enabled) { if (en.enabled) {
breakpointService.disableAll(bs, trace).exceptionally(ex -> { 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; return null;
}); });
} }
else { else {
breakpointService.enableAll(bs, trace).exceptionally(ex -> { 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; return null;
}); });
} }
@@ -628,7 +628,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
ProgramLocation location = getLocationFromContext(context); ProgramLocation location = getLocationFromContext(context);
Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location); Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
breakpointService.enableAll(col, getTraceFromContext(context)).exceptionally(ex -> { 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; return null;
}); });
} }
@@ -665,7 +665,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
ProgramLocation location = getLocationFromContext(context); ProgramLocation location = getLocationFromContext(context);
Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location); Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
breakpointService.disableAll(col, getTraceFromContext(context)).exceptionally(ex -> { 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; return null;
}); });
} }
@@ -704,7 +704,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
ProgramLocation location = getLocationFromContext(context); ProgramLocation location = getLocationFromContext(context);
Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location); Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
breakpointService.deleteAll(col, getTraceFromContext(context)).exceptionally(ex -> { 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; return null;
}); });
} }
@@ -733,6 +733,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
private DebuggerStaticMappingService mappingService; private DebuggerStaticMappingService mappingService;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; 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 + ")");
}
} }
@@ -25,19 +25,20 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( // @PluginInfo( //
shortDescription = "Debugger breakpoints manager", // shortDescription = "Debugger breakpoints manager", //
description = "GUI to manage breakpoints", // description = "GUI to manage breakpoints", //
category = PluginCategoryNames.DEBUGGER, // category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, // packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, // status = PluginStatus.RELEASED, //
servicesRequired = { // servicesRequired = { //
DebuggerLogicalBreakpointService.class, // DebuggerLogicalBreakpointService.class, //
DebuggerModelService.class, // DebuggerModelService.class, //
}, eventsConsumed = { },
TraceOpenedPluginEvent.class, // eventsConsumed = {
TraceClosedPluginEvent.class, // TraceOpenedPluginEvent.class, //
TraceActivatedPluginEvent.class, // TraceClosedPluginEvent.class, //
} // TraceActivatedPluginEvent.class, //
} //
) )
public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin { public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin {
protected DebuggerBreakpointsProvider provider; protected DebuggerBreakpointsProvider provider;
@@ -54,6 +55,7 @@ public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin {
@Override @Override
protected void dispose() { protected void dispose() {
provider.dispose();
tool.removeComponentProvider(provider); tool.removeComponentProvider(provider);
super.dispose(); super.dispose();
} }
@@ -254,8 +254,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
(DebuggerLogicalBreakpointsActionContext) context; (DebuggerLogicalBreakpointsActionContext) context;
Collection<LogicalBreakpoint> sel = ctx.getSelection(); Collection<LogicalBreakpoint> sel = ctx.getSelection();
breakpointService.enableAll(sel, null).exceptionally(ex -> { breakpointService.enableAll(sel, null).exceptionally(ex -> {
Msg.showError(this, getComponent(), "Enable Breakpoints", breakpointError("Enable Breakpoints", "Could not enable breakpoints", ex);
"Could not enable breakpoints", ex);
return null; return null;
}); });
} }
@@ -286,8 +285,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints(); Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints();
breakpointService.enableAll(all, null).exceptionally(ex -> { breakpointService.enableAll(all, null).exceptionally(ex -> {
Msg.showError(this, getComponent(), "Enable All Breakpoints", breakpointError("Enable All Breakpoints", "Could not enable breakpoints", ex);
"Could not enable breakpoints", ex);
return null; return null;
}); });
} }
@@ -320,8 +318,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
(DebuggerLogicalBreakpointsActionContext) context; (DebuggerLogicalBreakpointsActionContext) context;
Collection<LogicalBreakpoint> sel = ctx.getSelection(); Collection<LogicalBreakpoint> sel = ctx.getSelection();
breakpointService.disableAll(sel, null).exceptionally(ex -> { breakpointService.disableAll(sel, null).exceptionally(ex -> {
Msg.showError(this, getComponent(), "Disable Breakpoints", breakpointError("Disable Breakpoints", "Could not disable breakpoints", ex);
"Could not disable breakpoints", ex);
return null; return null;
}); });
} }
@@ -352,8 +349,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints(); Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints();
breakpointService.disableAll(all, null).exceptionally(ex -> { breakpointService.disableAll(all, null).exceptionally(ex -> {
Msg.showError(this, getComponent(), "Disable All Breakpoints", breakpointError("Disable All Breakpoints", "Could not disable breakpoints", ex);
"Could not disable breakpoints", ex);
return null; return null;
}); });
} }
@@ -365,7 +361,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
} }
protected class ClearSelectedBreakpointsAction extends AbstractClearSelectedBreakpointsAction { protected class ClearSelectedBreakpointsAction extends AbstractClearSelectedBreakpointsAction {
public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS + "Clear";
public ClearSelectedBreakpointsAction() { public ClearSelectedBreakpointsAction() {
super(plugin); super(plugin);
@@ -382,8 +378,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
(DebuggerLogicalBreakpointsActionContext) context; (DebuggerLogicalBreakpointsActionContext) context;
Collection<LogicalBreakpoint> sel = ctx.getSelection(); Collection<LogicalBreakpoint> sel = ctx.getSelection();
breakpointService.deleteAll(sel, null).exceptionally(ex -> { breakpointService.deleteAll(sel, null).exceptionally(ex -> {
Msg.showError(this, getComponent(), "Clear Breakpoints", breakpointError("Clear Breakpoints", "Could not clear breakpoints", ex);
"Could not clear breakpoints", ex);
return null; return null;
}); });
} }
@@ -401,7 +396,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
} }
protected class ClearAllBreakpointsAction extends AbstractClearAllBreakpointsAction { protected class ClearAllBreakpointsAction extends AbstractClearAllBreakpointsAction {
public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS + "Clear";
public ClearAllBreakpointsAction() { public ClearAllBreakpointsAction() {
super(plugin); super(plugin);
@@ -414,8 +409,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints(); Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints();
breakpointService.deleteAll(all, null).exceptionally(ex -> { breakpointService.deleteAll(all, null).exceptionally(ex -> {
Msg.showError(this, getComponent(), "Clear All Breakpoints", breakpointError("Clear All Breakpoints", "Could not clear breakpoints", ex);
"Could not clear breakpoints", ex);
return null; 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<LogicalBreakpoint> 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<LogicalBreakpoint> 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<BreakpointLocationRow> { class LocationsBySelectedBreakpointsTableFilter implements TableFilter<BreakpointLocationRow> {
@Override @Override
public boolean acceptsRow(BreakpointLocationRow locationRow) { public boolean acceptsRow(BreakpointLocationRow locationRow) {
@@ -540,13 +592,15 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
// @AutoServiceConsumed via method // @AutoServiceConsumed via method
private DebuggerLogicalBreakpointService breakpointService; private DebuggerLogicalBreakpointService breakpointService;
// @AutoServiceConsumed via method // @AutoServiceConsumed via method, package access for BreakpointLogicalRow
DebuggerModelService modelService; DebuggerModelService modelService;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerListingService listingService; private DebuggerListingService listingService;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
private GoToService goToService; private GoToService goToService;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring; private AutoService.Wiring autoServiceWiring;
@@ -571,13 +625,18 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
private ActionContext myActionContext; private ActionContext myActionContext;
private final DebuggerMakeBreakpointsEffectiveActionContext makeEffectiveResolutionContext =
new DebuggerMakeBreakpointsEffectiveActionContext();
// package access for testing // package access for testing
EnableSelectedBreakpointsAction actionEnableSelectedBreakpointsAction; EnableSelectedBreakpointsAction actionEnableSelectedBreakpoints;
EnableAllBreakpointsAction actionEnableAllBreakpointsAction; EnableAllBreakpointsAction actionEnableAllBreakpoints;
DisableSelectedBreakpointsAction actionDisableSelectedBreakpointsAction; DisableSelectedBreakpointsAction actionDisableSelectedBreakpoints;
DisableAllBreakpointsAction actionDisableAllBreakpointsAction; DisableAllBreakpointsAction actionDisableAllBreakpoints;
ClearSelectedBreakpointsAction actionClearSelectedBreakpointsAction; ClearSelectedBreakpointsAction actionClearSelectedBreakpoints;
ClearAllBreakpointsAction actionClearAllBreakpointsAction; ClearAllBreakpointsAction actionClearAllBreakpoints;
MakeBreakpointsEffectiveAction actionMakeBreakpointsEffective;
MakeBreakpointsEffectiveResolutionAction actionMakeBreakpointsEffectiveResolution;
ToggleDockingAction actionFilterByCurrentTrace; ToggleDockingAction actionFilterByCurrentTrace;
ToggleDockingAction actionFilterLocationsByBreakpoints; ToggleDockingAction actionFilterLocationsByBreakpoints;
@@ -598,6 +657,35 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
createActions(); 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 @AutoServiceConsumed
private void setBreakpointService(DebuggerLogicalBreakpointService breakpointService) { private void setBreakpointService(DebuggerLogicalBreakpointService breakpointService) {
if (this.breakpointService != null) { 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() { protected void loadBreakpoints() {
Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints(); Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints();
breakpointTableModel.addAllItems(all); breakpointTableModel.addAllItems(all);
@@ -921,12 +1018,13 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
} }
protected void createActions() { protected void createActions() {
actionEnableSelectedBreakpointsAction = new EnableSelectedBreakpointsAction(); actionEnableSelectedBreakpoints = new EnableSelectedBreakpointsAction();
actionEnableAllBreakpointsAction = new EnableAllBreakpointsAction(); actionEnableAllBreakpoints = new EnableAllBreakpointsAction();
actionDisableSelectedBreakpointsAction = new DisableSelectedBreakpointsAction(); actionDisableSelectedBreakpoints = new DisableSelectedBreakpointsAction();
actionDisableAllBreakpointsAction = new DisableAllBreakpointsAction(); actionDisableAllBreakpoints = new DisableAllBreakpointsAction();
actionClearSelectedBreakpointsAction = new ClearSelectedBreakpointsAction(); actionClearSelectedBreakpoints = new ClearSelectedBreakpointsAction();
actionClearAllBreakpointsAction = new ClearAllBreakpointsAction(); actionClearAllBreakpoints = new ClearAllBreakpointsAction();
actionMakeBreakpointsEffective = new MakeBreakpointsEffectiveAction();
actionFilterByCurrentTrace = FilterAction.builder(plugin) actionFilterByCurrentTrace = FilterAction.builder(plugin)
.toolBarIcon(DebuggerResources.ICON_TRACE) .toolBarIcon(DebuggerResources.ICON_TRACE)
.description("Filter locations to those in current 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")) .helpLocation(new HelpLocation(plugin.getName(), "filter_by_logical"))
.onAction(this::toggledFilterLocationsByBreakpoints) .onAction(this::toggledFilterLocationsByBreakpoints)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMakeBreakpointsEffectiveResolution = new MakeBreakpointsEffectiveResolutionAction();
} }
private void toggledFilterByCurrentTrace(ActionContext ignored) { private void toggledFilterByCurrentTrace(ActionContext ignored) {
@@ -979,4 +1079,13 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
DebuggerResources.setSelectedRows(sel, locationTableModel::getRow, locationTable, DebuggerResources.setSelectedRows(sel, locationTableModel::getRow, locationTable,
locationTableModel, locationFilterPanel); 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 + ")");
}
} }
@@ -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
}
@@ -24,7 +24,6 @@ import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet; import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.util.Msg;
public class LogicalBreakpointRow { public class LogicalBreakpointRow {
private final DebuggerBreakpointsProvider provider; private final DebuggerBreakpointsProvider provider;
@@ -64,7 +63,7 @@ public class LogicalBreakpointRow {
? lb.enableForTrace(provider.currentTrace) ? lb.enableForTrace(provider.currentTrace)
: lb.enable(); : lb.enable();
future.exceptionally(ex -> { 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; return null;
}); });
} }
@@ -73,7 +72,7 @@ public class LogicalBreakpointRow {
? lb.disableForTrace(provider.currentTrace) ? lb.disableForTrace(provider.currentTrace)
: lb.disable(); : lb.disable();
future.exceptionally(ex -> { 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; return null;
}); });
} }
@@ -29,6 +29,7 @@ import docking.ActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
import ghidra.app.services.DebuggerConsoleService; import ghidra.app.services.DebuggerConsoleService;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@@ -99,14 +100,24 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
super.dispose(); super.dispose();
} }
@Override
public void log(Icon icon, String message) {
provider.log(icon, message);
}
@Override @Override
public void log(Icon icon, String message, ActionContext context) { public void log(Icon icon, String message, ActionContext context) {
provider.log(icon, message, context); provider.log(icon, message, context);
} }
@Override @Override
public void remove(ActionContext context) { public void removeFromLog(ActionContext context) {
provider.remove(context); provider.removeFromLog(context);
}
@Override
public boolean logContains(ActionContext context) {
return provider.logContains(context);
} }
@Override @Override
@@ -127,4 +138,14 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
public long getRowCount(Class<? extends ActionContext> ctxCls) { public long getRowCount(Class<? extends ActionContext> ctxCls) {
return provider.getRowCount(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);
}
} }
@@ -56,7 +56,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
static final int ACTION_BUTTON_SIZE = 32; static final int ACTION_BUTTON_SIZE = 32;
static final Dimension ACTION_BUTTON_DIM = static final Dimension ACTION_BUTTON_DIM =
new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE); 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<LogTableColumns, LogRow> { protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow> {
LEVEL("Level", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false), 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; * An action bound to a context
protected final ActionContext context; *
* <p>
* 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) { public BoundAction(DockingActionIf action, ActionContext context) {
this.action = action; this.action = action;
@@ -144,10 +150,22 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
} }
} }
protected static class ActionList extends ArrayList<BoundAction> { /**
* A list of bound actions
*
* <p>
* This class is public for access by test cases only.
*/
public static class ActionList extends ArrayList<BoundAction> {
} }
protected static class LogRow { /**
* An entry in the console's log
*
* <p>
* This class is public for access by test cases only.
*/
public static class LogRow {
private final Icon icon; private final Icon icon;
private final String message; private final String message;
private final Date date; private final Date date;
@@ -224,7 +242,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
int rows = model.getRowCount(); int rows = model.getRowCount();
int cols = getColumnCount(); int cols = getColumnCount();
for (int r = 0; r < rows; r++) { for (int r = 0; r < rows; r++) {
int height = 0; int height = MIN_ROW_HEIGHT;
for (int c = 0; c < cols; c++) { for (int c = 0; c < cols; c++) {
height = Math.max(height, computePreferredHeight(r, 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) { protected void log(Icon icon, String message, ActionContext context) {
logRow(new LogRow(icon, message, new Date(), context, computeToolbarActions(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))); new Date(event.getTimeMillis()), context, computeToolbarActions(context)));
} }
protected void remove(ActionContext context) { protected void removeFromLog(ActionContext context) {
synchronized (buffer) { synchronized (buffer) {
LogRow r = logTableModel.deleteKey(context); LogRow r = logTableModel.deleteKey(context);
buffer.remove(r); buffer.remove(r);
} }
} }
protected boolean logContains(ActionContext context) {
synchronized (buffer) {
return logTableModel.getMap().containsKey(context);
}
}
protected void addResolutionAction(DockingActionIf action) { protected void addResolutionAction(DockingActionIf action) {
DockingActionIf replaced = DockingActionIf replaced =
actionsByOwnerThenName.computeIfAbsent(action.getOwner(), o -> new LinkedHashMap<>()) actionsByOwnerThenName.computeIfAbsent(action.getOwner(), o -> new LinkedHashMap<>())
@@ -479,9 +507,17 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
} }
protected long getRowCount(Class<? extends ActionContext> ctxCls) { protected long getRowCount(Class<? extends ActionContext> ctxCls) {
return logTableModel.getModelData() synchronized (buffer) {
.stream() return logTableModel.getModelData()
.filter(r -> ctxCls.isInstance(r.context)) .stream()
.count(); .filter(r -> ctxCls.isInstance(r.context))
.count();
}
}
public LogRow getLogRow(ActionContext ctx) {
synchronized (buffer) {
return logTableModel.getMap().get(ctx);
}
} }
} }
@@ -65,6 +65,7 @@ import utilities.util.SuppressableCallback.Suppression;
eventsConsumed = { eventsConsumed = {
// ProgramSelectionPluginEvent.class, // TODO: Later or remove // ProgramSelectionPluginEvent.class, // TODO: Later or remove
// ProgramHighlightPluginEvent.class, // TODO: Later or remove // ProgramHighlightPluginEvent.class, // TODO: Later or remove
ProgramOpenedPluginEvent.class, // For auto-open log cleanup
ProgramClosedPluginEvent.class, // For marker set cleanup ProgramClosedPluginEvent.class, // For marker set cleanup
ProgramLocationPluginEvent.class, // For static listing sync ProgramLocationPluginEvent.class, // For static listing sync
TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking 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) { if (event instanceof ProgramClosedPluginEvent) {
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event; ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
allProviders(p -> p.programClosed(ev.getProgram())); allProviders(p -> p.programClosed(ev.getProgram()));
@@ -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.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec; 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.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.plugin.core.debug.utils.*;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.plugin.core.exporter.ExporterDialog; import ghidra.app.plugin.core.exporter.ExporterDialog;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.*; import ghidra.app.services.*;
@@ -86,6 +85,9 @@ import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.util.TraceAddressSpace; import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.*; 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;
import utilities.util.SuppressableCallback.Suppression; import utilities.util.SuppressableCallback.Suppression;
@@ -353,6 +355,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
protected FollowsCurrentThreadAction actionFollowsCurrentThread; protected FollowsCurrentThreadAction actionFollowsCurrentThread;
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory; protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionExportView; protected DockingAction actionExportView;
protected DockingAction actionOpenProgram;
protected final DebuggerGoToDialog goToDialog; 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) { protected void markTrackedStaticLocation(ProgramLocation location) {
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
if (location == null) { 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) { public void programClosed(Program program) {
if (program == markedProgram) { if (program == markedProgram) {
removeOldStaticTrackingMarker(); removeOldStaticTrackingMarker();
@@ -784,6 +807,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
.onAction(this::activatedExportView) .onAction(this::activatedExportView)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionOpenProgram = OpenProgramAction.builder(plugin)
.withContext(DebuggerOpenProgramActionContext.class)
.onAction(this::activatedOpenProgram)
.build();
contextChanged(); contextChanged();
} }
@@ -813,6 +841,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
tool.showDialog(dialog); tool.showDialog(dialog);
} }
private void activatedOpenProgram(DebuggerOpenProgramActionContext context) {
programManager.openProgram(context.getDomainFile(), DomainFile.DEFAULT_VERSION,
ProgramManager.OPEN_CURRENT);
}
protected void activatedLocationTracking(ActionContext ctx) { protected void activatedLocationTracking(ActionContext ctx) {
doTrackSpec(); doTrackSpec();
} }
@@ -901,7 +934,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
public void programLocationChanged(ProgramLocation location, EventTrigger trigger) { public void programLocationChanged(ProgramLocation location, EventTrigger trigger) {
updateLocationLabel(); updateLocationLabel();
if (traceManager != null) { if (traceManager != null) {
location = traceManager.fixLocation(location, false); location = ProgramLocationUtils.fixLocation(location, false);
} }
super.programLocationChanged(location, trigger); super.programLocationChanged(location, trigger);
if (trigger == EventTrigger.GUI_ACTION) { if (trigger == EventTrigger.GUI_ACTION) {
@@ -935,7 +968,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
} }
protected void doSyncToStatic(ProgramLocation location) { protected void doSyncToStatic(ProgramLocation location) {
if (syncToStaticListing && location != null) { if (isSyncToStaticListing() && location != null) {
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location); ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
if (staticLoc != null) { if (staticLoc != null) {
Swing.runIfSwingOrRunLater(() -> plugin.fireStaticLocationEvent(staticLoc)); 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, "<html>Program <b>" +
HTMLUtilities.escapeHTML(df.getPathname()) +
"</b> has recovery data. It must be opened manually.</html>", 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, "<html>Program <b>" +
HTMLUtilities.escapeHTML(df.getPathname()) +
"</b> was created with a different version of Ghidra." +
" It must be opened manually.</html>", ctx);
}
return;
}
catch (Exception e) {
if (consoleService != null) {
consoleService.log(DebuggerResources.ICON_LOG_ERROR, "<html>Program <b>" +
HTMLUtilities.escapeHTML(df.getPathname()) +
"</b> could not be opened: " + e + ". Try opening it manually.</html>",
ctx);
}
return;
}
finally {
if (program != null) {
program.release(this);
}
}
}
}, tool.getToolFrame());
}
protected void doCheckCurrentModuleMissing() { 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; return;
} }
Trace trace = current.getTrace(); Trace trace = current.getTrace();
@@ -968,9 +1051,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
DomainFile df = ProgramURLUtils.getFileForHackedUpGhidraURL(tool.getProject(), DomainFile df = ProgramURLUtils.getFileForHackedUpGhidraURL(tool.getProject(),
mapping.getStaticProgramURL()); mapping.getStaticProgramURL());
if (df != null) { if (df != null) {
// We're almost certainly preparing to goTo, so make it current doTryOpenProgram(df, DomainFile.DEFAULT_VERSION, ProgramManager.OPEN_CURRENT);
programManager.openProgram(df, DomainFile.DEFAULT_VERSION,
ProgramManager.OPEN_CURRENT);
} }
} }
@@ -996,14 +1077,19 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
if (programManager != null && !toOpen.isEmpty()) { if (programManager != null && !toOpen.isEmpty()) {
for (DomainFile df : toOpen) { for (DomainFile df : toOpen) {
// Do not presume a goTo is about to happen. There are no mappings, yet. // 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); ProgramManager.OPEN_VISIBLE);
} }
} }
if (importerService == null || consoleService == null) {
return;
}
for (TraceModule mod : missing) { for (TraceModule mod : missing) {
consoleService.log(DebuggerResources.ICON_LOG_ERROR, consoleService.log(DebuggerResources.ICON_LOG_ERROR,
"<html>The module <b><tt>" + HTMLUtilities.escapeHTML(mod.getName()) + "<html>The module <b><tt>" + HTMLUtilities.escapeHTML(mod.getName()) +
"</tt></b> was not found in the project", "</tt></b> was not found in the project</html>",
new DebuggerMissingModuleActionContext(mod)); new DebuggerMissingModuleActionContext(mod));
} }
/** /**
@@ -1145,6 +1231,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
removeOldListeners(); removeOldListeners();
if (consoleService != null) {
if (actionOpenProgram != null) {
consoleService.removeResolutionAction(actionOpenProgram);
}
}
} }
@Override @Override
@@ -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;
}
}
@@ -15,6 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.gui.modules; package ghidra.app.plugin.core.debug.gui.modules;
import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
@@ -34,18 +35,17 @@ import ghidra.util.Swing;
public class DebuggerModuleMapProposalDialog public class DebuggerModuleMapProposalDialog
extends AbstractDebuggerMapProposalDialog<ModuleMapEntry> { extends AbstractDebuggerMapProposalDialog<ModuleMapEntry> {
static final String BLANK = "";
static final int BUTTON_SIZE = 32; static final int BUTTON_SIZE = 32;
protected enum ModuleMapTableColumns protected enum ModuleMapTableColumns
implements EnumeratedTableColumn<ModuleMapTableColumns, ModuleMapEntry> { implements EnumeratedTableColumn<ModuleMapTableColumns, ModuleMapEntry> {
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()), MODULE_NAME("Module", String.class, e -> e.getModule().getName()),
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getModule().getBase()), 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()), PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()),
STATIC_BASE("Static Base", Address.class, e -> e.getProgram().getImageBase()), STATIC_BASE("Static Base", Address.class, e -> e.getProgram().getImageBase()),
SIZE("Size", Long.class, e -> e.getModuleRange().getLength()), SIZE("Size", Long.class, e -> e.getModuleRange().getLength());
CHOOSE("Choose", String.class, e -> BLANK, (e, v) -> nop());
private final String header; private final String header;
private final Class<?> cls; private final Class<?> cls;
@@ -94,6 +94,19 @@ public class DebuggerModuleMapProposalDialog
} }
} }
protected static class ModuleMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<ModuleMapTableColumns, ModuleMapEntry> {
public ModuleMapPropsalTableModel() {
super("Module Map", ModuleMapTableColumns.class);
}
@Override
public List<ModuleMapTableColumns> defaultSortOrder() {
return List.of(ModuleMapTableColumns.MODULE_NAME);
}
}
private final DebuggerModulesProvider provider; private final DebuggerModulesProvider provider;
protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) { protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) {
@@ -102,8 +115,8 @@ public class DebuggerModuleMapProposalDialog
} }
@Override @Override
protected EnumeratedColumnTableModel<ModuleMapEntry> createTableModel() { protected ModuleMapPropsalTableModel createTableModel() {
return new DefaultEnumeratedColumnTableModel<>("Module Map", ModuleMapTableColumns.class); return new ModuleMapPropsalTableModel();
} }
@Override @Override
@@ -117,9 +130,19 @@ public class DebuggerModuleMapProposalDialog
CellEditorUtils.installButton(table, filterPanel, removeCol, CellEditorUtils.installButton(table, filterPanel, removeCol,
DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeEntry); 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()); TableColumn chooseCol = columnModel.getColumn(ModuleMapTableColumns.CHOOSE.ordinal());
CellEditorUtils.installButton(table, filterPanel, chooseCol, CellEditorUtils.installButton(table, filterPanel, chooseCol,
DebuggerResources.ICON_PROGRAM, BUTTON_SIZE, this::chooseAndSetProgram); 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) { private void chooseAndSetProgram(ModuleMapEntry entry) {
@@ -867,12 +867,12 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
Msg.error(this, "Import service is not present"); Msg.error(this, "Import service is not present");
} }
importModuleFromFileSystem(context.getModule()); 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) { private void activatedMapMissingModule(DebuggerMissingModuleActionContext context) {
mapModuleTo(context.getModule()); 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) { private void toggledFilter(ActionContext ignored) {
@@ -972,12 +972,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
} }
protected void promptModuleProposal(Collection<ModuleMapEntry> proposal) { protected void promptModuleProposal(Collection<ModuleMapEntry> 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<ModuleMapEntry> adjusted = Collection<ModuleMapEntry> adjusted =
moduleProposalDialog.adjustCollection(getTool(), proposal); moduleProposalDialog.adjustCollection(getTool(), proposal);
if (adjusted != null && staticMappingService != null) { if (adjusted == null || staticMappingService == null) {
tool.executeBackgroundCommand( return;
new MapModulesBackgroundCommand(staticMappingService, adjusted), currentTrace);
} }
tool.executeBackgroundCommand(
new MapModulesBackgroundCommand(staticMappingService, adjusted), currentTrace);
} }
protected void mapModules(Set<TraceModule> modules) { protected void mapModules(Set<TraceModule> modules) {
@@ -1004,12 +1011,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
} }
protected void promptSectionProposal(Collection<SectionMapEntry> proposal) { protected void promptSectionProposal(Collection<SectionMapEntry> 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<SectionMapEntry> adjusted = Collection<SectionMapEntry> adjusted =
sectionProposalDialog.adjustCollection(getTool(), proposal); sectionProposalDialog.adjustCollection(getTool(), proposal);
if (adjusted != null && staticMappingService != null) { if (adjusted == null || staticMappingService == null) {
tool.executeBackgroundCommand( return;
new MapSectionsBackgroundCommand(staticMappingService, adjusted), currentTrace);
} }
tool.executeBackgroundCommand(
new MapSectionsBackgroundCommand(staticMappingService, adjusted), currentTrace);
} }
protected void mapSections(Set<TraceSection> sections) { protected void mapSections(Set<TraceSection> sections) {
@@ -15,6 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.gui.modules; package ghidra.app.plugin.core.debug.gui.modules;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
@@ -35,20 +36,19 @@ import ghidra.util.Swing;
public class DebuggerSectionMapProposalDialog public class DebuggerSectionMapProposalDialog
extends AbstractDebuggerMapProposalDialog<SectionMapEntry> { extends AbstractDebuggerMapProposalDialog<SectionMapEntry> {
static final String BLANK = "";
static final int BUTTON_SIZE = 32; static final int BUTTON_SIZE = 32;
protected enum SectionMapTableColumns protected enum SectionMapTableColumns
implements EnumeratedTableColumn<SectionMapTableColumns, SectionMapEntry> { implements EnumeratedTableColumn<SectionMapTableColumns, SectionMapEntry> {
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()), MODULE_NAME("Module", String.class, e -> e.getModule().getName()),
SECTION_NAME("Section", String.class, e -> e.getSection().getName()), SECTION_NAME("Section", String.class, e -> e.getSection().getName()),
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getSection().getStart()), 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()), PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()),
BLOCK_NAME("Block", String.class, e -> e.getBlock().getName()), BLOCK_NAME("Block", String.class, e -> e.getBlock().getName()),
STATIC_BASE("Static Base", Address.class, e -> e.getBlock().getStart()), STATIC_BASE("Static Base", Address.class, e -> e.getBlock().getStart()),
SIZE("Size", Long.class, e -> e.getLength()), SIZE("Size", Long.class, e -> e.getLength());
CHOOSE("Choose", String.class, e -> BLANK, (e, s) -> nop());
private final String header; private final String header;
private final Class<?> cls; private final Class<?> cls;
@@ -98,6 +98,19 @@ public class DebuggerSectionMapProposalDialog
} }
} }
protected static class SectionMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<SectionMapTableColumns, SectionMapEntry> {
public SectionMapPropsalTableModel() {
super("Section Map", SectionMapTableColumns.class);
}
@Override
public List<SectionMapTableColumns> defaultSortOrder() {
return List.of(SectionMapTableColumns.MODULE_NAME, SectionMapTableColumns.SECTION_NAME);
}
}
private final DebuggerModulesProvider provider; private final DebuggerModulesProvider provider;
public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) { public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) {
@@ -106,8 +119,8 @@ public class DebuggerSectionMapProposalDialog
} }
@Override @Override
protected EnumeratedColumnTableModel<SectionMapEntry> createTableModel() { protected SectionMapPropsalTableModel createTableModel() {
return new DefaultEnumeratedColumnTableModel<>("Section Map", SectionMapTableColumns.class); return new SectionMapPropsalTableModel();
} }
@Override @Override
@@ -121,9 +134,19 @@ public class DebuggerSectionMapProposalDialog
CellEditorUtils.installButton(table, filterPanel, removeCol, CellEditorUtils.installButton(table, filterPanel, removeCol,
DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeEntry); 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()); TableColumn chooseCol = columnModel.getColumn(SectionMapTableColumns.CHOOSE.ordinal());
CellEditorUtils.installButton(table, filterPanel, chooseCol, DebuggerResources.ICON_PROGRAM, CellEditorUtils.installButton(table, filterPanel, chooseCol, DebuggerResources.ICON_PROGRAM,
BUTTON_SIZE, this::chooseAndSetBlock); 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) { private void chooseAndSetBlock(SectionMapEntry entry) {
@@ -1118,7 +1118,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
@Override @Override
public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) { public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) {
loc = traceManager.fixLocation(loc, true); loc = ProgramLocationUtils.fixLocation(loc, true);
TraceProgramView view = (TraceProgramView) loc.getProgram(); TraceProgramView view = (TraceProgramView) loc.getProgram();
Trace trace = view.getTrace(); Trace trace = view.getTrace();
TraceLocation tloc = new DefaultTraceLocation(trace, null, TraceLocation tloc = new DefaultTraceLocation(trace, null,
@@ -31,7 +31,6 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec; import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
import ghidra.async.AsyncReference; import ghidra.async.AsyncReference;
@@ -47,8 +46,6 @@ import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.lifecycle.Internal; 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;
import ghidra.trace.model.Trace.TraceThreadChangeType; import ghidra.trace.model.Trace.TraceThreadChangeType;
import ghidra.trace.model.TraceDomainObjectListener; import ghidra.trace.model.TraceDomainObjectListener;
@@ -1147,23 +1144,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
autoCloseOnTerminate.removeChangeListener(listener); 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 @Override
public boolean canClose() { public boolean canClose() {
if (isSaveTracesByDefault()) { if (isSaveTracesByDefault()) {
@@ -18,20 +18,48 @@ package ghidra.app.plugin.core.debug.utils;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.BytesFieldLocation;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
public enum ProgramLocationUtils { public enum ProgramLocationUtils {
; ;
public static ProgramLocation replaceAddress(ProgramLocation loc, Program program, public static ProgramLocation replaceAddress(ProgramLocation loc, Program program,
Address address) { Address address) {
// HACK: ... and a half // Outside of byte fields, I really don't care
SaveState state = new SaveState("LOC"); if (loc instanceof BytesFieldLocation) {
loc.saveState(state); return new BytesFieldLocation(program, address);
state.putString("_ADDRESS", address.toString()); }
state.putString("_BYTE_ADDR", return new ProgramLocation(program, address);
address.add(loc.getByteAddress().subtract(loc.getAddress())).toString()); }
return ProgramLocation.getLocation(program, state);
/**
* Swap out the trace view of a {@link ProgramLocation} if it is not the canonical view
*
* <p>
* 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) { public static ProgramLocation replaceProgram(ProgramLocation loc, Program program) {
@@ -27,6 +27,17 @@ import ghidra.util.HTMLUtilities;
@ServiceInfo(defaultProvider = DebuggerConsolePlugin.class) @ServiceInfo(defaultProvider = DebuggerConsolePlugin.class)
public interface DebuggerConsoleService extends DebuggerConsoleLogger { public interface DebuggerConsoleService extends DebuggerConsoleLogger {
/**
* Log a message to the console
*
* <p>
* <b>WARNING:</b> 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 * 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 * @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 * Add an action which might be applied to an actionable log message
@@ -22,7 +22,6 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@@ -195,21 +194,6 @@ public interface DebuggerTraceManagerService {
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
/**
* Swap out the trace view of a {@link ProgramLocation} if it is not the debugger's view
*
* <p>
* 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" * Fill in an incomplete coordinate specification, using the manager's "best judgement"
* *
Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="SVGRoot"
sodipodi:docname="breakpoints-make-effective.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="757"
inkscape:window-height="480"
id="namedview8"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-x="1405"
inkscape:window-y="650"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs5287" />
<metadata
id="metadata5290">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1">
<circle
style="opacity:1;fill:#aa0000;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path821"
cx="11"
cy="11.000002"
r="4" />
<path
style="opacity:1;fill:#7f7f7f;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 5,1 C 2.790861,1 1,2.790861 1,5 1,7.209139 2.790861,9 5,9 7.209139,9 9,7.209139 9,5 9,2.790861 7.209139,1 5,1 Z"
id="path821-7" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -31,6 +31,7 @@ import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; 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.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.Enablement; import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.async.AsyncTestUtils; import ghidra.async.AsyncTestUtils;
@@ -260,12 +261,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint(); addStaticMemoryAndBreakpoint();
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
LogicalBreakpointRow row = LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
@@ -274,28 +275,28 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
waitForSwing(); waitForSwing();
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement()); 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()); assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointTable.clearSelection(); breakpointsProvider.breakpointTable.clearSelection();
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing(); waitForSwing();
assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
// Bookmark part should actually be synchronous. // Bookmark part should actually be synchronous.
waitOn(row.getLogicalBreakpoint().delete()); waitOn(row.getLogicalBreakpoint().delete());
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
} }
@Test @Test
@@ -304,12 +305,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint(); addStaticMemoryAndBreakpoint();
waitForDomainObject(program); waitForDomainObject(program);
assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
LogicalBreakpointRow row = LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
@@ -317,18 +318,18 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
waitForSwing(); waitForSwing();
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement()); 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()); assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
// Bookmark part should actually be synchronous. // Bookmark part should actually be synchronous.
row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
} }
@Test @Test
@@ -337,12 +338,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint(); addStaticMemoryAndBreakpoint();
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
LogicalBreakpointRow row = LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
@@ -350,28 +351,28 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
waitForSwing(); waitForSwing();
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement()); 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()); assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointTable.clearSelection(); breakpointsProvider.breakpointTable.clearSelection();
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing(); waitForSwing();
assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
// Bookmark part should actually be synchronous. // Bookmark part should actually be synchronous.
waitOn(row.getLogicalBreakpoint().delete()); waitOn(row.getLogicalBreakpoint().delete());
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
} }
@Test @Test
@@ -380,27 +381,27 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint(); addStaticMemoryAndBreakpoint();
waitForDomainObject(program); waitForDomainObject(program);
assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
LogicalBreakpointRow row = LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement()); 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()); assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
// Bookmark part should actually be synchronous. // Bookmark part should actually be synchronous.
row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
} }
@Test @Test
@@ -409,34 +410,34 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint(); addStaticMemoryAndBreakpoint();
waitForDomainObject(program); waitForDomainObject(program);
assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
LogicalBreakpointRow row = LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData()); Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing(); waitForSwing();
assertTrue(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointTable.clearSelection(); breakpointsProvider.breakpointTable.clearSelection();
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row); breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing(); waitForSwing();
assertTrue(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
performAction(breakpointsProvider.actionClearSelectedBreakpointsAction); performAction(breakpointsProvider.actionClearSelectedBreakpoints);
assertProviderEmpty(); assertProviderEmpty();
assertFalse(breakpointsProvider.actionClearSelectedBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
} }
@Test @Test
@@ -445,17 +446,54 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(breakpointsProvider.actionClearAllBreakpointsAction.isEnabled()); assertFalse(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
addStaticMemoryAndBreakpoint(); addStaticMemoryAndBreakpoint();
waitForDomainObject(program); waitForDomainObject(program);
assertTrue(breakpointsProvider.actionClearAllBreakpointsAction.isEnabled()); assertTrue(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
performAction(breakpointsProvider.actionClearAllBreakpointsAction); performAction(breakpointsProvider.actionClearAllBreakpoints);
assertProviderEmpty(); 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 @Test
@@ -19,6 +19,7 @@ import static ghidra.lifecycle.Unfinished.TODO;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.*; import java.awt.*;
import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; 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.DebuggerResources.AbstractFollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*; 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.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService; import ghidra.async.SwingExecutorService;
import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin; import ghidra.plugin.importer.ImporterPlugin;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register; 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.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.database.UndoableTransaction; 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 { public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
static LocationTrackingSpec getLocationTrackingSpec(String name) { static LocationTrackingSpec getLocationTrackingSpec(String name) {
@@ -1351,4 +1358,127 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress()); 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));
}
} }
@@ -51,6 +51,7 @@ public class IconButtonTableCellEditor<R> extends AbstractCellEditor
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) { int row, int column) {
this.row = filterPanel.getRowObject(row); this.row = filterPanel.getRowObject(row);
button.setToolTipText(value.toString());
return button; return button;
} }
@@ -15,27 +15,31 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import java.awt.Component; import java.awt.*;
import java.awt.Dimension;
import javax.swing.Icon; import javax.swing.*;
import javax.swing.JButton;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGhidraColumnRenderer; import ghidra.util.table.column.AbstractGhidraColumnRenderer;
public class IconButtonTableCellRenderer public class IconButtonTableCellRenderer
extends AbstractGhidraColumnRenderer<String> { extends AbstractGhidraColumnRenderer<String> {
protected final JPanel panel = new JPanel();
protected final JButton button = new JButton(""); protected final JButton button = new JButton("");
public IconButtonTableCellRenderer(Icon icon, int buttonSize) { public IconButtonTableCellRenderer(Icon icon, int buttonSize) {
button.setIcon(icon); button.setIcon(icon);
button.setMinimumSize(new Dimension(buttonSize, buttonSize)); button.setMinimumSize(new Dimension(buttonSize, buttonSize));
panel.setMinimumSize(new Dimension(buttonSize, buttonSize));
panel.setLayout(new BorderLayout());
panel.add(button);
} }
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
return button; super.getTableCellRendererComponent(data); // Waste, but sets background
panel.setBackground(getBackground());
return panel;
} }
@Override @Override
@@ -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
*
* <p>
* 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
*
* <p>
* 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
*
* <p>
* 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;
}
}