GP-885: Better indication of ineffective breakpoints. Toggles on lower table.

This commit is contained in:
Dan
2021-04-23 16:12:22 -04:00
parent 128f8bc3d3
commit be8e27150a
34 changed files with 941 additions and 180 deletions
@@ -31,6 +31,9 @@ src/main/help/help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.ht
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/DebuggerBreakpointsPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-disable.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-enable.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-ineffective-d.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-ineffective-e.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-mixed-de.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-mixed-ed.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-clear-all.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-disable-all.png||GHIDRA||||END|
@@ -120,6 +123,8 @@ src/main/resources/images/blank.png||GHIDRA||||END|
src/main/resources/images/breakpoint-clear.png||GHIDRA||||END|
src/main/resources/images/breakpoint-disable.png||GHIDRA||||END|
src/main/resources/images/breakpoint-enable.png||GHIDRA||||END|
src/main/resources/images/breakpoint-ineffective-d.png||GHIDRA||||END|
src/main/resources/images/breakpoint-ineffective-e.png||GHIDRA||||END|
src/main/resources/images/breakpoint-mixed-de.png||GHIDRA||||END|
src/main/resources/images/breakpoint-mixed-ed.png||GHIDRA||||END|
src/main/resources/images/breakpoint-set.png||GHIDRA||||END|
@@ -165,6 +170,8 @@ src/main/svg/blank.svg||GHIDRA||||END|
src/main/svg/breakpoint-clear.svg||GHIDRA||||END|
src/main/svg/breakpoint-disable.svg||GHIDRA||||END|
src/main/svg/breakpoint-enable.svg||GHIDRA||||END|
src/main/svg/breakpoint-ineffective-d.svg||GHIDRA||||END|
src/main/svg/breakpoint-ineffective-e.svg||GHIDRA||||END|
src/main/svg/breakpoint-mixed-de.svg||GHIDRA||||END|
src/main/svg/breakpoint-mixed-ed.svg||GHIDRA||||END|
src/main/svg/breakpoint-set.svg||GHIDRA||||END|
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

@@ -25,7 +25,7 @@
<P>Breakpoints refer to any mechanism which may trap execution based on an address. The
breakpoints manager presents all active breakpoints among all open programs and live traces.
Note that dead traces are not considered, and only the breakpoints at the present are
considered, even if the user has stepped a trace backward. Breakpoints which map to the same
displayed, even if the user has stepped a trace backward. Breakpoints which map to the same
address in the same module, i.e., program image, and otherwise share the same attributes, are
grouped into a logical breakpoint. <B>NOTE:</B> The breakpoints window cannot display or
manipulate breakpoints from a target until that target is recorded into a trace. See the <A
@@ -46,28 +46,46 @@
the grouping of breakpoint locations into logical breakpoints by this manager is done
<EM>without respect</EM> to the specifications. Often the specification is at a higher stratum
than Ghidra natively understands, e.g., the source filename and line number, and so such
specifications are not relevant. Note, however, that the model may not permit locations to be
specifications are not relevant. Note, however, that the model might not permit locations to be
manipulated independently of their specification, which may limit how the manager can operate
on each breakpoint location.</P>
<P>Because of the logical grouping of breakpoints, it is possible for a breakpoint to be in a
mixed or inconsistent state. This happens quite commonly, e.g., when a breakpoint is placed in
a Ghidra program before that program is mapped in any traced target. Once mapped in, the
location of that breakpoint in the trace is computed and noted as missing. A missing location
in a logical breakpoint is treated the same as a disabled breakpoint. Thus, the logical
breakpoint enters an inconsistent state, because it is "enabled" in the program, but "disabled"
in the trace. Toggling such a breakpoint enables all missing locations, bringing it into a
consistent enabled state. Toggling it again will disable all locations.</P>
location of that breakpoint in the trace is computed and noted as missing. A logical breakpoint
without any location in a trace (i.e., on an actual target) is called "ineffective" and is
drawn in grey. An enabled logical breakpoint having a disabled location is called
"inconsistent" and its icon will indicate that state. A disabled logical breakpoint having an
enabled location is similarly "inconsistent." Toggling ineffective or inconsistent breakpoints
enables and/or places all locations, bringing it into a consistent enabled state. Toggling it
again disables all locations.</P>
<H2>Tables and Columns</H2>
<P>The top table, which lists logical breakpoints, has the following columns:</P>
<UL>
<LI>Enabled - displays an icon indicating the state of the breakpoint: <IMG alt="" src=
"images/breakpoint-enable.png"> Enabled, <IMG alt="" src="images/breakpoint-disable.png">
Disabled, or <IMG alt="" src="images/breakpoint-mixed-ed.png"> Inconsistent. Clicking the
icon toggles the breakpoint.</LI>
<LI>Enabled - displays an icon indicating the state of the breakpoint. Clicking the icon
toggles the breakpoint.</LI>
<LI style="list-style: none">
<UL>
<LI><IMG alt="" src="images/breakpoint-enable.png"> Enabled</LI>
<LI><IMG alt="" src="images/breakpoint-disable.png"> Disabled</LI>
<LI><IMG alt="" src="images/breakpoint-mixed-ed.png"> Inconsistent: Enabled with disabled
locations</LI>
<LI><IMG alt="" src="images/breakpoint-mixed-de.png"> Inconsistent: Disabled with enabled
locations</LI>
<LI><IMG alt="" src="images/breakpoint-ineffective-e.png"> Enabled but ineffective</LI>
<LI><IMG alt="" src="images/breakpoint-ineffective-d.png"> Disabled and ineffective</LI>
</UL>
</LI>
<LI>Image - gives the name of the Ghidra program, if the breakpoint is mapped to one.</LI>
@@ -89,6 +107,9 @@
<P>The bottom table, which lists trace breakpoint locations, has the following columns:</P>
<UL>
<LI>Enabled - displays an icon indicating the state of the location. Clicking the icon
toggles the location.</LI>
<LI>Name - displays the name given to the location by the connected debugger. This field is
user modifiable.</LI>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

@@ -252,10 +252,14 @@ public interface DebuggerResources {
String MARKER_NAME_BREAKPOINT_ENABLED = "Enabled Breakpoint";
String MARKER_NAME_BREAKPOINT_DISABLED = "Disabled Breakpoint";
String MARKER_NAME_BREAKPOINT_INEFFECTIVE_E = "Ineffective Enabled Breakpoint";
String MARKER_NAME_BREAKPOINT_INEFFECTIVE_D = "Ineffective Disabled Breakpoint";
String MARKER_NAME_BREAKPOINT_MIXED_ED = "Mixed Enabled-Disabled Breakpont";
String MARKER_NAME_BREAKPOINT_MIXED_DE = "Mixed Disabled-Enabled Breakpont";
int PRIORITY_BREAKPOINT_ENABLED_MARKER = MarkerService.BREAKPOINT_PRIORITY;
int PRIORITY_BREAKPOINT_DISABLED_MARKER = MarkerService.BREAKPOINT_PRIORITY;
int PRIORITY_BREAKPOINT_INEFFECTIVE_E_MARKER = MarkerService.BREAKPOINT_PRIORITY;
int PRIORITY_BREAKPOINT_INEFFECTIVE_D_MARKER = MarkerService.BREAKPOINT_PRIORITY;
int PRIORITY_BREAKPOINT_MIXED_ED_MARKER = MarkerService.BREAKPOINT_PRIORITY;
int PRIORITY_BREAKPOINT_MIXED_DE_MARKER = MarkerService.BREAKPOINT_PRIORITY;
ImageIcon ICON_BREAKPOINT_ENABLED_MARKER = ICON_ENABLE_BREAKPOINT;
@@ -264,6 +268,10 @@ public interface DebuggerResources {
ResourceManager.loadImage("images/breakpoint-mixed-ed.png");
ImageIcon ICON_BREAKPOINT_MIXED_DE_MARKER =
ResourceManager.loadImage("images/breakpoint-mixed-de.png");
ImageIcon ICON_BREAKPOINT_INEFFECTIVE_E_MARKER =
ResourceManager.loadImage("images/breakpoint-ineffective-e.png");
ImageIcon ICON_BREAKPOINT_INEFFECTIVE_D_MARKER =
ResourceManager.loadImage("images/breakpoint-ineffective-d.png");
Icon ICON_UNIQUE_REF_READ =
new RotateIcon(ResourceManager.loadImage("images/cursor_arrow.gif"), 180); // TODO
@@ -274,6 +282,13 @@ public interface DebuggerResources {
Color DEFAULT_COLOR_ENABLED_BREAKPOINT_MARKERS = new Color(0.875f, 0.75f, 0.75f);
String OPTION_NAME_COLORS_DISABLED_BREAKPOINT_MARKERS = "Colors.Disabled Breakpoint Markers";
Color DEFAULT_COLOR_DISABLED_BREAKPOINT_MARKERS = DEFAULT_COLOR_ENABLED_BREAKPOINT_MARKERS;
String OPTION_NAME_COLORS_INEFFECTIVE_E_BREAKPOINT_MARKERS =
"Colors.Ineffective Enabled Breakpoint Markers";
Color DEFAULT_COLOR_INEFFECTIVE_E_BREAKPOINT_MARKERS = new Color(0.75f, 0.75f, 0.75f);
String OPTION_NAME_COLORS_INEFFECTIVE_D_BREAKPOINT_MARKERS =
"Colors.Ineffective Disabled Breakpoint Markers";
Color DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_MARKERS =
DEFAULT_COLOR_INEFFECTIVE_E_BREAKPOINT_MARKERS;
String OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND =
"Colors.Enabled Breakpoint Markers Have Background";
@@ -283,6 +298,14 @@ public interface DebuggerResources {
"Colors.Disabled Breakpoint Markers Have Background";
boolean DEFAULT_COLOR_DISABLED_BREAKPOINT_COLORING_BACKGROUND = false;
String OPTION_NAME_COLORS_INEFFECTIVE_E_BREAKPOINT_COLORING_BACKGROUND =
"Colors.Ineffective Enabled Breakpoint Markers Have Background";
boolean DEFAULT_COLOR_INEFFECTIVE_E_BREAKPOINT_COLORING_BACKGROUND = true;
String OPTION_NAME_COLORS_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND =
"Colors.Ineffective Disabled Breakpoint Markers Have Background";
boolean DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND = false;
// TODO: Re-assign/name groups
String GROUP_GENERAL = "Dbg1. General";
String GROUP_CONNECTION = "Dbg2. Connection";
@@ -17,15 +17,20 @@ package ghidra.app.plugin.core.debug.gui.breakpoint;
import java.util.stream.Collectors;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetBreakpointLocation;
import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
public class BreakpointLocationRow {
private final DebuggerBreakpointsProvider provider;
private final TraceBreakpoint loc;
public BreakpointLocationRow(TraceBreakpoint loc) {
public BreakpointLocationRow(DebuggerBreakpointsProvider provider, TraceBreakpoint loc) {
this.provider = provider;
this.loc = loc;
}
@@ -33,6 +38,27 @@ public class BreakpointLocationRow {
return loc.getName();
}
public boolean isEnabled() {
return loc.isEnabled();
}
public void setEnabled(boolean enabled) {
TraceRecorder recorder = provider.modelService.getRecorder(loc.getTrace());
TargetBreakpointLocation bpt = recorder.getTargetBreakpoint(loc);
if (enabled) {
bpt.getSpecification().enable().exceptionally(ex -> {
Msg.showError(this, null, "Toggle breakpoint", "Could not enable breakpoint", ex);
return null;
});
}
else {
bpt.getSpecification().disable().exceptionally(ex -> {
Msg.showError(this, null, "Toggle breakpoint", "Could not disable breakpoint", ex);
return null;
});
}
}
public void setName(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(loc.getTrace(), "Set breakpoint name", true)) {
@@ -0,0 +1,80 @@
/* ###
* 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 java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.table.TableCellEditor;
import docking.widgets.table.GTableFilterPanel;
import ghidra.app.services.LogicalBreakpoint.Enablement;
public class DebuggerBreakpointEnablementTableCellEditor extends AbstractCellEditor
implements TableCellEditor, ActionListener {
private final GTableFilterPanel<LogicalBreakpointRow> filterPanel;
protected final JButton button = new JButton();
private Enablement value = Enablement.NONE;
private LogicalBreakpointRow row;
public DebuggerBreakpointEnablementTableCellEditor(
GTableFilterPanel<LogicalBreakpointRow> filterPanel) {
this.filterPanel = filterPanel;
button.setHorizontalAlignment(SwingConstants.CENTER);
button.setOpaque(true);
button.setBorder(BorderFactory.createEmptyBorder());
button.setUI(new BasicButtonUI());
button.addActionListener(this);
}
@Override
public Object getCellEditorValue() {
return value;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
if (isSelected) {
button.setBackground(table.getSelectionBackground());
}
else {
// TODO: Alternating colors? Can't inherit GTableCellRenderer....
button.setBackground(table.getBackground());
}
this.row = filterPanel.getRowObject(row);
this.value = (Enablement) value;
button.setIcon(DebuggerBreakpointEnablementTableCellRenderer.iconForEnablement(this.value));
button.setHorizontalAlignment(SwingConstants.CENTER);
return button;
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO: Consider the current trace if "current trace only" filter is on?
boolean curEn = row.getLogicalBreakpoint().getMappedTraces().isEmpty()
? value.enabled // Toggle when no traces are mappable
: value == Enablement.ENABLED; // Make consistent as enabled if mappable
value = curEn ? Enablement.DISABLED : Enablement.ENABLED;
fireEditingStopped();
}
}
@@ -17,36 +17,55 @@ package ghidra.app.plugin.core.debug.gui.breakpoint;
import java.awt.Component;
import javax.swing.Icon;
import javax.swing.SwingConstants;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class DebuggerBreakpointEnablementTableCellRenderer
extends AbstractGColumnRenderer<Boolean> {
extends AbstractGColumnRenderer<Enablement> {
protected static Icon iconForEnablement(Enablement en) {
switch (en) {
case NONE:
return null;
case ENABLED:
return DebuggerResources.ICON_BREAKPOINT_ENABLED_MARKER;
case DISABLED:
return DebuggerResources.ICON_BREAKPOINT_DISABLED_MARKER;
case INEFFECTIVE_ENABLED:
return DebuggerResources.ICON_BREAKPOINT_INEFFECTIVE_E_MARKER;
case INEFFECTIVE_DISABLED:
return DebuggerResources.ICON_BREAKPOINT_INEFFECTIVE_D_MARKER;
case ENABLED_DISABLED:
return DebuggerResources.ICON_BREAKPOINT_MIXED_ED_MARKER;
case DISABLED_ENABLED:
return DebuggerResources.ICON_BREAKPOINT_MIXED_DE_MARKER;
default:
throw new AssertionError(en);
}
}
public DebuggerBreakpointEnablementTableCellRenderer() {
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
Boolean enabled = (Boolean) data.getValue();
if (enabled == null) {
/**
* TODO: Distinguish DE from ED. Will need Enablement, not just Boolean. Will also
* require custom "cell editor".
*/
setIcon(DebuggerResources.ICON_BREAKPOINT_MIXED_ED_MARKER);
}
else if (enabled) {
setIcon(DebuggerResources.ICON_BREAKPOINT_ENABLED_MARKER);
}
else {
setIcon(DebuggerResources.ICON_BREAKPOINT_DISABLED_MARKER);
}
Enablement en = (Enablement) data.getValue();
setIcon(iconForEnablement(en));
setHorizontalAlignment(SwingConstants.CENTER);
setText("");
return this;
}
@Override
public String getFilterString(Boolean t, Settings settings) {
return t == null ? "Mixed" : t ? "Enabled" : "Disabled";
public String getFilterString(Enablement t, Settings settings) {
return t.name();
}
}
@@ -0,0 +1,67 @@
/* ###
* 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 java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.table.TableCellEditor;
public class DebuggerBreakpointLocEnabledTableCellEditor extends AbstractCellEditor
implements TableCellEditor, ActionListener {
protected final JButton button = new JButton();
private Boolean value = false;
public DebuggerBreakpointLocEnabledTableCellEditor() {
button.setHorizontalAlignment(SwingConstants.CENTER);
button.setOpaque(true);
button.setBorder(BorderFactory.createEmptyBorder());
button.setUI(new BasicButtonUI());
button.addActionListener(this);
}
@Override
public Object getCellEditorValue() {
return value;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
if (isSelected) {
button.setBackground(table.getSelectionBackground());
}
else {
// TODO: Alternating colors? Can't inherit GTableCellRenderer....
button.setBackground(table.getBackground());
}
this.value = (Boolean) value;
button.setIcon(DebuggerBreakpointLocEnabledTableCellRenderer.iconForEnabled(this.value));
button.setHorizontalAlignment(SwingConstants.CENTER);
return button;
}
@Override
public void actionPerformed(ActionEvent e) {
value = value == null ? true : !value;
fireEditingStopped();
}
}
@@ -0,0 +1,56 @@
/* ###
* 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 java.awt.Component;
import javax.swing.Icon;
import javax.swing.SwingConstants;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class DebuggerBreakpointLocEnabledTableCellRenderer
extends AbstractGColumnRenderer<Boolean> {
protected static Icon iconForEnabled(Boolean enabled) {
return enabled == null ? null
: enabled
? DebuggerResources.ICON_BREAKPOINT_ENABLED_MARKER
: DebuggerResources.ICON_BREAKPOINT_DISABLED_MARKER;
}
public DebuggerBreakpointLocEnabledTableCellRenderer() {
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
Boolean en = (Boolean) data.getValue();
setIcon(iconForEnabled(en));
setHorizontalAlignment(SwingConstants.CENTER);
setText("");
return this;
}
@Override
public String getFilterString(Boolean t, Settings settings) {
return t == null ? "null" : t ? "enabled" : "disabled";
}
}
@@ -56,21 +56,21 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.Msg;
@PluginInfo( //
shortDescription = "Debugger breakpoint marker service plugin", //
description = "Marks logical breakpoints in the listings", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = { //
ProgramOpenedPluginEvent.class, //
ProgramClosedPluginEvent.class, //
TraceOpenedPluginEvent.class, //
TraceClosedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerLogicalBreakpointService.class, //
MarkerService.class, //
} //
shortDescription = "Debugger breakpoint marker service plugin", //
description = "Marks logical breakpoints in the listings", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = { //
ProgramOpenedPluginEvent.class, //
ProgramClosedPluginEvent.class, //
TraceOpenedPluginEvent.class, //
TraceClosedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerLogicalBreakpointService.class, //
MarkerService.class, //
} //
)
public class DebuggerBreakpointMarkerPlugin extends Plugin
implements PopupActionProvider {
@@ -173,8 +173,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
protected static long computeDefaultLength(ActionContext context,
Collection<TraceBreakpointKind> selected) {
if (selected.contains(TraceBreakpointKind.EXECUTE) ||
selected.contains(TraceBreakpointKind.SOFTWARE)) {
if (selected.contains(TraceBreakpointKind.HW_EXECUTE) ||
selected.contains(TraceBreakpointKind.SW_EXECUTE)) {
return 1;
}
return computeLengthFromContext(context);
@@ -191,11 +191,11 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
Listing listing = loc.getProgram().getListing();
CodeUnit cu = listing.getCodeUnitContaining(loc.getAddress());
if (cu instanceof Instruction) {
if (supported.contains(TraceBreakpointKind.SOFTWARE)) {
return Set.of(TraceBreakpointKind.SOFTWARE);
if (supported.contains(TraceBreakpointKind.SW_EXECUTE)) {
return Set.of(TraceBreakpointKind.SW_EXECUTE);
}
else if (supported.contains(TraceBreakpointKind.EXECUTE)) {
return Set.of(TraceBreakpointKind.EXECUTE);
else if (supported.contains(TraceBreakpointKind.HW_EXECUTE)) {
return Set.of(TraceBreakpointKind.HW_EXECUTE);
}
return Set.of();
}
@@ -219,6 +219,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
final MarkerSet enabled;
final MarkerSet disabled;
final MarkerSet ineffectiveE;
final MarkerSet ineffectiveD;
final MarkerSet mixedED;
final MarkerSet mixedDE;
@@ -240,6 +242,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
enabled = getEnabledMarkerSet();
disabled = getDisabledMarkerSet();
ineffectiveE = getIneffectiveEMarkerSet();
ineffectiveD = getIneffectiveDMarkerSet();
mixedED = getMixedEDMarkerSet();
mixedDE = getMixedDEMarkerSet();
}
@@ -272,6 +276,36 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
false);
}
private MarkerSet getIneffectiveEMarkerSet() {
MarkerSet set = markerService
.getMarkerSet(DebuggerResources.MARKER_NAME_BREAKPOINT_INEFFECTIVE_E, program);
if (set != null) {
return set;
}
return markerService.createPointMarker(
DebuggerResources.MARKER_NAME_BREAKPOINT_INEFFECTIVE_E,
DebuggerResources.MARKER_NAME_BREAKPOINT_INEFFECTIVE_E, program,
DebuggerResources.PRIORITY_BREAKPOINT_INEFFECTIVE_E_MARKER, true, false, true,
breakpointIneffectiveEMarkerColor,
DebuggerResources.ICON_BREAKPOINT_INEFFECTIVE_E_MARKER,
false);
}
private MarkerSet getIneffectiveDMarkerSet() {
MarkerSet set = markerService
.getMarkerSet(DebuggerResources.MARKER_NAME_BREAKPOINT_INEFFECTIVE_D, program);
if (set != null) {
return set;
}
return markerService.createPointMarker(
DebuggerResources.MARKER_NAME_BREAKPOINT_INEFFECTIVE_D,
DebuggerResources.MARKER_NAME_BREAKPOINT_INEFFECTIVE_D, program,
DebuggerResources.PRIORITY_BREAKPOINT_INEFFECTIVE_D_MARKER, true, false, false,
breakpointIneffectiveDMarkerColor,
DebuggerResources.ICON_BREAKPOINT_INEFFECTIVE_D_MARKER,
false);
}
private MarkerSet getMixedEDMarkerSet() {
MarkerSet set = markerService
.getMarkerSet(DebuggerResources.MARKER_NAME_BREAKPOINT_MIXED_ED, program);
@@ -306,6 +340,10 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
return enabled;
case DISABLED:
return disabled;
case INEFFECTIVE_ENABLED:
return ineffectiveE;
case INEFFECTIVE_DISABLED:
return ineffectiveD;
case ENABLED_DISABLED:
return mixedED;
case DISABLED_ENABLED:
@@ -335,6 +373,18 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
public void setIneffectiveEnabledMarkerColor(Color color) {
if (ineffectiveE != null) {
ineffectiveE.setMarkerColor(color);
}
}
public void setIneffectiveDisabledMarkerColor(Color color) {
if (ineffectiveD != null) {
ineffectiveD.setMarkerColor(color);
}
}
public void setEnabledColoringBackground(boolean coloringBackground) {
if (enabled != null) {
enabled.setColoringBackground(coloringBackground);
@@ -353,6 +403,18 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
public void setIneffectiveEnabledColoringBackground(boolean coloringBackground) {
if (ineffectiveE != null) {
ineffectiveE.setColoringBackground(coloringBackground);
}
}
public void setIneffectiveDisabledColoringBackground(boolean coloringBackground) {
if (ineffectiveD != null) {
ineffectiveD.setColoringBackground(coloringBackground);
}
}
public void dispose() {
if (enabled != null) {
markerService.removeMarker(enabled, program);
@@ -360,6 +422,12 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (disabled != null) {
markerService.removeMarker(disabled, program);
}
if (ineffectiveE != null) {
markerService.removeMarker(ineffectiveE, program);
}
if (ineffectiveD != null) {
markerService.removeMarker(ineffectiveD, program);
}
if (mixedED != null) {
markerService.removeMarker(mixedED, program);
}
@@ -375,6 +443,12 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (disabled != null) {
disabled.clearAll();
}
if (ineffectiveE != null) {
ineffectiveE.clearAll();
}
if (ineffectiveD != null) {
ineffectiveD.clearAll();
}
if (mixedED != null) {
mixedED.clearAll();
}
@@ -409,12 +483,14 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
return breakpoint.computeEnablementForTrace(view.getTrace());
}
// Program view should consider all trace placements
// TODO: A mode for only considering the current trace (for effectiveness in program)
return breakpoint.computeEnablement();
}
/**
* TODO: Document me
*
* <p>
* This is a little different from that in the breakpoint service.
*
* @param loc
@@ -657,30 +733,62 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_MARKERS, //
description = "Background color for memory at an enabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_MARKERS, //
description = "Background color for memory at an enabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
private Color breakpointEnabledMarkerColor =
DebuggerResources.DEFAULT_COLOR_ENABLED_BREAKPOINT_MARKERS;
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND, //
description = "Whether or not to color background for memory at an enabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND, //
description = "Whether or not to color background for memory at an enabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
private boolean breakpointEnabledColoringBackground =
DebuggerResources.DEFAULT_COLOR_ENABLED_BREAKPOINT_COLORING_BACKGROUND;
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_MARKERS, //
description = "Background color for memory at a disabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_MARKERS, //
description = "Background color for memory at a disabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
private Color breakpointDisabledMarkerColor =
DebuggerResources.DEFAULT_COLOR_DISABLED_BREAKPOINT_MARKERS;
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND, //
description = "Whether or not to color background for memory at a disabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND, //
description = "Whether or not to color background for memory at a disabled breakpoint", //
help = @HelpInfo(anchor = "colors"))
private boolean breakpointDisabledColoringBackground =
DebuggerResources.DEFAULT_COLOR_DISABLED_BREAKPOINT_COLORING_BACKGROUND;
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_E_BREAKPOINT_MARKERS, //
description = "Background color for memory at an enabled, but ineffective, breakpoint", //
help = @HelpInfo(anchor = "colors"))
private Color breakpointIneffectiveEMarkerColor =
DebuggerResources.DEFAULT_COLOR_INEFFECTIVE_E_BREAKPOINT_MARKERS;
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_E_BREAKPOINT_COLORING_BACKGROUND, //
description = "Whether or not to color background for memory at an enabled, but ineffective, breakpoint", //
help = @HelpInfo(anchor = "colors"))
private boolean breakpointIneffectiveEColoringBackground =
DebuggerResources.DEFAULT_COLOR_INEFFECTIVE_E_BREAKPOINT_COLORING_BACKGROUND;
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_D_BREAKPOINT_MARKERS, //
description = "Background color for memory at an disabled, but ineffective, breakpoint", //
help = @HelpInfo(anchor = "colors"))
private Color breakpointIneffectiveDMarkerColor =
DebuggerResources.DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_MARKERS;
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND, //
description = "Whether or not to color background for memory at an disabled, but ineffective, breakpoint", //
help = @HelpInfo(anchor = "colors"))
private boolean breakpointIneffectiveDColoringBackground =
DebuggerResources.DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
@@ -728,7 +836,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND)
@AutoOptionConsumed(
name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND)
private void setEnabledBreakpointMarkerBackground(boolean breakpointColoringBackground) {
for (BreakpointMarkerSets markers : markersByProgram.values()) {
markers.setEnabledColoringBackground(breakpointColoringBackground);
@@ -742,13 +851,46 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND)
@AutoOptionConsumed(
name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND)
private void setDisabledBreakpointMarkerBackground(boolean breakpointColoringBackground) {
for (BreakpointMarkerSets markers : markersByProgram.values()) {
markers.setDisabledColoringBackground(breakpointColoringBackground);
}
}
@AutoOptionConsumed(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_E_BREAKPOINT_MARKERS)
private void setIneffectiveEBreakpointMarkerColor(Color breakpointMarkerColor) {
for (BreakpointMarkerSets markers : markersByProgram.values()) {
markers.setIneffectiveEnabledMarkerColor(breakpointMarkerColor);
}
}
@AutoOptionConsumed(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_E_BREAKPOINT_COLORING_BACKGROUND)
private void setIneffectiveEBreakpointMarkerBackground(boolean breakpointColoringBackground) {
for (BreakpointMarkerSets markers : markersByProgram.values()) {
markers.setIneffectiveEnabledColoringBackground(breakpointColoringBackground);
}
}
@AutoOptionConsumed(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_D_BREAKPOINT_MARKERS)
private void setIneffectiveDBreakpointMarkerColor(Color breakpointMarkerColor) {
for (BreakpointMarkerSets markers : markersByProgram.values()) {
markers.setIneffectiveDisabledMarkerColor(breakpointMarkerColor);
}
}
@AutoOptionConsumed(
name = DebuggerResources.OPTION_NAME_COLORS_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND)
private void setIneffectiveDBreakpointMarkerBackground(boolean breakpointColoringBackground) {
for (BreakpointMarkerSets markers : markersByProgram.values()) {
markers.setIneffectiveDisabledColoringBackground(breakpointColoringBackground);
}
}
protected TraceRecorder getRecorderFromContext(ActionContext context) {
if (modelService == null) {
return null;
@@ -879,8 +1021,10 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
protected void createActions() {
actionSetSoftwareBreakpoint = new SetBreakpointAction(Set.of(TraceBreakpointKind.SOFTWARE));
actionSetExecuteBreakpoint = new SetBreakpointAction(Set.of(TraceBreakpointKind.EXECUTE));
actionSetSoftwareBreakpoint =
new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE));
actionSetExecuteBreakpoint =
new SetBreakpointAction(Set.of(TraceBreakpointKind.HW_EXECUTE));
actionSetReadWriteBreakpoint =
new SetBreakpointAction(Set.of(TraceBreakpointKind.READ, TraceBreakpointKind.WRITE));
actionSetReadBreakpoint = new SetBreakpointAction(Set.of(TraceBreakpointKind.READ));
@@ -39,6 +39,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
@@ -61,7 +62,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
protected enum LogicalBreakpointTableColumns
implements EnumeratedTableColumn<LogicalBreakpointTableColumns, LogicalBreakpointRow> {
ENABLED("Enabled", Boolean.class, LogicalBreakpointRow::isEnabled, LogicalBreakpointRow::setEnabled, true),
ENABLED("Enabled", Enablement.class, LogicalBreakpointRow::getEnablement, LogicalBreakpointRow::setEnablement, true),
IMAGE("Image", String.class, LogicalBreakpointRow::getImageName, true),
ADDRESS("Address", Address.class, LogicalBreakpointRow::getAddress, true),
LENGTH("Length", Long.class, LogicalBreakpointRow::getLength, true),
@@ -137,7 +138,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
protected enum BreakpointLocationTableColumns
implements EnumeratedTableColumn<BreakpointLocationTableColumns, BreakpointLocationRow> {
// TODO: ENABLED
ENABLED("Enabled", Boolean.class, BreakpointLocationRow::isEnabled, BreakpointLocationRow::setEnabled, true),
NAME("Name", String.class, BreakpointLocationRow::getName, BreakpointLocationRow::setName, true),
ADDRESS("Address", Address.class, BreakpointLocationRow::getAddress, true),
TRACE("Trace", String.class, BreakpointLocationRow::getTraceName, true),
@@ -201,9 +202,9 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
extends RowWrappedEnumeratedColumnTableModel< //
BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> {
public BreakpointLocationTableModel() {
public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) {
super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey,
BreakpointLocationRow::new);
loc -> new BreakpointLocationRow(provider, loc));
}
@Override
@@ -540,7 +541,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
// @AutoServiceConsumed via method
private DebuggerLogicalBreakpointService breakpointService;
// @AutoServiceConsumed via method
private DebuggerModelService modelService;
DebuggerModelService modelService;
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
@@ -562,7 +563,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
GhidraTable breakpointTable;
GhidraTableFilterPanel<LogicalBreakpointRow> breakpointFilterPanel;
BreakpointLocationTableModel locationTableModel = new BreakpointLocationTableModel();
BreakpointLocationTableModel locationTableModel = new BreakpointLocationTableModel(this);
GhidraTable locationTable;
GhidraTableFilterPanel<BreakpointLocationRow> locationFilterPanel;
private final LocationsBySelectedBreakpointsTableFilter filterLocationsBySelectedBreakpoints =
@@ -839,9 +840,12 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
});
TableColumnModel bptColModel = breakpointTable.getColumnModel();
TableColumn enCol = bptColModel.getColumn(LogicalBreakpointTableColumns.ENABLED.ordinal());
enCol.setCellRenderer(new DebuggerBreakpointEnablementTableCellRenderer());
enCol.setPreferredWidth(30);
TableColumn bptEnCol =
bptColModel.getColumn(LogicalBreakpointTableColumns.ENABLED.ordinal());
bptEnCol.setCellRenderer(new DebuggerBreakpointEnablementTableCellRenderer());
bptEnCol.setCellEditor(
new DebuggerBreakpointEnablementTableCellEditor(breakpointFilterPanel));
bptEnCol.setPreferredWidth(30);
TableColumn bptAddrCol =
bptColModel.getColumn(LogicalBreakpointTableColumns.ADDRESS.ordinal());
bptAddrCol.setPreferredWidth(150);
@@ -856,6 +860,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
locsCol.setPreferredWidth(20);
TableColumnModel locColModel = locationTable.getColumnModel();
TableColumn locEnCol =
locColModel.getColumn(BreakpointLocationTableColumns.ENABLED.ordinal());
locEnCol.setCellRenderer(new DebuggerBreakpointLocEnabledTableCellRenderer());
locEnCol.setCellEditor(new DebuggerBreakpointLocEnabledTableCellEditor());
locEnCol.setPreferredWidth(30);
TableColumn locAddrCol =
locColModel.getColumn(BreakpointLocationTableColumns.ADDRESS.ordinal());
locAddrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
@@ -71,8 +71,8 @@ public class DebuggerPlaceBreakpointDialog extends DialogComponentProvider {
JLabel labelKinds = new JLabel("Kinds");
DefaultComboBoxModel<String> kindModel = new DefaultComboBoxModel<>();
// TODO: Let user select whatever combo?
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(SOFTWARE)));
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(EXECUTE)));
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(SW_EXECUTE)));
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(HW_EXECUTE)));
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(READ)));
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(WRITE)));
kindModel.addElement(TraceBreakpointKindSet.encode(Set.of(READ, WRITE)));
@@ -39,14 +39,23 @@ public class LogicalBreakpointRow {
return lb;
}
public Boolean isEnabled() {
Enablement en = provider.isFilterByCurrentTrace() && provider.currentTrace != null
public Enablement getEnablement() {
return provider.isFilterByCurrentTrace() && provider.currentTrace != null
? lb.computeEnablementForTrace(provider.currentTrace)
: lb.computeEnablement();
}
public void setEnablement(Enablement en) {
assert en.consistent && en.effective;
setEnabled(en.enabled);
}
public Boolean isEnabled() {
Enablement en = getEnablement();
if (!en.consistent) {
return null;
}
return en.enabled;
return en.enabled && en.effective;
}
public void setEnabled(boolean enabled) {
@@ -43,8 +43,8 @@ interface LogicalBreakpointInternal extends LogicalBreakpoint {
Set<TraceBreakpointKind> result = TraceBreakpointKindSet.decode(parts[0], false);
if (result.isEmpty()) {
Msg.warn(TraceBreakpointKind.class,
"Decoded empty set of kinds from bookmark. Assuming EXECUTE");
return Set.of(TraceBreakpointKind.EXECUTE);
"Decoded empty set of kinds from bookmark. Assuming SW_EXECUTE");
return Set.of(TraceBreakpointKind.SW_EXECUTE);
}
return result;
}
@@ -109,15 +109,15 @@ interface LogicalBreakpointInternal extends LogicalBreakpoint {
return location;
}
public Enablement computeEnablement() {
public ProgramEnablement computeEnablement() {
if (eBookmark != null) {
return Enablement.ENABLED;
return ProgramEnablement.ENABLED;
}
if (dBookmark != null) {
return Enablement.DISABLED;
return ProgramEnablement.DISABLED;
}
else {
return Enablement.DISABLED;
return ProgramEnablement.NONE;
}
}
@@ -199,11 +199,11 @@ interface LogicalBreakpointInternal extends LogicalBreakpoint {
}
public boolean isEnabled() {
return computeEnablement().enabled;
return computeEnablement() == ProgramEnablement.ENABLED;
}
public boolean isDisabled() {
return computeEnablement().disabled;
return computeEnablement() == ProgramEnablement.DISABLED;
}
public String computeCategory() {
@@ -304,16 +304,18 @@ interface LogicalBreakpointInternal extends LogicalBreakpoint {
return recorder.getMemoryMapper().traceToTarget(address);
}
public Enablement computeEnablement() {
public TraceEnablement computeEnablement() {
TraceEnablement en = TraceEnablement.MISSING;
if (breakpoints.isEmpty()) {
return Enablement.DISABLED;
return TraceEnablement.MISSING;
}
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
if (bpt.obj.isEnabled()) {
return Enablement.ENABLED;
en = en.combine(TraceEnablement.fromBool(bpt.obj.isEnabled()));
if (en == TraceEnablement.MIXED) {
return en;
}
}
return Enablement.DISABLED;
return en;
}
public boolean isEmpty() {
@@ -125,12 +125,12 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal {
if (trace != breaks.getTrace()) {
return Enablement.NONE;
}
return breaks.computeEnablement();
return ProgramEnablement.NONE.combineTrace(breaks.computeEnablement());
}
@Override
public Enablement computeEnablement() {
return breaks.computeEnablement();
return ProgramEnablement.NONE.combineTrace(breaks.computeEnablement());
}
@Override
@@ -302,24 +302,26 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
@Override
public Enablement computeEnablementForTrace(Trace trace) {
TraceBreakpointSet breaks = traceBreaks.get(trace);
ProgramEnablement progEn = progBreak.computeEnablement();
if (breaks == null) {
return Enablement.NONE;
return TraceEnablement.MISSING.combineProgram(progEn);
}
return breaks.computeEnablement().combine(progBreak.computeEnablement());
// NB: Order matters. Trace is primary
return breaks.computeEnablement().combineProgram(progEn);
}
@Override
public Enablement computeEnablement() {
Enablement en = progBreak.computeEnablement();
ProgramEnablement progEn = progBreak.computeEnablement();
TraceEnablement traceEn = TraceEnablement.NONE;
for (TraceBreakpointSet breaks : traceBreaks.values()) {
Enablement tEn = breaks.computeEnablement();
assert tEn.consistent;
en = en.combine(tEn);
if (en.consistent) {
return en;
TraceEnablement tEn = breaks.computeEnablement();
traceEn = traceEn.combine(tEn);
if (traceEn == TraceEnablement.MIXED) {
break;
}
}
return en;
return progEn.combineTrace(traceEn);
}
@Override
@@ -30,8 +30,8 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( //
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class, //
description = "Aggregate breakpoints for programs and live traces" //
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class, //
description = "Aggregate breakpoints for programs and live traces" //
)
public interface DebuggerLogicalBreakpointService {
/**
@@ -44,6 +44,7 @@ public interface DebuggerLogicalBreakpointService {
/**
* Get a map of addresses to collected logical breakpoints for a given program.
*
* <p>
* The program ought to be a program database, not a view of a trace.
*
* @param program the program database
@@ -54,6 +55,7 @@ public interface DebuggerLogicalBreakpointService {
/**
* Get a map of addresses to collected logical breakpoints (at present) for a given trace.
*
* <p>
* The trace must be associated with a live target. The returned map collects live breakpoints
* in the recorded target, using trace breakpoints from the recorder's current snapshot.
*
@@ -65,6 +67,7 @@ public interface DebuggerLogicalBreakpointService {
/**
* Get the collected logical breakpoints at the given program location.
*
* <p>
* The program ought to be a program database, not a view of a trace.
*
* @param program the program database
@@ -76,6 +79,7 @@ public interface DebuggerLogicalBreakpointService {
/**
* Get the collected logical breakpoints (at present) at the given trace location.
*
* <p>
* The trace must be associated with a live target. The returned collection includes live
* breakpoints in the recorded target, using trace breakpoints from the recorders' current
* snapshot.
@@ -89,10 +93,12 @@ public interface DebuggerLogicalBreakpointService {
/**
* Get the collected logical breakpoints (at present) at the given location.
*
* <p>
* The {@code program} field for the location may be either a program database (static image) or
* a view for a trace associated with a live target. If it is the latter, the view's current
* snapshot is ignored, in favor of the associated recorder's current snapshot.
*
* <p>
* If {@code program} is a static image, this is equivalent to using
* {@link #getBreakpointsAt(Program, Address)}. If {@code program} is a trace view, this is
* equivalent to using {@link #getBreakpointsAt(Trace, Address)}.
@@ -105,11 +111,13 @@ public interface DebuggerLogicalBreakpointService {
/**
* Add a listener for logical breakpoint changes.
*
* <p>
* Logical breakpoints may change from time to time for a variety of reasons: A new trace is
* started; a static image is opened; the user adds or removes breakpoints; mappings change;
* etc. The service reacts to these events, reconciles the breakpoints, and invokes callbacks
* for the changes, allowing other UI components and services to update accordingly.
*
* <p>
* The listening component must maintain a strong reference to the listener, otherwise it will
* be removed and garbage collected. Automatic removal is merely a resource-management
* protection; the listening component should politely remove its listener (see
@@ -177,6 +185,7 @@ public interface DebuggerLogicalBreakpointService {
* Create an enabled breakpoint at the given program location and each mapped live trace
* location.
*
* <p>
* The implementation should take care not to create the same breakpoint multiple times. The
* risk of this happening derives from the possibility of one module mapped to multiple targets
* which are all managed by the same debugger, having a single breakpoint container.
@@ -193,9 +202,11 @@ public interface DebuggerLogicalBreakpointService {
/**
* Create an enabled breakpoint at the given trace location only.
*
* <p>
* If the given location is mapped to a static module, this still only creates the breakpoint in
* the given trace. However, a logical breakpoint mark will appear at all mapped locations.
*
* <p>
* Note, the debugger ultimately determines the placement behavior. If it is managing multiple
* targets, it is possible the breakpoint will be effective in another trace. This fact should
* be reflected correctly in the resulting logical markings once all resulting events have been
@@ -213,6 +224,7 @@ public interface DebuggerLogicalBreakpointService {
/**
* Create an enabled breakpoint at the given location.
*
* <p>
* If the given location refers to a static image, this behaves as in
* {@link #placeBreakpointAt(Program, Address, TraceBreakpointKind)}. If it refers to a trace
* view, this behaves as in {@link #placeBreakpointAt(Trace, Address, TraceBreakpointKind)},
@@ -229,6 +241,7 @@ public interface DebuggerLogicalBreakpointService {
/**
* Enable a collection of logical breakpoints on target, if applicable
*
* <p>
* This method is preferable to calling {@link LogicalBreakpoint#enable()} on each logical
* breakpoint, because depending on the debugger, a single breakpoint specification may produce
* several effective breakpoints, perhaps spanning multiple targets. While not terribly
@@ -32,56 +32,260 @@ public interface LogicalBreakpoint {
String BREAKPOINT_ENABLED_BOOKMARK_TYPE = "BreakpointEnabled";
String BREAKPOINT_DISABLED_BOOKMARK_TYPE = "BreakpointDisabled";
public enum ProgramEnablement {
NONE {
@Override
public Enablement combineTrace(TraceEnablement traceEn) {
switch (traceEn) {
case NONE:
return Enablement.NONE;
case MISSING:
return Enablement.NONE;
case ENABLED:
return Enablement.ENABLED;
case MIXED:
return Enablement.DISABLED_ENABLED;
case DISABLED:
return Enablement.DISABLED;
default:
throw new AssertionError();
}
}
},
MISSING {
@Override
public Enablement combineTrace(TraceEnablement traceEn) {
switch (traceEn) {
case NONE:
return Enablement.NONE;
case MISSING:
return Enablement.NONE;
case ENABLED:
case MIXED:
return Enablement.DISABLED_ENABLED;
case DISABLED:
return Enablement.DISABLED;
default:
throw new AssertionError();
}
}
},
ENABLED {
@Override
public Enablement combineTrace(TraceEnablement traceEn) {
switch (traceEn) {
case NONE:
case MISSING:
return Enablement.INEFFECTIVE_ENABLED;
case ENABLED:
return Enablement.ENABLED;
case DISABLED:
case MIXED:
return Enablement.ENABLED_DISABLED;
default:
throw new AssertionError();
}
}
},
DISABLED {
@Override
public Enablement combineTrace(TraceEnablement traceEn) {
switch (traceEn) {
case NONE:
case MISSING:
return Enablement.INEFFECTIVE_DISABLED;
case ENABLED:
case MIXED:
return Enablement.DISABLED_ENABLED;
case DISABLED:
return Enablement.DISABLED;
default:
throw new AssertionError();
}
}
};
public abstract Enablement combineTrace(TraceEnablement traceEn);
}
public enum TraceEnablement {
NONE {
@Override
public TraceEnablement combine(TraceEnablement that) {
return that;
}
@Override
public Enablement combineProgram(ProgramEnablement progEn) {
switch (progEn) {
case NONE:
case MISSING:
return Enablement.NONE;
case ENABLED:
return Enablement.INEFFECTIVE_ENABLED;
case DISABLED:
return Enablement.INEFFECTIVE_DISABLED;
default:
throw new AssertionError();
}
}
},
MISSING {
@Override
public TraceEnablement combine(TraceEnablement that) {
return that;
}
@Override
public Enablement combineProgram(ProgramEnablement progEn) {
switch (progEn) {
case NONE:
case MISSING:
return Enablement.NONE;
case ENABLED:
return Enablement.INEFFECTIVE_ENABLED;
case DISABLED:
return Enablement.INEFFECTIVE_DISABLED;
default:
throw new AssertionError();
}
}
},
ENABLED {
@Override
public TraceEnablement combine(TraceEnablement that) {
switch (that) {
case NONE:
case MISSING:
case ENABLED:
return ENABLED;
case DISABLED:
case MIXED:
return MIXED;
default:
throw new AssertionError();
}
}
@Override
public Enablement combineProgram(ProgramEnablement progEn) {
switch (progEn) {
case NONE:
case MISSING:
case DISABLED:
return Enablement.ENABLED_DISABLED;
case ENABLED:
return Enablement.ENABLED;
default:
throw new AssertionError();
}
}
},
DISABLED {
@Override
public TraceEnablement combine(TraceEnablement that) {
switch (that) {
case NONE:
case MISSING:
case DISABLED:
return DISABLED;
case ENABLED:
case MIXED:
return MIXED;
default:
throw new AssertionError();
}
}
@Override
public Enablement combineProgram(ProgramEnablement progEn) {
switch (progEn) {
case NONE:
case MISSING:
case DISABLED:
return Enablement.DISABLED;
case ENABLED:
return Enablement.DISABLED_ENABLED;
default:
throw new AssertionError();
}
}
},
MIXED {
@Override
public TraceEnablement combine(TraceEnablement that) {
return MIXED;
}
@Override
public Enablement combineProgram(ProgramEnablement progEn) {
return Enablement.ENABLED_DISABLED;
}
};
public static TraceEnablement fromBool(boolean en) {
return en ? ENABLED : DISABLED;
}
public abstract TraceEnablement combine(TraceEnablement that);
public abstract Enablement combineProgram(ProgramEnablement progEn);
}
public enum Enablement {
NONE(false, false, true) {
NONE(false, false, true, false) {
@Override
public Enablement getPrimary() {
return NONE;
}
},
ENABLED(true, false, true) {
ENABLED(true, false, true, true) {
@Override
public Enablement getPrimary() {
return ENABLED;
}
},
DISABLED(false, true, true) {
DISABLED(false, true, true, true) {
@Override
public Enablement getPrimary() {
return DISABLED;
}
},
ENABLED_DISABLED(true, false, false) {
INEFFECTIVE_ENABLED(true, false, true, false) {
@Override
public Enablement getPrimary() {
return ENABLED;
}
},
DISABLED_ENABLED(false, true, false) {
INEFFECTIVE_DISABLED(false, true, true, false) {
@Override
public Enablement getPrimary() {
return DISABLED;
}
},
ENABLED_DISABLED(true, false, false, true) {
@Override
public Enablement getPrimary() {
return ENABLED;
}
},
DISABLED_ENABLED(false, true, false, true) {
@Override
public Enablement getPrimary() {
return DISABLED;
}
};
public final boolean enabled;
public final boolean disabled;
public final boolean consistent;
public final boolean enabled; // indicates any enabled location
public final boolean disabled; // indicates any disabled location
public final boolean consistent; // bookmark and target locations all agree
public final boolean effective; // has a target location, even if disabled
Enablement(boolean enabled, boolean disabled, boolean consistent) {
Enablement(boolean enabled, boolean disabled, boolean consistent, boolean effective) {
this.enabled = enabled;
this.disabled = disabled;
this.consistent = consistent;
}
public Enablement combine(Enablement that) {
if (this == NONE) {
return that;
}
if (that == NONE) {
return this;
}
return fromBools(this.enabled, that.enabled);
this.effective = effective;
}
public Enablement sameAdddress(Enablement that) {
@@ -91,6 +295,9 @@ public interface LogicalBreakpoint {
if (that == NONE) {
return this;
}
if (!this.effective && !that.effective) {
return this.enabled || that.enabled ? INEFFECTIVE_ENABLED : INEFFECTIVE_DISABLED;
}
return fromBools(this.enabled || that.enabled, this.enabled && that.enabled);
}
@@ -76,9 +76,9 @@ public interface TraceRecorder {
case WRITE:
return TraceBreakpointKind.WRITE;
case HW_EXECUTE:
return TraceBreakpointKind.EXECUTE;
return TraceBreakpointKind.HW_EXECUTE;
case SW_EXECUTE:
return TraceBreakpointKind.SOFTWARE;
return TraceBreakpointKind.SW_EXECUTE;
default:
throw new AssertionError();
}
@@ -109,9 +109,9 @@ public interface TraceRecorder {
return TargetBreakpointKind.READ;
case WRITE:
return TargetBreakpointKind.WRITE;
case EXECUTE:
case HW_EXECUTE:
return TargetBreakpointKind.HW_EXECUTE;
case SOFTWARE:
case SW_EXECUTE:
return TargetBreakpointKind.SW_EXECUTE;
default:
throw new AssertionError();
Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

@@ -0,0 +1,34 @@
<?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"
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="SVGRoot">
<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">
<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 8 4 A 4 4 0 0 0 4 8 A 4 4 0 0 0 8 12 A 4 4 0 0 0 12 8 A 4 4 0 0 0 8 4 z M 8 6 A 2 2 0 0 1 10 8 A 2 2 0 0 1 8 10 A 2 2 0 0 1 6 8 A 2 2 0 0 1 8 6 z "
id="path821" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,36 @@
<?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"
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
id="SVGRoot">
<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:#7f7f7f;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path821"
cx="8"
cy="7.9999981"
r="4" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 974 B

@@ -70,7 +70,7 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
Msg.debug(this, "Placing breakpoint");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401c60), 1,
Set.of(TraceBreakpointKind.SOFTWARE));
Set.of(TraceBreakpointKind.SW_EXECUTE));
Msg.debug(this, "Disabling breakpoint");
LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
@@ -79,7 +79,7 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
Msg.debug(this, "Placing another");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401c63), 1,
Set.of(TraceBreakpointKind.SOFTWARE));
Set.of(TraceBreakpointKind.SW_EXECUTE));
Msg.debug(this, "Saving program");
program.save("Placed breakpoints", TaskMonitor.DUMMY);
@@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.breakpoint;
import static ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest.waitForPass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.Set;
@@ -138,7 +139,8 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
waitOn(bc3.placeBreakpoint(mb.addr(0x7fac1234), Set.of(TargetBreakpointKind.SW_EXECUTE)));
TraceBreakpoint bpt = waitForValue(() -> Unique.assertAtMostOne(
trace3.getBreakpointManager().getBreakpointsAt(0, addr(trace3, 0x7fac1234))));
trace3.getBreakpointManager()
.getBreakpointsAt(recorder3.getSnap(), addr(trace3, 0x7fac1234))));
try (UndoableTransaction tid =
UndoableTransaction.start(trace3, "Disable breakpoint", true)) {
bpt.setEnabled(false);
@@ -147,14 +149,15 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint", true)) {
program.getBookmarkManager()
.setBookmark(addr(program, 0x00401234),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SOFTWARE;1", "");
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "");
program.getBookmarkManager()
.setBookmark(addr(program, 0x00402345),
LogicalBreakpoint.BREAKPOINT_DISABLED_BOOKMARK_TYPE, "SOFTWARE;1", "");
LogicalBreakpoint.BREAKPOINT_DISABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "");
}
waitForPass(() -> {
assertEquals(3, breakpointService.getAllBreakpoints().size());
assertFalse(bpt.isEnabled());
});
captureIsolatedProvider(provider, 600, 600);
@@ -115,7 +115,7 @@ public class DebuggerMemviewPluginScreenShots extends GhidraScreenShotGenerator
Set<TraceThread> threads = new HashSet<TraceThread>();
Set<TraceBreakpointKind> kinds = new HashSet<TraceBreakpointKind>();
threads.add(thread1);
kinds.add(TraceBreakpointKind.EXECUTE);
kinds.add(TraceBreakpointKind.HW_EXECUTE);
tb.trace.getBreakpointManager()
.addBreakpoint("bpt1", Range.closed(17L, 25L), tb.range(0x7fac1234, 0x7fc1238),
threads, kinds, true, "break here");
@@ -110,7 +110,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
TaskMonitor.DUMMY, false);
program.getBookmarkManager()
.setBookmark(addr(program, 0x00400123),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SOFTWARE;1", "");
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "");
}
}
@@ -212,7 +212,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Address addr = addr(program, 0x00400123);
hackMarkerBackgroundColors(program);
assertEquals(E_COLOR, getBackgroundColor(program, addr));
waitForPass(() -> assertEquals(E_COLOR, getBackgroundColor(program, addr)));
lb.disableForProgram();
waitForDomainObject(program);
@@ -265,7 +265,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
AbstractClearBreakpointAction.NAME);
protected static final Set<String> SET_ACTIONS =
Set.of("SOFTWARE", "EXECUTE", "READ,WRITE", "READ", "WRITE");
Set.of("SW_EXECUTE", "HW_EXECUTE", "READ,WRITE", "READ", "WRITE");
@Test
public void testProgramNoBreakPopupMenus() throws Exception {
@@ -458,7 +458,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
assertEquals(Enablement.ENABLED, lb.computeEnablement());
// TODO: Different cases for different expected default kinds?
assertEquals(Set.of(TraceBreakpointKind.SOFTWARE), lb.getKinds());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), lb.getKinds());
});
}
@@ -566,25 +566,25 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
@Test
public void testActionSetSoftwareBreakpointProgram() throws Exception {
testActionSetBreakpointProgram(breakpointMarkerPlugin.actionSetSoftwareBreakpoint,
Set.of(TraceBreakpointKind.SOFTWARE));
Set.of(TraceBreakpointKind.SW_EXECUTE));
}
@Test
public void testActionSetSoftwareBreakpointTrace() throws Exception {
testActionSetBreakpointTrace(breakpointMarkerPlugin.actionSetSoftwareBreakpoint,
Set.of(TraceBreakpointKind.SOFTWARE));
Set.of(TraceBreakpointKind.SW_EXECUTE));
}
@Test
public void testActionSetExecuteBreakpointProgram() throws Exception {
testActionSetBreakpointProgram(breakpointMarkerPlugin.actionSetExecuteBreakpoint,
Set.of(TraceBreakpointKind.EXECUTE));
Set.of(TraceBreakpointKind.HW_EXECUTE));
}
@Test
public void testActionSetExecuteBreakpointTrace() throws Exception {
testActionSetBreakpointTrace(breakpointMarkerPlugin.actionSetExecuteBreakpoint,
Set.of(TraceBreakpointKind.EXECUTE));
Set.of(TraceBreakpointKind.HW_EXECUTE));
}
@Test
@@ -694,7 +694,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
performAction(breakpointMarkerPlugin.actionClearBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
// NB. Because it was deleted from the *trace context*
waitForPass(
() -> assertEquals(Enablement.DISABLED_ENABLED, lb.computeEnablementForTrace(trace)));
() -> assertEquals(Enablement.INEFFECTIVE_ENABLED,
lb.computeEnablementForTrace(trace)));
}
}
@@ -32,6 +32,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
@@ -95,7 +96,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
TaskMonitor.DUMMY, false);
program.getBookmarkManager()
.setBookmark(addr(program, 0x00400123),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SOFTWARE;1", "");
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "");
}
}
@@ -129,8 +130,8 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals("55550123", row.getAddress().toString());
assertEquals(trace, row.getDomainObject());
assertEquals("SOFTWARE", row.getKinds());
assertTrue(row.isEnabled());
assertEquals("SW_EXECUTE", row.getKinds());
assertEquals(Enablement.ENABLED, row.getEnablement());
}
@Test
@@ -149,16 +150,16 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertTrue(row.isEnabled());
assertEquals(Enablement.ENABLED, row.getEnablement());
// NB, the row does not take the value immediately, but via async callbacks
row.setEnabled(false);
waitForPass(() -> assertEquals(Boolean.FALSE, row.isEnabled()));
waitForPass(() -> assertEquals(Enablement.DISABLED, row.getEnablement()));
row.setEnabled(true);
waitForPass(() -> assertEquals(Boolean.TRUE, row.isEnabled()));
waitForPass(() -> assertEquals(Enablement.ENABLED, row.getEnablement()));
}
@Test
@@ -172,8 +173,8 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals("00400123", row.getAddress().toString());
assertEquals(program, row.getDomainObject());
assertEquals("SOFTWARE", row.getKinds());
assertTrue(row.isEnabled());
assertEquals("SW_EXECUTE", row.getKinds());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
}
@Test
@@ -185,17 +186,17 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertTrue(row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
row.setEnabled(false); // Synchronous, but on swing thread
waitForDomainObject(program);
assertEquals(Boolean.FALSE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
row.setEnabled(true);
waitForDomainObject(program);
assertEquals(Boolean.TRUE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
}
@Test
@@ -221,7 +222,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
LogicalBreakpoint lb = row.getLogicalBreakpoint();
assertEquals(program, lb.getProgram());
assertEquals(Set.of(trace), lb.getParticipatingTraces());
assertEquals(Boolean.TRUE, row.isEnabled());
assertEquals(Enablement.ENABLED, row.getEnablement());
});
LogicalBreakpointRow row =
@@ -231,24 +232,22 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
lb.disableForProgram();
waitForDomainObject(program);
// TODO: As of now, the table displays "N/A", which is not ideal
// Would be nicer to have icons, but still want click to toggle.
assertNull(row.isEnabled());
assertEquals(Enablement.DISABLED_ENABLED, row.getEnablement());
// NOTE: This acts on the corresponding target, not directly on trace
lb.disableForTrace(trace);
waitForPass(() -> assertEquals(Boolean.FALSE, row.isEnabled()));
waitForPass(() -> assertEquals(Enablement.DISABLED, row.getEnablement()));
lb.enableForProgram();
waitForDomainObject(program);
assertNull(row.isEnabled());
assertEquals(Enablement.ENABLED_DISABLED, row.getEnablement());
// This duplicates the initial case, but without it, I just feel incomplete
lb.enableForTrace(trace);
waitForPass(() -> assertEquals(Boolean.TRUE, row.isEnabled()));
waitForPass(() -> assertEquals(Enablement.ENABLED, row.getEnablement()));
}
// TODO: Test a scenario where one spec manifests two breaks, select both, and perform actions
@@ -256,7 +255,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
// TODO: Test a scenario where one spec manifests the same mapped breakpoint in two traces
@Test
public void testActionEnableSelectedBreakpoints() throws Exception {
public void testActionEnableSelectedBreakpoints() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
@@ -274,12 +273,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertEquals(Boolean.FALSE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled());
performAction(breakpointsProvider.actionEnableSelectedBreakpointsAction);
assertEquals(Boolean.TRUE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled());
breakpointsProvider.breakpointTable.clearSelection();
@@ -293,7 +292,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
assertTrue(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled());
// Bookmark part should actually be synchronous.
row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
waitOn(row.getLogicalBreakpoint().delete());
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionEnableSelectedBreakpointsAction.isEnabled());
@@ -317,12 +316,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
row.setEnabled(false);
waitForSwing();
assertEquals(Boolean.FALSE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled());
performAction(breakpointsProvider.actionEnableAllBreakpointsAction);
assertEquals(Boolean.TRUE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionEnableAllBreakpointsAction.isEnabled());
// Bookmark part should actually be synchronous.
@@ -333,7 +332,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
}
@Test
public void testActionDisableSelectedBreakpoints() throws Exception {
public void testActionDisableSelectedBreakpoints() throws Throwable {
createProgram();
programManager.openProgram(program);
waitForSwing();
@@ -350,12 +349,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
breakpointsProvider.breakpointFilterPanel.setSelectedItem(row);
waitForSwing();
assertEquals(Boolean.TRUE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled());
performAction(breakpointsProvider.actionDisableSelectedBreakpointsAction);
assertEquals(Boolean.FALSE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled());
breakpointsProvider.breakpointTable.clearSelection();
@@ -369,7 +368,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
assertTrue(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled());
// Bookmark part should actually be synchronous.
row.getLogicalBreakpoint().delete().get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
waitOn(row.getLogicalBreakpoint().delete());
waitForDomainObject(program);
assertFalse(breakpointsProvider.actionDisableSelectedBreakpointsAction.isEnabled());
@@ -389,12 +388,12 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled());
LogicalBreakpointRow row =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
assertEquals(Boolean.TRUE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_ENABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled());
performAction(breakpointsProvider.actionDisableAllBreakpointsAction);
assertEquals(Boolean.FALSE, row.isEnabled());
assertEquals(Enablement.INEFFECTIVE_DISABLED, row.getEnablement());
assertTrue(breakpointsProvider.actionDisableAllBreakpointsAction.isEnabled());
// Bookmark part should actually be synchronous.
@@ -377,7 +377,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
LogicalBreakpoint enLb = Unique
.assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset)));
assertNull(enLb.getProgramLocation());
assertEquals(Set.of(TraceBreakpointKind.SOFTWARE), enLb.getKinds());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds());
TraceBreakpoint bpt = Unique.assertOne(trace.getBreakpointManager().getAllBreakpoints());
assertEquals(Set.of(trace), enLb.getMappedTraces());
@@ -394,7 +394,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
.assertOne(breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)));
assertNull(enLb.getProgramBookmark());
assertEquals(Enablement.DISABLED_ENABLED, enLb.computeEnablementForProgram(program));
assertEquals(Set.of(TraceBreakpointKind.SOFTWARE), enLb.getKinds());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds());
TraceBreakpoint bpt = Unique.assertOne(trace.getBreakpointManager().getAllBreakpoints());
assertEquals(Set.of(trace), enLb.getMappedTraces());
@@ -414,7 +414,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
assertEquals(enBm, enLb.getProgramBookmark());
assertTrue(enLb.getMappedTraces().isEmpty());
assertEquals(Enablement.ENABLED, enLb.computeEnablementForProgram(program));
assertEquals(Set.of(TraceBreakpointKind.SOFTWARE), enLb.getKinds());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds());
LogicalBreakpoint disLb = Unique
.assertOne(breakpointService.getBreakpointsAt(program, addr(program, 0x00400321)));
@@ -423,7 +423,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
assertEquals(disBm, disLb.getProgramBookmark());
assertTrue(disLb.getMappedTraces().isEmpty());
assertEquals(Enablement.DISABLED, disLb.computeEnablementForProgram(program));
assertEquals(Set.of(TraceBreakpointKind.SOFTWARE), disLb.getKinds());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), disLb.getKinds());
}
protected void assertLogicalBreakpointsForMappedBookmarks(Trace trace) {
@@ -22,8 +22,10 @@ import org.apache.commons.collections4.set.AbstractSetDecorator;
/**
* The kind of breakpoint
*
* <p>
* This identifies the sort of access that would trap execution
*
* <p>
* TODO: This is identical to {@code TargetBreakpointKind} (not in the classpath here). Is there a
* common place we could factor both? Should we? CAUTION: Encoding in a trace database depends on
* this enum's {@code bits} field, so we must take care not to introduce a dependency that would
@@ -32,8 +34,8 @@ import org.apache.commons.collections4.set.AbstractSetDecorator;
public enum TraceBreakpointKind {
READ(1 << 0),
WRITE(1 << 1),
EXECUTE(1 << 2),
SOFTWARE(1 << 3);
HW_EXECUTE(1 << 2),
SW_EXECUTE(1 << 3);
public static class TraceBreakpointKindSet extends AbstractSetDecorator<TraceBreakpointKind> {
public static TraceBreakpointKindSet of(TraceBreakpointKind... kinds) {