diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java
index 146097f5f0..9f9c9f4e01 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java
@@ -53,8 +53,10 @@ import ghidra.util.exception.DuplicateNameException;
* Most of these are already integrated via the {@link DebuggerEmulationService}. Please see if that
* service satisfies your use case before employing these directly.
*/
-public enum ProgramEmulationUtils {
- ;
+public class ProgramEmulationUtils {
+ private ProgramEmulationUtils() {
+ }
+
public static final String BLOCK_NAME_STACK = "STACK";
/**
@@ -122,21 +124,27 @@ public enum ProgramEmulationUtils {
* Create regions for each block in a program, without relocation, and map the program in
*
*
- * This creates a region for each loaded, non-overlay block in the program. Permissions/flags
- * are assigned accordingly. A single static mapping is generated to cover the entire range of
- * created regions. Note that no bytes are copied in, as that could be prohibitive for large
- * programs. Instead, the emulator should load them, based on the static mapping, as needed.
+ * This creates a region for each loaded, block in the program. Typically, only non-overlay
+ * blocks are included. To activate an overlay space, include it in the set of
+ * {@code activeOverlays}. This will alter the mapping from the trace to the static program such
+ * that the specified overlays are effective. The gaps between overlays are mapped to their
+ * physical (non-overlay) portions. Permissions/flags are assigned accordingly. Note that no
+ * bytes are copied in, as that could be prohibitive for large programs. Instead, the emulator
+ * should load them, based on the static mapping, as needed.
*
*
* A transaction must already be started on the destination trace.
*
* @param snapshot the destination snapshot, usually 0
* @param program the program to load
+ * @param activeOverlays which overlay spaces to use
*/
- public static void loadExecutable(TraceSnapshot snapshot, Program program) {
+ public static void loadExecutable(TraceSnapshot snapshot, Program program,
+ List activeOverlays) {
Trace trace = snapshot.getTrace();
PathPattern patRegion = computePatternRegion(trace);
Map extremaBySpace = new HashMap<>();
+ Lifespan nowOn = Lifespan.nowOn(snapshot.getKey());
try {
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (!DebuggerStaticMappingUtils.isReal(block)) {
@@ -147,18 +155,35 @@ public enum ProgramEmulationUtils {
.consider(range);
String modName = getModuleName(program);
- // TODO: Do I populate modules, since the mapping will already be done?
+ // NB. No need to populate as module.
+ // UI will sync from mapping, so it's obvious where the cursor is.
String path = PathUtils.toString(patRegion
.applyKeys(block.getStart() + "-" + modName + ":" + block.getName())
.getSingletonPath());
trace.getMemoryManager()
.createRegion(path, snapshot.getKey(), range, getRegionFlags(block));
}
+ AddressSet identical = new AddressSet();
for (Extrema extrema : extremaBySpace.values()) {
+ identical.add(extrema.getMin(), extrema.getMax());
+ }
+ for (MemoryBlock block : program.getMemory().getBlocks()) {
+ if (!block.isOverlay() ||
+ !activeOverlays.contains(block.getStart().getAddressSpace())) {
+ continue;
+ }
+ Address phys = block.getStart().getPhysicalAddress();
DebuggerStaticMappingUtils.addMapping(
- new DefaultTraceLocation(trace, null, Lifespan.nowOn(snapshot.getKey()),
- extrema.getMin()),
- new ProgramLocation(program, extrema.getMin()), extrema.getLength(), false);
+ new DefaultTraceLocation(trace, null, nowOn, phys),
+ new ProgramLocation(program, block.getStart()),
+ block.getSize(), false);
+ identical.delete(phys, block.getEnd().getPhysicalAddress());
+ }
+ for (AddressRange range : identical) {
+ DebuggerStaticMappingUtils.addMapping(
+ new DefaultTraceLocation(trace, null, nowOn, range.getMinAddress()),
+ new ProgramLocation(program, range.getMinAddress()),
+ range.getLength(), false);
}
}
catch (TraceOverlappedRegionException | DuplicateNameException
@@ -407,7 +432,10 @@ public enum ProgramEmulationUtils {
TraceSnapshot initial =
trace.getTimeManager().createSnapshot(EMULATION_STARTED_AT + pc);
long snap = initial.getKey();
- loadExecutable(initial, program);
+ List overlays =
+ pc.getAddressSpace().isOverlaySpace() ? List.of(pc.getAddressSpace())
+ : List.of();
+ loadExecutable(initial, program, overlays);
doLaunchEmulationThread(trace, snap, program, pc, pc);
}
trace.clearUndo();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java
new file mode 100644
index 0000000000..30f10c03cf
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java
@@ -0,0 +1,790 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.gui.breakpoint;
+
+import static org.junit.Assert.*;
+
+import java.awt.event.MouseEvent;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import db.Transaction;
+import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
+import generic.Unique;
+import generic.test.category.NightlyCategory;
+import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
+import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
+import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
+import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
+import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
+import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
+import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
+import ghidra.app.services.*;
+import ghidra.dbg.model.TestTargetProcess;
+import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
+import ghidra.dbg.target.TargetBreakpointSpecContainer;
+import ghidra.debug.api.action.ActionSource;
+import ghidra.debug.api.breakpoint.LogicalBreakpoint;
+import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
+import ghidra.debug.api.control.ControlMode;
+import ghidra.debug.api.model.TraceRecorder;
+import ghidra.framework.store.LockException;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryConflictException;
+import ghidra.program.util.ProgramLocation;
+import ghidra.trace.model.*;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.trace.model.time.TraceSnapshot;
+import ghidra.util.SystemUtilities;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
+public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebuggerTest {
+ protected static final long TIMEOUT_MILLIS =
+ SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
+
+ protected DebuggerBreakpointsPlugin breakpointsPlugin;
+ protected DebuggerBreakpointsProvider breakpointsProvider;
+ protected DebuggerStaticMappingService mappingService;
+ protected DebuggerLogicalBreakpointService breakpointService;
+
+ @Before
+ public void setUpBreakpointsProviderTest() throws Exception {
+ breakpointsPlugin = addPlugin(tool, DebuggerBreakpointsPlugin.class);
+ breakpointsProvider = waitForComponentProvider(DebuggerBreakpointsProvider.class);
+ mappingService = tool.getService(DebuggerStaticMappingService.class);
+ breakpointService = tool.getService(DebuggerLogicalBreakpointService.class);
+ }
+
+ protected void addMapping(Trace trace, Program prog) throws Exception {
+ try (Transaction tx = trace.openTransaction("Add mapping")) {
+ DebuggerStaticMappingUtils.addMapping(
+ new DefaultTraceLocation(trace, null, Lifespan.nowOn(0), addr(trace, 0x55550000)),
+ new ProgramLocation(prog, addr(prog, 0x00400000)), 0x1000, false);
+ }
+ }
+
+ protected void addLiveMemoryAndBreakpoint(TestTargetProcess process, TraceRecorder recorder)
+ throws Exception {
+ process.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
+ addLiveBreakpoint(recorder, 0x55550123);
+ }
+
+ protected void addLiveBreakpoint(TraceRecorder recorder, long offset) throws Exception {
+ TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
+ cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
+ .get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ }
+
+ protected void addStaticMemoryAndBreakpoint() throws LockException, MemoryConflictException,
+ AddressOverflowException, CancelledException {
+ try (Transaction tx = program.openTransaction("Add bookmark break")) {
+ program.getMemory()
+ .createInitializedBlock(".text", addr(program, 0x00400000), 0x1000, (byte) 0,
+ TaskMonitor.DUMMY, false);
+ program.getBookmarkManager()
+ .setBookmark(addr(program, 0x00400123), LogicalBreakpoint.ENABLED_BOOKMARK_TYPE,
+ "SW_EXECUTE;1", "");
+ }
+ }
+
+ protected void assertProviderEmpty() {
+ assertTrue(breakpointsProvider.breakpointTableModel.getModelData().isEmpty());
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ waitForSwing();
+ assertProviderEmpty();
+ }
+
+ @Test
+ public void testAddLiveOpenTracePopulatesProvider() throws Throwable {
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+
+ addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
+ waitRecorder(recorder);
+
+ // NB, optionally open trace. Mapping only works if open...
+ traceManager.openTrace(trace);
+ waitForSwing();
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ assertEquals("55550123", row.getAddress().toString());
+ assertEquals(trace, row.getDomainObject());
+ assertEquals("SW_EXECUTE", row.getKinds());
+ assertEquals(State.INCONSISTENT_ENABLED, row.getState());
+ }
+
+ @Test
+ public void testToggleLiveViaTable() throws Exception {
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+
+ addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
+ waitForDomainObject(trace);
+
+ traceManager.openTrace(trace);
+ waitForSwing();
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ assertEquals(State.INCONSISTENT_ENABLED, row.getState());
+
+ // NB, the row does not take the value immediately, but via async callbacks
+ row.setEnabled(false);
+
+ waitForPass(() -> assertEquals(State.INCONSISTENT_DISABLED, row.getState()));
+
+ row.setEnabled(true);
+
+ waitForPass(() -> assertEquals(State.INCONSISTENT_ENABLED, row.getState()));
+ }
+
+ @Test
+ public void testOpenProgramAddBookmarkPopulatesProvider() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ assertEquals("00400123", row.getAddress().toString());
+ assertEquals(program, row.getDomainObject());
+ assertEquals("SW_EXECUTE", row.getKinds());
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+ }
+
+ @Test
+ public void testToggleStaticViaTable() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+
+ row.setEnabled(false); // Synchronous, but on swing thread
+ waitForDomainObject(program);
+
+ assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
+
+ row.setEnabled(true);
+ waitForDomainObject(program);
+
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+ }
+
+ @Test
+ public void testEnablementColumnMapped() throws Throwable {
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+ createProgramFromTrace(trace);
+ intoProject(trace);
+ intoProject(program);
+
+ addMapping(trace, program);
+ addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
+ addStaticMemoryAndBreakpoint();
+ programManager.openProgram(program);
+ traceManager.openTrace(trace);
+ // Because mapping service debounces, wait for breakpoints to be reconciled
+ waitForPass(() -> {
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ LogicalBreakpoint lb = row.getLogicalBreakpoint();
+ assertEquals(program, lb.getProgram());
+ assertEquals(Set.of(trace), lb.getParticipatingTraces());
+ assertEquals(State.ENABLED, row.getState());
+ });
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ LogicalBreakpoint lb = row.getLogicalBreakpoint();
+
+ lb.disableForProgram();
+ waitForDomainObject(program);
+
+ waitForPass(() -> assertEquals(State.INCONSISTENT_DISABLED, row.getState()));
+
+ // NOTE: This acts on the corresponding target, not directly on trace
+ waitOn(lb.disableForTrace(trace));
+ waitRecorder(recorder);
+
+ waitForPass(() -> assertEquals(State.DISABLED, row.getState()));
+
+ lb.enableForProgram();
+ waitForDomainObject(program);
+
+ waitForPass(() -> assertEquals(State.INCONSISTENT_ENABLED, row.getState()));
+
+ // This duplicates the initial case, but without it, I just feel incomplete
+ waitOn(lb.enableForTrace(trace));
+ waitRecorder(recorder);
+
+ waitForPass(() -> assertEquals(State.ENABLED, row.getState()));
+ }
+
+ @Test
+ public void testRenameStaticViaTable() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ assertEquals("", row.getName());
+
+ row.setName("Test name");
+ waitForDomainObject(program);
+
+ assertEquals("Test name", row.getName());
+
+ // Check that name persists, since bookmark is swapped
+ row.setEnabled(false);
+ waitForDomainObject(program);
+
+ assertEquals("Test name", row.getName());
+ }
+
+ // TODO: Test a scenario where one spec manifests two breaks, select both, and perform actions
+
+ // TODO: Test a scenario where one spec manifests the same mapped breakpoint in two traces
+
+ @Test
+ public void testActionEnableSelectedBreakpoints() throws Throwable {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ row.setEnabled(false);
+ breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
+ waitForSwing();
+
+ assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
+ assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+
+ performAction(breakpointsProvider.actionEnableSelectedBreakpoints);
+
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+ assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+
+ breakpointsProvider.breakpointTable.clearSelection();
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+
+ breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
+ waitForSwing();
+
+ assertTrue(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+
+ // Bookmark part should actually be synchronous.
+ waitOn(row.getLogicalBreakpoint().delete());
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionEnableSelectedBreakpoints.isEnabled());
+ }
+
+ @Test
+ public void testActionEnableAllBreakpoints() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
+
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ row.setEnabled(false);
+ waitForSwing();
+
+ assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
+ assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
+
+ performAction(breakpointsProvider.actionEnableAllBreakpoints);
+
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+ assertTrue(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
+
+ // Bookmark part should actually be synchronous.
+ row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionEnableAllBreakpoints.isEnabled());
+ }
+
+ @Test
+ public void testActionDisableSelectedBreakpoints() throws Throwable {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
+ waitForSwing();
+
+ waitForPass(() -> assertEquals(State.INEFFECTIVE_ENABLED, row.getState()));
+ assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+
+ performAction(breakpointsProvider.actionDisableSelectedBreakpoints);
+
+ waitForPass(() -> assertEquals(State.INEFFECTIVE_DISABLED, row.getState()));
+ assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+
+ breakpointsProvider.breakpointTable.clearSelection();
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+
+ breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
+ waitForSwing();
+
+ assertTrue(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+
+ // Bookmark part should actually be synchronous.
+ waitOn(row.getLogicalBreakpoint().delete());
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionDisableSelectedBreakpoints.isEnabled());
+ }
+
+ @Test
+ public void testActionDisableAllBreakpoints() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
+
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+ assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
+
+ performAction(breakpointsProvider.actionDisableAllBreakpoints);
+
+ assertEquals(State.INEFFECTIVE_DISABLED, row.getState());
+ assertTrue(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
+
+ // Bookmark part should actually be synchronous.
+ row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionDisableAllBreakpoints.isEnabled());
+ }
+
+ @Test
+ public void testActionClearSelectedBreakpoints() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
+
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
+
+ LogicalBreakpointRow row =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
+ waitForSwing();
+
+ assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
+
+ breakpointsProvider.breakpointTable.clearSelection();
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
+
+ breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
+ waitForSwing();
+
+ assertTrue(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
+
+ performAction(breakpointsProvider.actionClearSelectedBreakpoints);
+
+ assertProviderEmpty();
+ assertFalse(breakpointsProvider.actionClearSelectedBreakpoints.isEnabled());
+ }
+
+ @Test
+ public void testActionClearAllBreakpoints() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ assertFalse(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
+
+ addStaticMemoryAndBreakpoint();
+ waitForDomainObject(program);
+
+ assertTrue(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
+
+ performAction(breakpointsProvider.actionClearAllBreakpoints);
+
+ assertProviderEmpty();
+ assertFalse(breakpointsProvider.actionClearAllBreakpoints.isEnabled());
+ }
+
+ @Test
+ public void testActionMakeBreakpointsEffective() throws Exception {
+ DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
+
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+ createProgramFromTrace(trace);
+ intoProject(trace);
+ intoProject(program);
+
+ assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
+ programManager.openProgram(program);
+ assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
+ traceManager.openTrace(trace);
+ assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
+ addStaticMemoryAndBreakpoint();
+ assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
+
+ addMapping(trace, program);
+ waitForPass(() -> {
+ assertTrue(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
+ assertEquals(1,
+ consolePlugin.getRowCount(DebuggerMakeBreakpointsEffectiveActionContext.class));
+ });
+
+ performAction(breakpointsProvider.actionMakeBreakpointsEffective);
+
+ waitForPass(() -> {
+ assertFalse(breakpointsProvider.actionMakeBreakpointsEffective.isEnabled());
+ assertEquals(0,
+ consolePlugin.getRowCount(DebuggerMakeBreakpointsEffectiveActionContext.class));
+ });
+ }
+
+ protected static List copyModelData(
+ RowWrappedEnumeratedColumnTableModel, ?, R, ?> model) {
+ synchronized (model) {
+ return List.copyOf(model.getModelData());
+ }
+ }
+
+ @Test
+ public void testActionFilters() throws Throwable {
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+
+ TraceRecorder recorder1 = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace1 = recorder1.getTrace();
+
+ TraceRecorder recorder3 = modelService.recordTarget(mb.testProcess3,
+ createTargetTraceMapper(mb.testProcess3), ActionSource.AUTOMATIC);
+ Trace trace3 = recorder3.getTrace();
+
+ createProgramFromTrace(trace1);
+ intoProject(trace1);
+ intoProject(trace3);
+ intoProject(program);
+
+ addMapping(trace1, program);
+ addMapping(trace3, program);
+ addLiveMemoryAndBreakpoint(mb.testProcess1, recorder1);
+ addLiveBreakpoint(recorder1, 0x55550321);
+ addLiveMemoryAndBreakpoint(mb.testProcess3, recorder3);
+ addLiveBreakpoint(recorder3, 0x55550321);
+ waitRecorder(recorder1);
+ waitRecorder(recorder3);
+ addStaticMemoryAndBreakpoint();
+ // Note, no program breakpoint for 0321...
+
+ programManager.openProgram(program);
+ traceManager.openTrace(trace1);
+ CompletableFuture mappingsSettled = mappingService.changesSettled();
+ CompletableFuture breakpointsSettled = breakpointService.changesSettled();
+ traceManager.openTrace(trace3);
+ waitForSwing();
+ waitOn(mappingsSettled);
+ waitOn(breakpointsSettled);
+ waitForSwing();
+
+ LogicalBreakpointTableModel bptModel = breakpointsProvider.breakpointTableModel;
+
+ List data = copyModelData(bptModel);
+ assertEquals(2, data.size());
+ LogicalBreakpointRow row1 = data.get(0);
+ LogicalBreakpointRow row2 = data.get(1);
+ LogicalBreakpoint lb1 = row1.getLogicalBreakpoint();
+ LogicalBreakpoint lb2 = row2.getLogicalBreakpoint();
+ assertEquals(program, lb1.getProgram());
+ assertEquals(program, lb2.getProgram());
+ assertEquals(addr(program, 0x00400123), lb1.getAddress());
+ assertEquals(addr(program, 0x00400321), lb2.getAddress());
+ assertEquals(Set.of(trace1, trace3), lb1.getParticipatingTraces());
+ assertEquals(Set.of(trace1, trace3), lb2.getParticipatingTraces());
+
+ // Sanity check / experiment: Equal fields, but from different traces
+ TraceBreakpoint bl1t1 = Unique.assertOne(lb1.getTraceBreakpoints(trace1));
+ TraceBreakpoint bl1t3 = Unique.assertOne(lb1.getTraceBreakpoints(trace3));
+ assertNotEquals(bl1t1, bl1t3);
+
+ // OK, back to work
+ assertEquals(2, lb1.getTraceBreakpoints().size());
+ assertEquals(2, lb2.getTraceBreakpoints().size());
+
+ List filtLocs =
+ breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
+
+ for (LogicalBreakpointRow breakRow : data) {
+ assertEquals(2, breakRow.getLocationCount());
+ }
+ assertEquals(4, filtLocs.size());
+
+ assertTrue(breakpointsProvider.actionFilterByCurrentTrace.isEnabled());
+ performAction(breakpointsProvider.actionFilterByCurrentTrace);
+
+ // No trace active, so empty :)
+ data = copyModelData(bptModel);
+ filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
+ for (LogicalBreakpointRow breakRow : data) {
+ assertEquals(0, breakRow.getLocationCount());
+ }
+ assertEquals(0, filtLocs.size());
+
+ traceManager.activateTrace(trace1);
+ waitForSwing();
+
+ data = copyModelData(bptModel);
+ filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
+ for (LogicalBreakpointRow breakRow : data) {
+ assertEquals(1, breakRow.getLocationCount());
+ }
+ assertEquals(2, filtLocs.size());
+
+ assertTrue(breakpointsProvider.actionFilterLocationsByBreakpoints.isEnabled());
+ performAction(breakpointsProvider.actionFilterLocationsByBreakpoints);
+
+ // No breakpoint selected, so no change, yet.
+ data = copyModelData(bptModel);
+ filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
+ for (LogicalBreakpointRow breakRow : data) {
+ assertEquals(1, breakRow.getLocationCount());
+ }
+ assertEquals(2, filtLocs.size());
+
+ LogicalBreakpointRow bpRow = data.get(0);
+ runSwing(() -> breakpointsProvider
+ .setSelectedBreakpoints(Set.of(bpRow.getLogicalBreakpoint())));
+ waitForSwing();
+
+ filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
+ assertEquals(1, filtLocs.size());
+
+ assertTrue(breakpointsProvider.actionFilterByCurrentTrace.isEnabled());
+ performAction(breakpointsProvider.actionFilterByCurrentTrace);
+
+ data = copyModelData(bptModel);
+ filtLocs = breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
+ for (LogicalBreakpointRow breakRow : data) {
+ assertEquals(2, breakRow.getLocationCount());
+ }
+ assertEquals(2, filtLocs.size());
+ }
+
+ public static final Set POPUP_ACTIONS = Set.of(
+ AbstractEnableSelectedBreakpointsAction.NAME, AbstractDisableSelectedBreakpointsAction.NAME,
+ AbstractClearSelectedBreakpointsAction.NAME);
+
+ @Test
+ public void testPopupActionsOnBreakpointSelections() throws Exception {
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ addStaticMemoryAndBreakpoint();
+ waitForProgram(program);
+
+ // NOTE: the row becomes selected by right-click
+ clickTableCellWithButton(breakpointsProvider.breakpointTable, 0, 0, MouseEvent.BUTTON3);
+ waitForSwing();
+ assertMenu(POPUP_ACTIONS,
+ Set.of(AbstractEnableSelectedBreakpointsAction.NAME,
+ AbstractDisableSelectedBreakpointsAction.NAME,
+ AbstractClearSelectedBreakpointsAction.NAME));
+
+ // NOTE: With no selection, no actions (even table built-in) apply, so no menu
+ }
+
+ @Test
+ public void testEmuBreakpointState() throws Throwable {
+ addPlugin(tool, DebuggerControlServicePlugin.class);
+
+ createProgram();
+ intoProject(program);
+ programManager.openProgram(program);
+ waitForSwing();
+
+ addStaticMemoryAndBreakpoint();
+ waitForProgram(program);
+
+ LogicalBreakpointRow row = waitForValue(
+ () -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+
+ // Do our own launch, so that object mode is enabled during load (region creation)
+ createTrace(program.getLanguageID().getIdAsString());
+ try (Transaction startTransaction = tb.startTransaction()) {
+ TraceSnapshot initial = tb.trace.getTimeManager().getSnapshot(0, true);
+ ProgramEmulationUtils.loadExecutable(initial, program, List.of());
+ Address pc = program.getMinAddress();
+ ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, 0, program, pc, pc);
+ }
+
+ traceManager.openTrace(tb.trace);
+ traceManager.activateTrace(tb.trace);
+ waitForSwing();
+ waitOn(mappingService.changesSettled());
+ waitOn(breakpointService.changesSettled());
+ waitForSwing();
+
+ row = waitForValue(
+ () -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+
+ row.setEnabled(true);
+ waitForSwing();
+
+ row = waitForValue(
+ () -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
+ assertEquals(State.ENABLED, row.getState());
+ }
+
+ @Test
+ public void testTablesAndStatesWhenhModeChanges() throws Throwable {
+ DebuggerControlService controlService =
+ addPlugin(tool, DebuggerControlServicePlugin.class);
+
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+ createProgramFromTrace(trace);
+ intoProject(trace);
+ intoProject(program);
+
+ mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
+ waitRecorder(recorder);
+ addMapping(trace, program);
+ addStaticMemoryAndBreakpoint();
+ programManager.openProgram(program);
+ traceManager.openTrace(trace);
+ waitForSwing();
+
+ LogicalBreakpointRow lbRow1 = waitForPass(() -> {
+ LogicalBreakpointRow newRow =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ LogicalBreakpoint lb = newRow.getLogicalBreakpoint();
+ assertEquals(program, lb.getProgram());
+ assertEquals(Set.of(trace), lb.getMappedTraces());
+ assertEquals(Set.of(), lb.getParticipatingTraces());
+ assertEquals(State.INEFFECTIVE_ENABLED, newRow.getState());
+ return newRow;
+ });
+
+ controlService.setCurrentMode(trace, ControlMode.RW_EMULATOR);
+ lbRow1.setEnabled(true);
+ TraceBreakpoint emuBpt = waitForValue(
+ () -> Unique.assertAtMostOne(trace.getBreakpointManager().getAllBreakpoints()));
+ assertNull(recorder.getTargetBreakpoint(emuBpt));
+
+ LogicalBreakpointRow lbRow2 =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ waitForPass(() -> assertEquals(State.ENABLED, lbRow2.getState()));
+
+ waitForPass(() -> {
+ BreakpointLocationRow newRow =
+ Unique.assertOne(breakpointsProvider.locationTableModel.getModelData());
+ assertEquals(State.ENABLED, newRow.getState());
+ });
+
+ for (int i = 0; i < 3; i++) {
+ controlService.setCurrentMode(trace, ControlMode.RO_TARGET);
+ waitOn(breakpointService.changesSettled());
+ waitForSwing();
+ assertEquals(0, breakpointsProvider.locationTableModel.getModelData().size());
+
+ controlService.setCurrentMode(trace, ControlMode.RW_EMULATOR);
+ waitOn(breakpointService.changesSettled());
+ waitForSwing();
+ assertEquals(1, breakpointsProvider.locationTableModel.getModelData().size());
+ }
+ }
+}
diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointsProviderTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointsProviderTest.java
index 8db783b869..b5d282d5ea 100644
--- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointsProviderTest.java
+++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointsProviderTest.java
@@ -711,7 +711,7 @@ public abstract class AbstractDebuggerBreakpointsProviderTest
createTrace(program.getLanguageID().getIdAsString());
try (Transaction startTransaction = tb.startTransaction()) {
TraceSnapshot initial = tb.trace.getTimeManager().getSnapshot(0, true);
- ProgramEmulationUtils.loadExecutable(initial, program);
+ ProgramEmulationUtils.loadExecutable(initial, program, List.of());
Address pc = program.getMinAddress();
ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, 0, program, pc, pc);
}