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 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); }