diff --git a/Ghidra/Debug/Debugger/build.gradle b/Ghidra/Debug/Debugger/build.gradle
index 2f3a865dc5..54783c069f 100644
--- a/Ghidra/Debug/Debugger/build.gradle
+++ b/Ghidra/Debug/Debugger/build.gradle
@@ -28,9 +28,11 @@ dependencies {
api project(':Framework-TraceModeling')
api project(':Base')
api project(':ByteViewer')
+ api project(':Decompiler')
api project(':ProposedUtils')
helpPath project(path: ':Base', configuration: 'helpPath')
+ helpPath project(path: ':Decompiler', configuration: 'helpPath')
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
testImplementation project(path: ':Base', configuration: 'testArtifacts')
diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest
index 723e3acccf..e2ab6a3cc9 100644
--- a/Ghidra/Debug/Debugger/certification.manifest
+++ b/Ghidra/Debug/Debugger/certification.manifest
@@ -23,6 +23,7 @@ src/main/help/help/topics/Debugger/Troubleshooting.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBots/DebuggerBots.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png||GHIDRA||||END|
+src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerPlaceBreakpointDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/breakpoint-clear.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/breakpoint-disable.png||GHIDRA||||END|
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html
index d79889ac73..eca15dcc08 100644
--- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html
+++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html
@@ -32,6 +32,29 @@
the current target. NOTE: Depending on the connected debugger, locations resulting from
a common specification may not be independently manipulated.
+ In the Decompiler
+
+
+
+
+  |
+
+
+
+
+ When active in the Debugger, the Decompiler will display breakpoints in
+ its margin. Keep in mind, the relationship between machine instructions and the decompiled code
+ is not always so simple. One line may correspond to a single address, many addresses, or no
+ addresses at all. If there is a breakpoint at any address for a given line, then that line will
+ have a marker indicating the breakpoint's state. If there are multiple, then that state may be
+ mixed. In most cases, Setting a breakpoint in the decompiler will
+ suggest a Software Execution breakpoint at the minimum address of the current line. If the
+ current token is a static variable, then it will suggest a Read/Write breakpoint at the
+ variable's address. (Note that Read/Write breakpoints are not indicated in the Decompiler's
+ margin.) The other actions affect all breakpoints on the current line.
+
Actions
The following actions are added to all disassembly listings by the breakpoint marker plugin.
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png
new file mode 100644
index 0000000000..b0a0d584d3
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java
new file mode 100644
index 0000000000..2921048c83
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java
@@ -0,0 +1,140 @@
+/* ###
+ * 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.*;
+import java.awt.event.*;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import javax.swing.JPanel;
+
+import docking.widgets.fieldpanel.LayoutModel;
+import docking.widgets.fieldpanel.listener.IndexMapper;
+import docking.widgets.fieldpanel.listener.LayoutModelListener;
+import ghidra.app.decompiler.ClangLine;
+import ghidra.app.decompiler.component.margin.DecompilerMarginProvider;
+import ghidra.app.decompiler.component.margin.LayoutPixelIndexMap;
+import ghidra.app.services.LogicalBreakpoint;
+import ghidra.app.services.LogicalBreakpoint.State;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.ProgramLocation;
+import ghidra.trace.model.breakpoint.TraceBreakpointKind;
+
+public class BreakpointsDecompilerMarginProvider extends JPanel
+ implements DecompilerMarginProvider, LayoutModelListener {
+
+ private Program program;
+ private LayoutModel model;
+ private LayoutPixelIndexMap pixmap;
+
+ private final DebuggerBreakpointMarkerPlugin plugin;
+
+ public BreakpointsDecompilerMarginProvider(DebuggerBreakpointMarkerPlugin plugin) {
+ this.plugin = plugin;
+ setPreferredSize(new Dimension(16, 0));
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ doToggleBreakpoint(e);
+ }
+ });
+ }
+
+ @Override
+ public void setProgram(Program program, LayoutModel model, LayoutPixelIndexMap pixmap) {
+ this.program = program;
+ setLayoutManager(model);
+ this.pixmap = pixmap;
+ repaint();
+ }
+
+ private void setLayoutManager(LayoutModel model) {
+ if (this.model == model) {
+ return;
+ }
+ if (this.model != null) {
+ this.model.removeLayoutModelListener(this);
+ }
+ this.model = model;
+ if (this.model != null) {
+ this.model.addLayoutModelListener(this);
+ }
+ }
+
+ @Override
+ public Component getComponent() {
+ return this;
+ }
+
+ @Override
+ public void modelSizeChanged(IndexMapper indexMapper) {
+ repaint();
+ }
+
+ @Override
+ public void dataChanged(BigInteger start, BigInteger end) {
+ repaint();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ if (plugin.breakpointService == null) {
+ return;
+ }
+ Rectangle visible = getVisibleRect();
+ BigInteger startIdx = pixmap.getIndex(visible.y);
+ BigInteger endIdx = pixmap.getIndex(visible.y + visible.height);
+
+ List lines = plugin.decompilerMarginService.getDecompilerPanel().getLines();
+ for (BigInteger index = startIdx; index.compareTo(endIdx) <= 0; index =
+ index.add(BigInteger.ONE)) {
+ int i = index.intValue();
+ if (i >= lines.size()) {
+ continue;
+ }
+ ClangLine line = lines.get(i);
+ List locs =
+ DebuggerBreakpointMarkerPlugin.getLocationsFromLine(program, line);
+ State state = plugin.computeState(locs);
+ if (state.icon != null) {
+ state.icon.paintIcon(this, g, 0, pixmap.getPixel(index));
+ }
+ }
+ }
+
+ private void doToggleBreakpoint(MouseEvent e) {
+ if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
+ int i = pixmap.getIndex(e.getY()).intValue();
+ List lines = plugin.decompilerMarginService.getDecompilerPanel().getLines();
+ List locs =
+ DebuggerBreakpointMarkerPlugin.nearestLocationsToLine(program, i, lines);
+ if (locs == null || locs.isEmpty()) {
+ return;
+ }
+ Set col = plugin.collectBreakpoints(locs);
+ plugin.breakpointService.toggleBreakpointsAt(col, locs.get(0), () -> {
+ plugin.placeBreakpointDialog.prompt(plugin.getTool(), plugin.breakpointService,
+ "Set breakpoint", locs.get(0), 1, Set.of(TraceBreakpointKind.SW_EXECUTE), "");
+ // Not great, but I'm not sticking around for the dialog
+ return CompletableFuture.completedFuture(Set.of());
+ });
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java
index c3d1c31f1b..7e309a8998 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java
@@ -28,6 +28,8 @@ import docking.Tool;
import docking.action.*;
import docking.actions.PopupActionProvider;
import ghidra.app.context.ProgramLocationActionContext;
+import ghidra.app.decompiler.*;
+import ghidra.app.decompiler.component.margin.LineNumberDecompilerMarginProvider;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.events.ProgramOpenedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
@@ -36,6 +38,7 @@ import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
+import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.app.util.viewer.listingpanel.MarkerClickedListener;
@@ -49,6 +52,8 @@ import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.*;
+import ghidra.program.model.pcode.PcodeOp;
+import ghidra.program.model.pcode.Varnode;
import ghidra.program.util.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
@@ -76,44 +81,22 @@ import ghidra.util.Msg;
public class DebuggerBreakpointMarkerPlugin extends Plugin
implements PopupActionProvider {
- protected static Address computeAddressFromContext(ActionContext context) {
+ protected static ProgramLocation getSingleLocationFromContext(ActionContext context) {
if (context == null) {
return null;
}
- if (context instanceof ProgramLocationActionContext) {
- ProgramLocationActionContext ctx = (ProgramLocationActionContext) context;
- if (ctx.hasSelection()) {
- ProgramSelection sel = ctx.getSelection();
- AddressRange range = sel.getRangeContaining(ctx.getAddress());
- if (range != null) {
- return range.getMinAddress();
+ if (context instanceof DecompilerActionContext ctx) {
+ // Use the token here, not the line
+ if (!(ctx.getSourceComponent() instanceof LineNumberDecompilerMarginProvider) &&
+ ctx.getTokenAtCursor() instanceof ClangVariableToken tok) {
+ Varnode varnode = tok.getVarnode();
+ Address address = varnode == null ? null : varnode.getAddress();
+ if (address != null && address.isMemoryAddress()) {
+ return new ProgramLocation(ctx.getProgram(), address);
}
}
- return ctx.getAddress();
}
- Object obj = context.getContextObject();
- if (obj instanceof MarkerLocation) {
- MarkerLocation ml = (MarkerLocation) obj;
- return ml.getAddr();
- }
- return null;
- }
-
- /**
- * Attempt to derive a location from the given context
- *
- *
- * Currently, this supports {@link ProgramLocationActionContext} and {@link MarkerLocation}.
- *
- * @param context a possible location context
- * @return the program location, or {@code null}
- */
- protected static ProgramLocation getLocationFromContext(ActionContext context) {
- if (context == null) {
- return null;
- }
- if (context instanceof ProgramLocationActionContext) {
- ProgramLocationActionContext ctx = (ProgramLocationActionContext) context;
+ if (context instanceof ProgramLocationActionContext ctx) {
if (ctx.hasSelection()) {
ProgramSelection sel = ctx.getSelection();
AddressRange range = sel.getRangeContaining(ctx.getAddress());
@@ -124,13 +107,100 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
return ctx.getLocation();
}
Object obj = context.getContextObject();
- if (obj instanceof MarkerLocation) {
- MarkerLocation ml = (MarkerLocation) obj;
+ if (obj instanceof MarkerLocation ml) {
return new ProgramLocation(ml.getProgram(), ml.getAddr());
}
return null;
}
+ protected static List
getAddressesFromLine(ClangLine line) {
+ Set result = new TreeSet<>();
+ for (int i = 0; i < line.getNumTokens(); i++) {
+ ClangToken tok = line.getToken(i);
+ if (tok instanceof ClangLabelToken) {
+ continue;
+ }
+ if (tok instanceof ClangCommentToken) {
+ /*
+ * Comment tokens should never have an address anyway, but sometimes the decompiler
+ * assigns the entry address to a warning comment that precedes the function header.
+ * This will filter that oddity.
+ */
+ continue;
+ }
+ // Don't let line-wrapped calls display one breakpoint on all lines
+ // NOTE: The call itself will be represented by the ClangFuncNameToken
+ if (tok instanceof ClangVariableToken varTok &&
+ varTok.getPcodeOp() != null && varTok.getPcodeOp().getOpcode() == PcodeOp.CALL) {
+ continue;
+ }
+ if (tok instanceof ClangOpToken opTok &&
+ opTok.getPcodeOp() != null && opTok.getPcodeOp().getOpcode() == PcodeOp.CALL) {
+ continue;
+ }
+ // NOTE: I've seen no case where max != min
+ Address min = tok.getMinAddress();
+ if (min == null) {
+ continue;
+ }
+ result.add(min);
+ }
+ return List.copyOf(result);
+ }
+
+ protected static List getLocationsFromLine(Program program, ClangLine line) {
+ List result = new ArrayList<>();
+ for (Address addr : getAddressesFromLine(line)) {
+ result.add(new ProgramLocation(program, addr));
+ }
+ return result;
+ }
+
+ /**
+ * Find the nearest line, only looking forward, having an address and get its addresses wrapped
+ * in program locations
+ *
+ * @param program the current program, for generating program locations
+ * @param index the index of the first line to consider, the current/context line
+ * @param lines the complete list of decompiled source lines of the current function
+ * @return the locations, or null if no such line is found
+ */
+ protected static List nearestLocationsToLine(Program program, int index,
+ List lines) {
+ if (index < 0) {
+ return null;
+ }
+ for (int n = index; n < lines.size(); n++) {
+ ClangLine clangLine = lines.get(n);
+ List locs = getLocationsFromLine(program, clangLine);
+ if (locs != null && !locs.isEmpty()) {
+ return locs;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Attempt to derive one or more locations from the given context
+ *
+ * @param context a possible location context
+ * @return the program location, or {@code null}
+ */
+ protected static List getLocationsFromContext(ActionContext context) {
+ if (context == null) {
+ return null;
+ }
+ if (context instanceof DecompilerActionContext ctx) {
+ int lineNumber = ctx.getLineNumber();
+ // Return even if null, to prevent token from being used
+ // Using the token might surprise the user, esp., if it's not on screen
+ return nearestLocationsToLine(ctx.getProgram(), lineNumber - 1,
+ ctx.getDecompilerPanel().getLines());
+ }
+ ProgramLocation loc = getSingleLocationFromContext(context);
+ return loc == null ? null : List.of(loc);
+ }
+
protected static long computeLengthFromContext(ActionContext context) {
if (context == null) {
return 1;
@@ -153,15 +223,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
protected static boolean contextHasLocation(ActionContext context) {
- return getLocationFromContext(context) != null;
+ List locs = getLocationsFromContext(context);
+ return locs != null && !locs.isEmpty();
}
protected static Trace getTraceFromContext(ActionContext context) {
- ProgramLocation loc = getLocationFromContext(context);
- if (loc == null) {
+ List locs = getLocationsFromContext(context);
+ if (locs == null || locs.isEmpty()) {
return null;
}
- Program progOrView = loc.getProgram();
+ Program progOrView = locs.get(0).getProgram();
if (progOrView instanceof TraceProgramView) {
TraceProgramView view = (TraceProgramView) progOrView;
return view.getTrace();
@@ -190,7 +261,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
long length = computeLengthFromContext(ctx);
if (length == 1) {
- ProgramLocation loc = getLocationFromContext(ctx);
+ ProgramLocation loc = getSingleLocationFromContext(ctx);
Listing listing = loc.getProgram().getListing();
CodeUnit cu = listing.getCodeUnitContaining(loc.getAddress());
if (cu instanceof Instruction) {
@@ -218,41 +289,23 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
protected Color colorForState(State state) {
- if (state.isEnabled()) {
- if (state.isEffective()) {
- return breakpointEnabledMarkerColor;
- }
- else {
- return breakpointIneffEnMarkerColor;
- }
- }
- else {
- if (state.isEffective()) {
- return breakpointDisabledMarkerColor;
- }
- else {
- return breakpointIneffDisMarkerColor;
- }
- }
+ return state.isEnabled()
+ ? state.isEffective()
+ ? breakpointEnabledMarkerColor
+ : breakpointIneffEnMarkerColor
+ : state.isEffective()
+ ? breakpointDisabledMarkerColor
+ : breakpointIneffDisMarkerColor;
}
protected boolean stateColorsBackground(State state) {
- if (state.isEnabled()) {
- if (state.isEffective()) {
- return breakpointEnabledColoringBackground;
- }
- else {
- return breakpointIneffEnColoringBackground;
- }
- }
- else {
- if (state.isEffective()) {
- return breakpointDisabledColoringBackground;
- }
- else {
- return breakpointIneffDisColoringBackground;
- }
- }
+ return state.isEnabled()
+ ? state.isEffective()
+ ? breakpointEnabledColoringBackground
+ : breakpointIneffEnColoringBackground
+ : state.isEffective()
+ ? breakpointDisabledColoringBackground
+ : breakpointIneffDisColoringBackground;
}
/**
@@ -432,17 +485,18 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
return breakpoint.computeState();
}
- /**
- * It seems the purpose of this was to omit the program mode from the dynamic listing. I don't
- * think we need that anymore, so I've just delegated to exactly the same as the breakpoint
- * service, which will include the program mode, if applicable. TODO: Remove this and just call
- * the service's version directly?
- *
- * @param loc
- * @return
- */
- protected State computeState(ProgramLocation loc) {
- return breakpointService.computeState(loc);
+ protected Set collectBreakpoints(Collection locs) {
+ return locs.stream()
+ .flatMap(l -> breakpointService.getBreakpointsAt(l).stream())
+ .collect(Collectors.toSet());
+ }
+
+ protected State computeState(List locs) {
+ if (locs.isEmpty()) {
+ return State.NONE;
+ }
+ Set col = collectBreakpoints(locs);
+ return breakpointService.computeState(col, locs.get(0));
}
protected class ToggleBreakpointAction extends AbstractToggleBreakpointAction {
@@ -489,7 +543,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return;
}
- ProgramLocation location = getLocationFromContext(context);
+ ProgramLocation location = getSingleLocationFromContext(context);
long length = computeDefaultLength(context, kinds);
placeBreakpointDialog.prompt(tool, breakpointService, NAME, location, length, kinds,
"");
@@ -500,7 +554,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return false;
}
- ProgramLocation loc = getLocationFromContext(context);
+ ProgramLocation loc = getSingleLocationFromContext(context);
if (!(loc.getProgram() instanceof TraceProgramView)) {
return true;
}
@@ -530,8 +584,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return;
}
- ProgramLocation location = getLocationFromContext(context);
- Set col = breakpointService.getBreakpointsAt(location);
+ List locs = getLocationsFromContext(context);
+ Set col = collectBreakpoints(locs);
Trace trace = getTraceFromContext(context);
String status = breakpointService.generateStatusEnable(col, trace);
if (status != null) {
@@ -548,8 +602,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return false;
}
- ProgramLocation location = getLocationFromContext(context);
- State state = computeState(location);
+ List locs = getLocationsFromContext(context);
+ State state = computeState(locs);
if (state == State.ENABLED || state == State.NONE) {
return false;
}
@@ -572,8 +626,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return;
}
- ProgramLocation location = getLocationFromContext(context);
- Set col = breakpointService.getBreakpointsAt(location);
+ List locs = getLocationsFromContext(context);
+ Set col = collectBreakpoints(locs);
breakpointService.disableAll(col, getTraceFromContext(context)).exceptionally(ex -> {
breakpointError(NAME, "Could not disable breakpoint", ex);
return null;
@@ -585,8 +639,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return false;
}
- ProgramLocation location = getLocationFromContext(context);
- State state = computeState(location);
+ List locs = getLocationsFromContext(context);
+ State state = computeState(locs);
if (state == State.DISABLED || state == State.NONE) {
return false;
}
@@ -611,8 +665,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return;
}
- ProgramLocation location = getLocationFromContext(context);
- Set col = breakpointService.getBreakpointsAt(location);
+ List locs = getLocationsFromContext(context);
+ Set col = collectBreakpoints(locs);
breakpointService.deleteAll(col, getTraceFromContext(context)).exceptionally(ex -> {
breakpointError(NAME, "Could not delete breakpoint", ex);
return null;
@@ -624,8 +678,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (!contextCanManipulateBreakpoints(context)) {
return false;
}
- ProgramLocation location = getLocationFromContext(context);
- State state = computeState(location);
+ List locs = getLocationsFromContext(context);
+ State state = computeState(locs);
if (state == State.NONE) {
return false;
}
@@ -636,7 +690,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
// @AutoServiceConsumed via method
private MarkerService markerService;
// @AutoServiceConsumed via method
- private DebuggerLogicalBreakpointService breakpointService;
+ DebuggerLogicalBreakpointService breakpointService;
@AutoServiceConsumed
private DebuggerModelService modelService;
@AutoServiceConsumed
@@ -645,6 +699,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
+ // @AutoServiceConsumed via method
+ DecompilerMarginService decompilerMarginService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@@ -730,8 +786,11 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
DebuggerPlaceBreakpointDialog placeBreakpointDialog = new DebuggerPlaceBreakpointDialog();
+ BreakpointsDecompilerMarginProvider decompilerMarginProvider;
+
public DebuggerBreakpointMarkerPlugin(PluginTool tool) {
super(tool);
+ this.decompilerMarginProvider = new BreakpointsDecompilerMarginProvider(this);
this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
this.autoOptionsWiring = AutoOptions.wireOptions(this);
@@ -824,12 +883,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (mappingService == null || modelService == null) {
return Set.of();
}
- ProgramLocation loc = getLocationFromContext(context);
+ ProgramLocation loc = getSingleLocationFromContext(context);
if (loc == null || loc.getProgram() instanceof TraceProgramView) {
return Set.of();
}
+ Set mappedLocs = mappingService.getOpenMappedLocations(loc);
+ if (mappedLocs == null || mappedLocs.isEmpty()) {
+ return Set.of();
+ }
Set result = new HashSet<>();
- for (TraceLocation tloc : mappingService.getOpenMappedLocations(loc)) {
+ for (TraceLocation tloc : mappedLocs) {
TraceRecorder rec = modelService.getRecorder(tloc.getTrace());
if (rec != null) {
result.add(rec);
@@ -870,15 +933,17 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
if (breakpointService == null) {
return;
}
- ProgramLocation loc = getLocationFromContext(context);
- if (loc == null) {
+ List locs = getLocationsFromContext(context);
+ if (locs == null || locs.isEmpty()) {
return;
}
- String status = breakpointService.generateStatusToggleAt(loc);
+ Set col = collectBreakpoints(locs);
+ ProgramLocation loc = locs.get(0);
+ String status = breakpointService.generateStatusToggleAt(col, loc);
if (status != null) {
tool.setStatusInfo(status, true);
}
- breakpointService.toggleBreakpointsAt(loc, () -> {
+ breakpointService.toggleBreakpointsAt(col, loc, () -> {
Set supported = getSupportedKindsFromContext(context);
if (supported.isEmpty()) {
breakpointError(title, "It seems this target does not support breakpoints.");
@@ -886,7 +951,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
Set kinds = computeDefaultKinds(context, supported);
long length = computeDefaultLength(context, kinds);
- placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds, "");
+ placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds,
+ "");
// Not great, but I'm not sticking around for the dialog
return CompletableFuture.completedFuture(Set.of());
}).exceptionally(ex -> {
@@ -985,6 +1051,17 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
+ @AutoServiceConsumed
+ private void setDecompilerMarginService(DecompilerMarginService decompilerMarginService) {
+ if (this.decompilerMarginService != null) {
+ this.decompilerMarginService.removeMarginProvider(decompilerMarginProvider);
+ }
+ this.decompilerMarginService = decompilerMarginService;
+ if (this.decompilerMarginService != null) {
+ this.decompilerMarginService.addMarginProvider(decompilerMarginProvider);
+ }
+ }
+
protected void createActions() {
actionSetSoftwareBreakpoint =
new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE));
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java
index db59ba4989..33f193f5fb 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java
@@ -583,6 +583,10 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
public TraceLocation toDynamicLocation(ProgramLocation loc) {
+ if (mappingService == null) {
+ // Must be shutting down
+ return null;
+ }
return mappingService.getOpenMappedLocation(trace, loc, recorder.getSnap());
}
}
@@ -1216,8 +1220,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
@Override
- public String generateStatusToggleAt(ProgramLocation loc) {
- Set bs = getBreakpointsAt(loc);
+ public String generateStatusToggleAt(Set bs, ProgramLocation loc) {
if (bs == null || bs.isEmpty()) {
return null;
}
@@ -1242,9 +1245,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
@Override
- public CompletableFuture> toggleBreakpointsAt(ProgramLocation loc,
- Supplier>> placer) {
- Set bs = getBreakpointsAt(loc);
+ public CompletableFuture> toggleBreakpointsAt(Set bs,
+ ProgramLocation loc, Supplier>> placer) {
if (bs == null || bs.isEmpty()) {
return placer.get();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
index dfd8947e6b..836162aef5 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
@@ -370,20 +370,47 @@ public interface DebuggerLogicalBreakpointService {
*/
CompletableFuture deleteLocs(Collection col);
+ /**
+ * Generate an informational message when toggling the breakpoints
+ *
+ *
+ * This works in the same manner as {@link #generateStatusEnable(Collection, Trace)}, except it
+ * is for toggling breakpoints. If the breakpoint set is empty, this should return null, since
+ * the usual behavior in that case is to prompt to place a new breakpoint.
+ *
+ * @see #generateStatusEnable(Collection, Trace))
+ * @param loc a representative location
+ * @return the status message, or null
+ */
+ String generateStatusToggleAt(Set bs, ProgramLocation loc);
+
/**
* Generate an informational message when toggling the breakpoints at the given location
*
*
- * This works in the same manner as {@link #generateStatusEnable(Collection)}, except it is for
- * toggling breakpoints at a given location. If there are no breakpoints at the location, this
- * should return null, since the usual behavior in that case is to prompt to place a new
+ * This works in the same manner as {@link #generateStatusEnable(Collection, Trace))}, except it
+ * is for toggling breakpoints at a given location. If there are no breakpoints at the location,
+ * this should return null, since the usual behavior in that case is to prompt to place a new
* breakpoint.
*
* @see #generateStatusEnable(Collection)
* @param loc the location
* @return the status message, or null
*/
- String generateStatusToggleAt(ProgramLocation loc);
+ default String generateStatusToggleAt(ProgramLocation loc) {
+ return generateStatusToggleAt(getBreakpointsAt(loc), loc);
+ }
+
+ /**
+ * Toggle the breakpoints at the given location
+ *
+ * @param bs the set of breakpoints to toggle
+ * @param location the location
+ * @param placer if the breakpoint set is empty, a routine for placing a breakpoint
+ * @return a future which completes when the command has been processed
+ */
+ CompletableFuture> toggleBreakpointsAt(Set bs,
+ ProgramLocation location, Supplier>> placer);
/**
* Toggle the breakpoints at the given location
@@ -392,6 +419,8 @@ public interface DebuggerLogicalBreakpointService {
* @param placer if there are no breakpoints, a routine for placing a breakpoint
* @return a future which completes when the command has been processed
*/
- CompletableFuture> toggleBreakpointsAt(ProgramLocation location,
- Supplier>> placer);
+ default CompletableFuture> toggleBreakpointsAt(ProgramLocation location,
+ Supplier>> placer) {
+ return toggleBreakpointsAt(getBreakpointsAt(location), location, placer);
+ }
}
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java
index 5e02f231e0..fb0e04e91f 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java
@@ -30,6 +30,8 @@ import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpoint
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
+import ghidra.app.plugin.core.decompile.DecompilePlugin;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.State;
@@ -42,6 +44,7 @@ import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.util.Msg;
+import ghidra.util.Swing;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
@@ -130,6 +133,44 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
captureProviderWithScreenShot(listing);
}
+ @Test
+ public void testCaptureDebuggerDecompilerBreakpointMargin() throws Throwable {
+ mb.createTestModel();
+ modelService.addModel(mb.testModel);
+ mb.createTestProcessesAndThreads();
+ TestDebuggerTargetTraceMapper mapper = new TestDebuggerTargetTraceMapper(mb.testProcess1);
+ TraceRecorder recorder =
+ modelService.recordTarget(mb.testProcess1, mapper, ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+
+ traceManager.openTrace(trace);
+ traceManager.activateTrace(trace);
+
+ tool.getProject()
+ .getProjectData()
+ .getRootFolder()
+ .createFile("WinHelloCPP", program, TaskMonitor.DUMMY);
+
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add Mapping")) {
+ mappingService.addIdentityMapping(trace, program, Range.atLeast(0L), true);
+ }
+ waitForValue(() -> mappingService.getOpenMappedLocation(
+ new DefaultTraceLocation(trace, null, Range.singleton(0L), mb.addr(0x00401070))));
+
+ Msg.debug(this, "Placing breakpoint");
+ breakpointService.placeBreakpointAt(program, addr(program, 0x00401070), 1,
+ Set.of(TraceBreakpointKind.SW_EXECUTE), "");
+
+ addPlugin(tool, DecompilePlugin.class);
+
+ DecompilerProvider decompilerProvider = waitForComponentProvider(DecompilerProvider.class);
+ Swing.runNow(() -> tool.showComponentProvider(decompilerProvider, true));
+ goTo(tool, program, addr(program, 0x00401070));
+ waitForCondition(() -> decompilerProvider.getDecompilerPanel().getLines().size() > 4);
+
+ captureIsolatedProvider(decompilerProvider, 500, 700);
+ }
+
@Test
public void testCaptureDebuggerPlaceBreakpointDialog() throws Throwable {
runSwing(
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java
index fbdbbda177..7e11020f3d 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java
@@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.breakpoint;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import java.awt.*;
import java.awt.event.MouseEvent;
@@ -41,11 +40,15 @@ import docking.widgets.fieldpanel.FieldPanel;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.context.ProgramLocationActionContext;
+import ghidra.app.decompiler.DecompileResults;
+import ghidra.app.decompiler.component.DecompileData;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
+import ghidra.app.plugin.core.decompile.DecompilePlugin;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
@@ -53,11 +56,12 @@ import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.framework.store.LockException;
import ghidra.program.disassemble.Disassembler;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.address.*;
import ghidra.program.model.data.ByteDataType;
+import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryConflictException;
+import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
@@ -85,6 +89,10 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
protected DebuggerStaticMappingService mappingService;
protected MarkerService markerService;
+ protected Function function;
+ protected Address entry;
+ protected DecompilerProvider decompilerProvider;
+
@Before
public void setUpBreakpointMarkerPluginTest() throws Exception {
breakpointMarkerPlugin = addPlugin(tool, DebuggerBreakpointMarkerPlugin.class);
@@ -96,6 +104,28 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
markerService = tool.getService(MarkerService.class);
}
+ protected void prepareDecompiler() throws Throwable {
+ addPlugin(tool, DecompilePlugin.class);
+ decompilerProvider = waitForComponentProvider(DecompilerProvider.class);
+ runSwing(() -> tool.showComponentProvider(decompilerProvider, true));
+ createProgram();
+ programManager.openProgram(program);
+ waitForSwing();
+
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Create function")) {
+ entry = addr(program, 0x00400000);
+ program.getMemory()
+ .createInitializedBlock(".text", entry, 0x1000, (byte) 0,
+ TaskMonitor.DUMMY, false);
+ Disassembler dis = Disassembler.getDisassembler(program, monitor, null);
+ dis.disassemble(entry, new AddressSet(entry));
+
+ function = program.getFunctionManager()
+ .createFunction("test", entry,
+ new AddressSet(entry), SourceType.USER_DEFINED);
+ }
+ }
+
protected void addLiveMemoryAndBreakpoint(TraceRecorder recorder)
throws InterruptedException, ExecutionException, TimeoutException {
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
@@ -704,4 +734,85 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
waitForPass(() -> assertEquals(State.NONE, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces
}
+
+ protected static DecompileData mockData(DecompileResults decompileResults) {
+ Function function = decompileResults.getFunction();
+ Program program = function.getProgram();
+ ProgramLocation location = new ProgramLocation(program, function.getEntryPoint());
+ return new DecompileData(program, function, location, decompileResults, "", null, null);
+ }
+
+ @Test
+ public void testToggleBreakpointDecompilerNoAddress() throws Throwable {
+ prepareDecompiler();
+ DecompileResults results = new MockDecompileResults(function) {
+ {
+ root(function(token("token_no_addr")));
+ }
+ };
+ runSwing(() -> decompilerProvider.getController().setDecompileData(mockData(results)));
+
+ runSwing(() -> assertFalse(breakpointMarkerPlugin.actionToggleBreakpoint
+ .isEnabledForContext(decompilerProvider.getActionContext(null))));
+ }
+
+ @Test
+ public void testToggleBreakpointDecompilerOneAddress() throws Throwable {
+ prepareDecompiler();
+ DecompileResults results = new MockDecompileResults(function) {
+ {
+ root(function(token("token_with_addr", entry)));
+ }
+ };
+ runSwing(() -> decompilerProvider.getController().setDecompileData(mockData(results)));
+
+ performEnabledAction(decompilerProvider, breakpointMarkerPlugin.actionToggleBreakpoint,
+ false);
+ DebuggerPlaceBreakpointDialog dialog =
+ waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
+ runSwing(() -> dialog.okCallback());
+ waitForPass(() -> {
+ LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
+ assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState());
+ assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), lb.getKinds());
+ });
+
+ performEnabledAction(decompilerProvider, breakpointMarkerPlugin.actionToggleBreakpoint,
+ true);
+ waitForPass(() -> {
+ LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
+ assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
+ assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), lb.getKinds());
+ });
+ }
+
+ @Test
+ public void testToggleBreakpointDecompilerTwoAddresses() throws Throwable {
+ prepareDecompiler();
+ DecompileResults results = new MockDecompileResults(function) {
+ {
+ root(function(token("token1_with_addr", entry),
+ token("token2_with_addr", entry.add(2))));
+ }
+ };
+ runSwing(() -> decompilerProvider.getController().setDecompileData(mockData(results)));
+
+ waitOn(breakpointService.placeBreakpointAt(program, entry, 1,
+ Set.of(TraceBreakpointKind.SW_EXECUTE), ""));
+ waitOn(breakpointService.placeBreakpointAt(program, entry.add(2), 1,
+ Set.of(TraceBreakpointKind.SW_EXECUTE), ""));
+ waitForPass(() -> {
+ assertEquals(2, breakpointService.getAllBreakpoints().size());
+ });
+
+ performEnabledAction(decompilerProvider, breakpointMarkerPlugin.actionToggleBreakpoint,
+ true);
+ waitForPass(() -> {
+ Set all = breakpointService.getAllBreakpoints();
+ assertEquals(2, all.size());
+ for (LogicalBreakpoint lb : all) {
+ assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
+ }
+ });
+ }
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java
new file mode 100644
index 0000000000..8a573e3bc3
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java
@@ -0,0 +1,133 @@
+/* ###
+ * 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.util.List;
+
+import ghidra.app.decompiler.*;
+import ghidra.app.decompiler.DecompileProcess.DisposeState;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+
+public class MockDecompileResults extends DecompileResults {
+ private ClangTokenGroup root;
+
+ public interface ClangBuilder {
+ T build(ClangTokenGroup parent);
+
+ default T build() {
+ return build(null);
+ }
+ }
+
+ public static class MockToken extends ClangToken {
+ private final Address address;
+
+ public MockToken(ClangNode parent, String txt, Address address) {
+ super(parent, txt);
+ this.address = address;
+ }
+
+ public MockToken(ClangNode parent, String txt) {
+ this(parent, txt, null);
+ }
+
+ @Override
+ public Address getMinAddress() {
+ return address;
+ }
+
+ @Override
+ public Address getMaxAddress() {
+ return address;
+ }
+ }
+
+ public static class TokenBuilder implements ClangBuilder {
+ private final String text;
+ private final Address address;
+
+ public TokenBuilder(String text, Address address) {
+ this.text = text;
+ this.address = address;
+ }
+
+ public TokenBuilder(String text) {
+ this(text, null);
+ }
+
+ @Override
+ public ClangToken build(ClangTokenGroup parent) {
+ return new MockToken(parent, text, address);
+ }
+ }
+
+ public static class GroupBuilder implements ClangBuilder {
+ private final List> children;
+
+ public GroupBuilder(ClangBuilder>... children) {
+ this.children = List.of(children);
+ }
+
+ protected ClangTokenGroup newGroup(ClangTokenGroup parent) {
+ return new ClangTokenGroup(parent);
+ }
+
+ @Override
+ public ClangTokenGroup build(ClangTokenGroup parent) {
+ ClangTokenGroup group = newGroup(parent);
+ for (ClangBuilder> child : children) {
+ group.AddTokenGroup(child.build(group));
+ }
+ return group;
+ }
+ }
+
+ public MockDecompileResults(Function function) {
+ super(function, function.getProgram().getLanguage(),
+ function.getProgram().getCompilerSpec(), null, "", null, DisposeState.NOT_DISPOSED);
+ }
+
+ @Override
+ public ClangTokenGroup getCCodeMarkup() {
+ return root;
+ }
+
+ protected void root(GroupBuilder group) {
+ root = group.build();
+ }
+
+ protected GroupBuilder function(ClangBuilder>... children) {
+ return new GroupBuilder(children) {
+ @Override
+ protected ClangTokenGroup newGroup(ClangTokenGroup parent) {
+ return new ClangFunction(parent, null);
+ }
+ };
+ }
+
+ protected GroupBuilder group(ClangBuilder>... children) {
+ return new GroupBuilder(children);
+ }
+
+ protected TokenBuilder token(String text) {
+ return new TokenBuilder(text);
+ }
+
+ protected TokenBuilder token(String text, Address address) {
+ return new TokenBuilder(text, address);
+ }
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java
index b31e769738..e9188f0cc5 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
- * REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,23 +19,28 @@
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
-
+
package ghidra.app.decompiler;
import ghidra.program.model.pcode.HighFunction;
/**
- *
- *
- * All the fields making up one function in the display
+ * All the tokens making up one function in the display
*/
public class ClangFunction extends ClangTokenGroup {
- private HighFunction hfunc;
-
- public ClangFunction(ClangNode par,HighFunction hf) { super(par); hfunc = hf; }
- @Override
- public ClangFunction getClangFunction() { return this; }
- public HighFunction getHighFunction() { return hfunc; }
-}
-
+ private final HighFunction hfunc;
+ public ClangFunction(ClangNode parent, HighFunction hfunc) {
+ super(parent);
+ this.hfunc = hfunc;
+ }
+
+ @Override
+ public ClangFunction getClangFunction() {
+ return this;
+ }
+
+ public HighFunction getHighFunction() {
+ return hfunc;
+ }
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java
index 6309c1d448..2fbf530c18 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java
@@ -49,9 +49,9 @@ public class ClangTokenGroup implements ClangNode, Iterable {
return maxaddress;
}
- public void AddTokenGroup(Object obj) {
- Address minaddr = ((ClangNode) obj).getMinAddress();
- Address maxaddr = ((ClangNode) obj).getMaxAddress();
+ public void AddTokenGroup(ClangNode obj) {
+ Address minaddr = obj.getMinAddress();
+ Address maxaddr = obj.getMaxAddress();
if (minaddr != null) {
if (minaddress == null) {
@@ -69,7 +69,7 @@ public class ClangTokenGroup implements ClangNode, Iterable {
maxaddress = maxaddr;
}
}
- tokgroup.add((ClangNode) obj);
+ tokgroup.add(obj);
}
@Override
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java
index 4dc074aded..f2bc5271eb 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java
@@ -15,6 +15,7 @@
*/
package ghidra.app.decompiler;
+import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.margin.DecompilerMarginProvider;
/**
@@ -34,4 +35,11 @@ public interface DecompilerMarginService {
* @param provider the margin provider
*/
void removeMarginProvider(DecompilerMarginProvider provider);
+
+ /**
+ * Get the panel associated with this margin
+ *
+ * @return the panel
+ */
+ DecompilerPanel getDecompilerPanel();
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java
index d153587ba8..c77bea769c 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java
@@ -82,8 +82,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
clearHighlights();
- ClangLayoutController layoutModel =
- (ClangLayoutController) decompilerPanel.getLayoutModel();
+ ClangLayoutController layoutModel = decompilerPanel.getLayoutController();
ClangTokenGroup root = layoutModel.getRoot();
Map highlights = new HashMap<>();
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
index 6ece053a8a..a37dadf9c6 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
@@ -69,7 +69,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private LineNumberDecompilerMarginProvider lineNumbersMargin;
private final DecompilerFieldPanel fieldPanel;
- private ClangLayoutController layoutMgr;
+ private ClangLayoutController layoutController;
private final IndexedScrollPane scroller;
private final JComponent taskMonitorComponent;
@@ -114,8 +114,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
hlFactory = new SearchHighlightFactory();
- layoutMgr = new ClangLayoutController(options, this, metrics, hlFactory);
- fieldPanel = new DecompilerFieldPanel(layoutMgr);
+ layoutController = new ClangLayoutController(options, this, metrics, hlFactory);
+ fieldPanel = new DecompilerFieldPanel(layoutController);
setBackground(options.getCodeViewerBackgroundColor());
scroller = new IndexedScrollPane(fieldPanel);
@@ -144,11 +144,11 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public List getLines() {
- return layoutMgr.getLines();
+ return layoutController.getLines();
}
public List getFields() {
- return Arrays.asList(layoutMgr.getFields());
+ return Arrays.asList(layoutController.getFields());
}
public FieldPanel getFieldPanel() {
@@ -265,12 +265,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public void addHighlights(Set varnodes, ColorProvider colorProvider) {
- ClangTokenGroup root = layoutMgr.getRoot();
+ ClangTokenGroup root = layoutController.getRoot();
highlightController.addPrimaryHighlights(root, colorProvider);
}
public void addHighlights(Set ops, Color hlColor) {
- ClangTokenGroup root = layoutMgr.getRoot();
+ ClangTokenGroup root = layoutController.getRoot();
highlightController.addPrimaryHighlights(root, ops, hlColor);
}
@@ -395,7 +395,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
* @param decompileData the new data
*/
void setDecompileData(DecompileData decompileData) {
- if (layoutMgr == null) {
+ if (layoutController == null) {
// we've been disposed!
return;
}
@@ -404,14 +404,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
this.decompileData = decompileData;
Function function = decompileData.getFunction();
if (decompileData.hasDecompileResults()) {
- layoutMgr.buildLayouts(function, decompileData.getCCodeMarkup(), null, true);
+ layoutController.buildLayouts(function, decompileData.getCCodeMarkup(), null, true);
if (decompileData.getDebugFile() != null) {
controller.setStatusMessage(
"Debug file generated: " + decompileData.getDebugFile().getAbsolutePath());
}
}
else {
- layoutMgr.buildLayouts(null, null, decompileData.getErrorMessage(), true);
+ layoutController.buildLayouts(null, null, decompileData.getErrorMessage(), true);
}
setLocation(oldData, decompileData);
@@ -475,8 +475,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
}
- public LayoutModel getLayoutModel() {
- return layoutMgr;
+ public ClangLayoutController getLayoutController() {
+ return layoutController;
}
public boolean containsLocation(ProgramLocation location) {
@@ -520,7 +520,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
- List tokens = DecompilerUtils.getTokensFromView(layoutMgr.getFields(), address);
+ List tokens = DecompilerUtils.getTokensFromView(layoutController.getFields(), address);
goToBeginningOfLine(tokens);
}
@@ -535,7 +535,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return false;
}
- List lines = layoutMgr.getLines();
+ List lines = layoutController.getLines();
ClangLine signatureLine = getFunctionSignatureLine(lines);
if (signatureLine == null) {
return false; // can happen when there is no function decompiled
@@ -570,7 +570,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
- int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields());
+ int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutController.getFields());
if (firstLineNumber != -1) {
fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false);
}
@@ -629,7 +629,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
fieldSelection = new FieldSelection();
}
else {
- List tokens = DecompilerUtils.getTokens(layoutMgr.getRoot(), selection);
+ List tokens = DecompilerUtils.getTokens(layoutController.getRoot(), selection);
fieldSelection = DecompilerUtils.getFieldSelection(tokens);
}
fieldPanel.setSelection(fieldSelection);
@@ -652,7 +652,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
public void dispose() {
setDecompileData(new EmptyDecompileData("Disposed"));
- layoutMgr = null;
+ layoutController = null;
decompilerHoverProvider.dispose();
highlighCursorUpdater.dispose();
highlightController.dispose();
@@ -771,7 +771,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
ClangNode node = token.Parent();
if (node instanceof ClangStatement) {
// check for a goto label
- ClangTokenGroup root = layoutMgr.getRoot();
+ ClangTokenGroup root = layoutController.getRoot();
ClangLabelToken destination = DecompilerUtils.getGoToTargetToken(root, token);
if (destination != null) {
goToToken(destination);
@@ -909,7 +909,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
if (trigger != EventTrigger.API_CALL) {
Program program = decompileData.getProgram();
- Field[] lines = layoutMgr.getFields();
+ Field[] lines = layoutController.getFields();
List tokenList = DecompilerUtils.getTokensInSelection(selection, lines);
AddressSpace functionSpace = decompileData.getFunctionSpace();
AddressSet addrset =
@@ -923,7 +923,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
public void layoutsChanged(List layouts) {
pixmap.layoutsChanged(layouts);
for (DecompilerMarginProvider element : marginProviders) {
- element.setProgram(getProgram(), layoutMgr, pixmap);
+ element.setProgram(getProgram(), layoutController, pixmap);
}
}
@@ -937,7 +937,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
Address address = DecompilerUtils.getClosestAddress(getProgram(), token);
if (address == null) {
- address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token);
+ address = DecompilerUtils.findAddressBefore(layoutController.getFields(), token);
}
if (address == null) {
address = decompileData.getFunction().getEntryPoint();
@@ -954,12 +954,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
public SearchLocation searchText(String text, FieldLocation startLocation,
boolean forwardDirection) {
- return layoutMgr.findNextTokenForSearch(text, startLocation, forwardDirection);
+ return layoutController.findNextTokenForSearch(text, startLocation, forwardDirection);
}
public SearchLocation searchTextRegex(String text, FieldLocation startLocation,
boolean forwardDirection) {
- return layoutMgr.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
+ return layoutController.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
}
public void setSearchResults(SearchLocation searchLocation) {
@@ -1057,7 +1057,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return null;
}
- Field[] lines = layoutMgr.getFields();
+ Field[] lines = layoutController.getFields();
List tokens = DecompilerUtils.getTokensInSelection(selection, lines);
long count = tokens.stream().filter(t -> !t.getText().trim().isEmpty()).count();
@@ -1076,6 +1076,19 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return ((ClangTextField) field).getToken(cursorPosition);
}
+ /**
+ * Get the line number for the given y position, relative to the scroll panel
+ *
+ *
+ * If the y position is below all the lines, the last line is returned.
+ *
+ * @param y the y position
+ * @return the line number, or 0 if not applicable
+ */
+ public int getLineNumber(int y) {
+ return pixmap.getIndex(y).intValue() + 1;
+ }
+
public DecompileOptions getOptions() {
return options;
}
@@ -1104,7 +1117,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
public List findTokensByName(String name) {
List tokens = new ArrayList<>();
- doFindTokensByName(tokens, layoutMgr.getRoot(), name);
+ doFindTokensByName(tokens, layoutController.getRoot(), name);
return tokens;
}
@@ -1139,7 +1152,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public void selectAll(EventTrigger trigger) {
- BigInteger numIndexes = layoutMgr.getNumIndexes();
+ BigInteger numIndexes = layoutController.getNumIndexes();
FieldSelection selection = new FieldSelection();
selection.addRange(BigInteger.ZERO, numIndexes);
fieldPanel.setSelection(selection, trigger);
@@ -1172,9 +1185,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public void addMarginProvider(DecompilerMarginProvider provider) {
- marginProviders.add(provider);
+ marginProviders.add(0, provider);
provider.setOptions(options);
- provider.setProgram(getProgram(), layoutMgr, pixmap);
+ provider.setProgram(getProgram(), layoutController, pixmap);
buildPanels();
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java
index 1f4363d965..eb7ea75b78 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java
@@ -51,7 +51,6 @@ public class LineNumberDecompilerMarginProvider extends JPanel
this.model.removeLayoutModelListener(this);
}
this.model = model;
- this.model.addLayoutModelListener(this);
setWidthForLastLine();
if (this.model != null) {
this.model.addLayoutModelListener(this);
@@ -87,7 +86,7 @@ public class LineNumberDecompilerMarginProvider extends JPanel
}
int lastLine = model.getNumIndexes().intValueExact();
int width = getFontMetrics(getFont()).stringWidth(Integer.toString(lastLine));
- setPreferredSize(new Dimension(width, 0));
+ setPreferredSize(new Dimension(Math.max(16, width), 0));
invalidate();
}
@@ -99,7 +98,9 @@ public class LineNumberDecompilerMarginProvider extends JPanel
BigInteger endIdx = pixmap.getIndex(visible.y + visible.height);
int ascent = g.getFontMetrics().getMaxAscent();
for (BigInteger i = startIdx; i.compareTo(endIdx) <= 0; i = i.add(BigInteger.ONE)) {
- GraphicsUtils.drawString(this, g, i.add(BigInteger.ONE).toString(), 0,
+ String text = i.add(BigInteger.ONE).toString();
+ int width = g.getFontMetrics().stringWidth(text);
+ GraphicsUtils.drawString(this, g, text, getWidth() - width,
pixmap.getPixel(i) + ascent);
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
index f9382d2c3c..73ac48a123 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
@@ -49,7 +49,9 @@ import ghidra.util.task.SwingUpdateManager;
GoToService.class, NavigationHistoryService.class, ClipboardService.class,
DataTypeManagerService.class /*, ProgramManager.class */
},
- servicesProvided = { DecompilerHighlightService.class },
+ servicesProvided = {
+ DecompilerHighlightService.class, DecompilerMarginService.class
+ },
eventsConsumed = {
ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class,
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java
index 76163df5e3..ccde49eb95 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java
@@ -21,8 +21,7 @@ import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.context.RestrictedAddressSetContext;
-import ghidra.app.decompiler.ClangToken;
-import ghidra.app.decompiler.ClangTokenGroup;
+import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
@@ -36,15 +35,45 @@ public class DecompilerActionContext extends NavigatableActionContext
implements RestrictedAddressSetContext {
private final Address functionEntryPoint;
private final boolean isDecompiling;
+ private final int lineNumber;
private ClangToken tokenAtCursor = null;
private boolean tokenIsInitialized = false;
+ /**
+ * Construct a context specifying the line number
+ *
+ *
+ * The specified line number may not necessarily correspond to that of the current token. This
+ * is usually the case when the user clicks somewhere where a token is not present, e.g., the
+ * margin. In these cases, the line number should be that under the mouse cursor.
+ *
+ * @param provider the decompiler provider producing the context
+ * @param functionEntryPoint the current function's entry, if applicable
+ * @param isDecompiling true if the decompiler is still working
+ * @param lineNumber non-zero to specify the line number
+ */
public DecompilerActionContext(DecompilerProvider provider, Address functionEntryPoint,
- boolean isDecompiling) {
+ boolean isDecompiling, int lineNumber) {
super(provider, provider);
+ if (lineNumber < 0) {
+ throw new IllegalArgumentException("lineNumber must be >= 0. Got " + lineNumber);
+ }
this.functionEntryPoint = functionEntryPoint;
this.isDecompiling = isDecompiling;
+ this.lineNumber = lineNumber;
+ }
+
+ /**
+ * Construct a context using the current token's line number
+ *
+ * @param provider the decompiler provider producing the context
+ * @param functionEntryPoint the current function's entry, if applicable
+ * @param isDecompiling true if the decompiler is still working
+ */
+ public DecompilerActionContext(DecompilerProvider provider, Address functionEntryPoint,
+ boolean isDecompiling) {
+ this(provider, functionEntryPoint, isDecompiling, 0);
}
public Address getFunctionEntryPoint() {
@@ -72,6 +101,32 @@ public class DecompilerActionContext extends NavigatableActionContext
return tokenAtCursor;
}
+ /**
+ * Get the line number
+ *
+ *
+ * This may not always correspond to the line number of the token at the cursor. For example, if
+ * there is no token under the mouse, or if the context is produced by the margin. When
+ * generated by a mouse event, this is the line number determined by the mouse's vertical
+ * position. Otherwise, this is the line number of the current token. If there is no current
+ * token and the line number was not given at construction, this returns 0 to indicate this
+ * context has no line number.
+ *
+ *
+ * If the current token's line number is desired, regardless of the user's mouse position, then
+ * use {@code context.}{@link #getTokenAtCursor()}{@code .}{@link ClangToken#getLineParent()
+ * getLineParent()}{@code .}{@link ClangLine#getLineNumber() getLineNumber()}.
+ *
+ * @return the line number
+ */
+ public int getLineNumber() {
+ if (lineNumber != 0) {
+ return lineNumber;
+ }
+ getTokenAtCursor();
+ return tokenAtCursor == null ? 0 : tokenAtCursor.getLineParent().getLineNumber();
+ }
+
public DecompilerPanel getDecompilerPanel() {
return getComponentProvider().getDecompilerPanel();
}
@@ -98,9 +153,12 @@ public class DecompilerActionContext extends NavigatableActionContext
}
/**
- * The companion method of {@link #checkActionEnablement(Supplier)}. Decompiler actions
- * must call this method from their {@link DockingActionIf#actionPerformed(ActionContext)}
- * if they require state from the Decompiler.
+ * The companion method of {@link #checkActionEnablement(Supplier)}.
+ *
+ *
+ * Decompiler actions must call this method from their
+ * {@link DockingActionIf#actionPerformed(ActionContext)} if they require state from the
+ * Decompiler.
*
* @param actionCallback the action's code to execute
*/
@@ -117,9 +175,12 @@ public class DecompilerActionContext extends NavigatableActionContext
}
/**
- * The companion method of {@link #performAction(Callback)}. Decompiler actions must call
- * this method from their {@link DockingActionIf#isEnabledForContext(ActionContext)} if they
- * require state from the Decompiler.
+ * The companion method of {@link #performAction(Callback)}.
+ *
+ *
+ * Decompiler actions must call this method from their
+ * {@link DockingActionIf#isEnabledForContext(ActionContext)} if they require state from the
+ * Decompiler.
*
* @param actionBooleanSupplier the action's code to verify its enablement
* @return true if the action should be considered enabled
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java
index 1851b9f2ee..1a2bab4b18 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java
@@ -206,7 +206,7 @@ public class DecompilerClipboardProvider extends ByteCopier
endRow = fieldRange.getEnd().getRow();
}
- LayoutModel model = provider.getDecompilerPanel().getLayoutModel();
+ LayoutModel model = provider.getDecompilerPanel().getLayoutController();
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
ClangTextField field = (ClangTextField) layout.getField(0);
int numSpaces = field.getStartX() / spaceCharWidthInPixels;
@@ -235,7 +235,7 @@ public class DecompilerClipboardProvider extends ByteCopier
int startRow = fieldRange.getStart().getRow();
int endRow = fieldRange.getEnd().getRow();
- LayoutModel model = provider.getDecompilerPanel().getLayoutModel();
+ LayoutModel model = provider.getDecompilerPanel().getLayoutController();
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
ClangTextField field = (ClangTextField) layout.getField(0);
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
index a5bcbf8ac5..47617b3d3a 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
@@ -197,7 +197,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
Address entryPoint = function.getEntryPoint();
boolean isDecompiling = controller.isDecompiling();
- return new DecompilerActionContext(this, entryPoint, isDecompiling);
+ int lineNumber =
+ event != null & !isDecompiling ? getDecompilerPanel().getLineNumber(event.getY()) : 0;
+ return new DecompilerActionContext(this, entryPoint, isDecompiling, lineNumber);
}
@Override
@@ -656,14 +658,15 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
followUpWorkUpdater.update();
}
+ @Override
+ public DecompilerPanel getDecompilerPanel() {
+ return controller.getDecompilerPanel();
+ }
+
//==================================================================================================
// methods called from other members
//==================================================================================================
- DecompilerPanel getDecompilerPanel() {
- return controller.getDecompilerPanel();
- }
-
// snapshot callback
public void cloneWindow() {
DecompilerProvider newProvider = plugin.createNewDisconnectedProvider();
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java
index d85c785d9f..d3c41f0108 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java
@@ -112,7 +112,8 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading
- DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
+ DecompilerActionContext context =
+ new DecompilerActionContext(provider, addr(0x0), false);
performAction(findReferencesAction, context, true);
ThreadedTableModel, ?> model = waitForSearchProvider();
@@ -123,7 +124,8 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading
- DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
+ DecompilerActionContext context =
+ new DecompilerActionContext(provider, addr(0x0), false);
performAction(findReferencesToAddressAction, context, true);
ThreadedTableModel, ?> model = waitForSearchProvider();
@@ -134,7 +136,8 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading
- DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
+ DecompilerActionContext context =
+ new DecompilerActionContext(provider, addr(0x0), false);
performAction(findReferencesToSymbolAction, context, true);
ThreadedTableModel, ?> model = waitForSearchProvider();
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java
index a4f56fce39..086dc7b143 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java
@@ -62,6 +62,7 @@ public class EquateTest extends AbstractDecompilerTest {
/**
* Simulate the action of "converting" the current token to the given format
+ *
* @param convertType is the given format
*/
private void convertToken(int convertType) {
@@ -94,6 +95,7 @@ public class EquateTest extends AbstractDecompilerTest {
/**
* Simulate setting an equate on the current token with the given name
+ *
* @param nm is the given name
*/
private void convertToken(String nm) {