GP-1280: Added breakpoint margin to Decompiler in Debugger.

This commit is contained in:
Dan
2022-10-17 12:18:30 -04:00
parent 35b58b3105
commit 518575b075
23 changed files with 844 additions and 189 deletions
+2
View File
@@ -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')
@@ -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|
@@ -32,6 +32,29 @@
the current target. <B>NOTE:</B> Depending on the connected debugger, locations resulting from
a common specification may not be independently manipulated.</P>
<H2>In the Decompiler</H2>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerDecompilerBreakpointMargin.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>When active in the Debugger, the <A href=
"help/topics/DecompilePlugin/DecompilerIntro.html">Decompiler</A> 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, <A href="#set_breakpoint">Setting</A> 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.</P>
<H2>Actions</H2>
<P>The following actions are added to all disassembly listings by the breakpoint marker plugin.
@@ -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<ClangLine> 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<ProgramLocation> 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<ClangLine> lines = plugin.decompilerMarginService.getDecompilerPanel().getLines();
List<ProgramLocation> locs =
DebuggerBreakpointMarkerPlugin.nearestLocationsToLine(program, i, lines);
if (locs == null || locs.isEmpty()) {
return;
}
Set<LogicalBreakpoint> 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());
});
}
}
}
@@ -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
*
* <p>
* 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<Address> getAddressesFromLine(ClangLine line) {
Set<Address> 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<ProgramLocation> getLocationsFromLine(Program program, ClangLine line) {
List<ProgramLocation> 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<ProgramLocation> nearestLocationsToLine(Program program, int index,
List<ClangLine> lines) {
if (index < 0) {
return null;
}
for (int n = index; n < lines.size(); n++) {
ClangLine clangLine = lines.get(n);
List<ProgramLocation> 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<ProgramLocation> 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<ProgramLocation> locs = getLocationsFromContext(context);
return locs != null && !locs.isEmpty();
}
protected static Trace getTraceFromContext(ActionContext context) {
ProgramLocation loc = getLocationFromContext(context);
if (loc == null) {
List<ProgramLocation> 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<LogicalBreakpoint> collectBreakpoints(Collection<ProgramLocation> locs) {
return locs.stream()
.flatMap(l -> breakpointService.getBreakpointsAt(l).stream())
.collect(Collectors.toSet());
}
protected State computeState(List<ProgramLocation> locs) {
if (locs.isEmpty()) {
return State.NONE;
}
Set<LogicalBreakpoint> 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<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
List<ProgramLocation> locs = getLocationsFromContext(context);
Set<LogicalBreakpoint> 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<ProgramLocation> 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<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
List<ProgramLocation> locs = getLocationsFromContext(context);
Set<LogicalBreakpoint> 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<ProgramLocation> 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<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
List<ProgramLocation> locs = getLocationsFromContext(context);
Set<LogicalBreakpoint> 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<ProgramLocation> 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<TraceLocation> mappedLocs = mappingService.getOpenMappedLocations(loc);
if (mappedLocs == null || mappedLocs.isEmpty()) {
return Set.of();
}
Set<TraceRecorder> 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<ProgramLocation> locs = getLocationsFromContext(context);
if (locs == null || locs.isEmpty()) {
return;
}
String status = breakpointService.generateStatusToggleAt(loc);
Set<LogicalBreakpoint> 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<TraceBreakpointKind> 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<TraceBreakpointKind> 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));
@@ -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<LogicalBreakpoint> bs = getBreakpointsAt(loc);
public String generateStatusToggleAt(Set<LogicalBreakpoint> bs, ProgramLocation loc) {
if (bs == null || bs.isEmpty()) {
return null;
}
@@ -1242,9 +1245,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
@Override
public CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation loc,
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer) {
Set<LogicalBreakpoint> bs = getBreakpointsAt(loc);
public CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(Set<LogicalBreakpoint> bs,
ProgramLocation loc, Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer) {
if (bs == null || bs.isEmpty()) {
return placer.get();
}
@@ -370,20 +370,47 @@ public interface DebuggerLogicalBreakpointService {
*/
CompletableFuture<Void> deleteLocs(Collection<TraceBreakpoint> col);
/**
* Generate an informational message when toggling the breakpoints
*
* <p>
* 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<LogicalBreakpoint> bs, ProgramLocation loc);
/**
* Generate an informational message when toggling the breakpoints at the given location
*
* <p>
* 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<Set<LogicalBreakpoint>> toggleBreakpointsAt(Set<LogicalBreakpoint> bs,
ProgramLocation location, Supplier<CompletableFuture<Set<LogicalBreakpoint>>> 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<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation location,
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer);
default CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation location,
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer) {
return toggleBreakpointsAt(getBreakpointsAt(location), location, placer);
}
}
@@ -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(
@@ -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<LogicalBreakpoint> all = breakpointService.getAllBreakpoints();
assertEquals(2, all.size());
for (LogicalBreakpoint lb : all) {
assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState());
}
});
}
}
@@ -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 extends ClangNode> {
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<ClangToken> {
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<ClangTokenGroup> {
private final List<ClangBuilder<?>> 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);
}
}
@@ -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;
}
}
@@ -49,9 +49,9 @@ public class ClangTokenGroup implements ClangNode, Iterable<ClangNode> {
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<ClangNode> {
maxaddress = maxaddr;
}
}
tokgroup.add((ClangNode) obj);
tokgroup.add(obj);
}
@Override
@@ -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();
}
@@ -82,8 +82,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
clearHighlights();
ClangLayoutController layoutModel =
(ClangLayoutController) decompilerPanel.getLayoutModel();
ClangLayoutController layoutModel = decompilerPanel.getLayoutController();
ClangTokenGroup root = layoutModel.getRoot();
Map<ClangToken, Color> highlights = new HashMap<>();
@@ -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<ClangLine> getLines() {
return layoutMgr.getLines();
return layoutController.getLines();
}
public List<Field> 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<Varnode> varnodes, ColorProvider colorProvider) {
ClangTokenGroup root = layoutMgr.getRoot();
ClangTokenGroup root = layoutController.getRoot();
highlightController.addPrimaryHighlights(root, colorProvider);
}
public void addHighlights(Set<PcodeOp> 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<ClangToken> tokens = DecompilerUtils.getTokensFromView(layoutMgr.getFields(), address);
List<ClangToken> tokens = DecompilerUtils.getTokensFromView(layoutController.getFields(), address);
goToBeginningOfLine(tokens);
}
@@ -535,7 +535,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return false;
}
List<ClangLine> lines = layoutMgr.getLines();
List<ClangLine> 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<ClangToken> tokens = DecompilerUtils.getTokens(layoutMgr.getRoot(), selection);
List<ClangToken> 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<ClangToken> 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<AnchoredLayout> 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<ClangToken> 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
*
* <p>
* 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<ClangToken> findTokensByName(String name) {
List<ClangToken> 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();
}
@@ -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);
}
}
@@ -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,
@@ -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
*
* <p>
* 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
*
* <p>
* 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.
*
* <p>
* 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)}.
*
* <p>
* 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)}.
*
* <p>
* 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
@@ -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);
@@ -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();
@@ -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();
@@ -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) {