mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 05:45:51 +08:00
GP-1280: Added breakpoint margin to Decompiler in Debugger.
This commit is contained in:
@@ -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|
|
||||
|
||||
+23
@@ -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.
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+140
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
-104
@@ -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));
|
||||
|
||||
+7
-5
@@ -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();
|
||||
}
|
||||
|
||||
+35
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -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(
|
||||
|
||||
+115
-4
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+133
@@ -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
|
||||
|
||||
+8
@@ -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();
|
||||
}
|
||||
|
||||
+1
-2
@@ -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<>();
|
||||
|
||||
+41
-28
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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,
|
||||
|
||||
+70
-9
@@ -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
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
|
||||
+8
-5
@@ -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();
|
||||
|
||||
+6
-3
@@ -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();
|
||||
|
||||
+2
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user