mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-06-02 20:54:39 +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(':Framework-TraceModeling')
|
||||||
api project(':Base')
|
api project(':Base')
|
||||||
api project(':ByteViewer')
|
api project(':ByteViewer')
|
||||||
|
api project(':Decompiler')
|
||||||
api project(':ProposedUtils')
|
api project(':ProposedUtils')
|
||||||
|
|
||||||
helpPath project(path: ':Base', configuration: 'helpPath')
|
helpPath project(path: ':Base', configuration: 'helpPath')
|
||||||
|
helpPath project(path: ':Decompiler', configuration: 'helpPath')
|
||||||
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
|
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
|
||||||
|
|
||||||
testImplementation project(path: ':Base', configuration: 'testArtifacts')
|
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/DebuggerBots/DebuggerBots.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.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/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/DebuggerPlaceBreakpointDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/breakpoint-clear.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|
|
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
|
the current target. <B>NOTE:</B> Depending on the connected debugger, locations resulting from
|
||||||
a common specification may not be independently manipulated.</P>
|
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>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
<P>The following actions are added to all disassembly listings by the breakpoint marker plugin.
|
<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.action.*;
|
||||||
import docking.actions.PopupActionProvider;
|
import docking.actions.PopupActionProvider;
|
||||||
import ghidra.app.context.ProgramLocationActionContext;
|
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.ProgramClosedPluginEvent;
|
||||||
import ghidra.app.events.ProgramOpenedPluginEvent;
|
import ghidra.app.events.ProgramOpenedPluginEvent;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
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.event.TraceOpenedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
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.*;
|
||||||
import ghidra.app.services.LogicalBreakpoint.State;
|
import ghidra.app.services.LogicalBreakpoint.State;
|
||||||
import ghidra.app.util.viewer.listingpanel.MarkerClickedListener;
|
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.Address;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
|
import ghidra.program.model.pcode.Varnode;
|
||||||
import ghidra.program.util.*;
|
import ghidra.program.util.*;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.TraceLocation;
|
import ghidra.trace.model.TraceLocation;
|
||||||
@@ -76,44 +81,22 @@ import ghidra.util.Msg;
|
|||||||
public class DebuggerBreakpointMarkerPlugin extends Plugin
|
public class DebuggerBreakpointMarkerPlugin extends Plugin
|
||||||
implements PopupActionProvider {
|
implements PopupActionProvider {
|
||||||
|
|
||||||
protected static Address computeAddressFromContext(ActionContext context) {
|
protected static ProgramLocation getSingleLocationFromContext(ActionContext context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (context instanceof ProgramLocationActionContext) {
|
if (context instanceof DecompilerActionContext ctx) {
|
||||||
ProgramLocationActionContext ctx = (ProgramLocationActionContext) context;
|
// Use the token here, not the line
|
||||||
if (ctx.hasSelection()) {
|
if (!(ctx.getSourceComponent() instanceof LineNumberDecompilerMarginProvider) &&
|
||||||
ProgramSelection sel = ctx.getSelection();
|
ctx.getTokenAtCursor() instanceof ClangVariableToken tok) {
|
||||||
AddressRange range = sel.getRangeContaining(ctx.getAddress());
|
Varnode varnode = tok.getVarnode();
|
||||||
if (range != null) {
|
Address address = varnode == null ? null : varnode.getAddress();
|
||||||
return range.getMinAddress();
|
if (address != null && address.isMemoryAddress()) {
|
||||||
|
return new ProgramLocation(ctx.getProgram(), address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ctx.getAddress();
|
|
||||||
}
|
}
|
||||||
Object obj = context.getContextObject();
|
if (context instanceof ProgramLocationActionContext ctx) {
|
||||||
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 (ctx.hasSelection()) {
|
if (ctx.hasSelection()) {
|
||||||
ProgramSelection sel = ctx.getSelection();
|
ProgramSelection sel = ctx.getSelection();
|
||||||
AddressRange range = sel.getRangeContaining(ctx.getAddress());
|
AddressRange range = sel.getRangeContaining(ctx.getAddress());
|
||||||
@@ -124,13 +107,100 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
return ctx.getLocation();
|
return ctx.getLocation();
|
||||||
}
|
}
|
||||||
Object obj = context.getContextObject();
|
Object obj = context.getContextObject();
|
||||||
if (obj instanceof MarkerLocation) {
|
if (obj instanceof MarkerLocation ml) {
|
||||||
MarkerLocation ml = (MarkerLocation) obj;
|
|
||||||
return new ProgramLocation(ml.getProgram(), ml.getAddr());
|
return new ProgramLocation(ml.getProgram(), ml.getAddr());
|
||||||
}
|
}
|
||||||
return null;
|
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) {
|
protected static long computeLengthFromContext(ActionContext context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -153,15 +223,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean contextHasLocation(ActionContext context) {
|
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) {
|
protected static Trace getTraceFromContext(ActionContext context) {
|
||||||
ProgramLocation loc = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
if (loc == null) {
|
if (locs == null || locs.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Program progOrView = loc.getProgram();
|
Program progOrView = locs.get(0).getProgram();
|
||||||
if (progOrView instanceof TraceProgramView) {
|
if (progOrView instanceof TraceProgramView) {
|
||||||
TraceProgramView view = (TraceProgramView) progOrView;
|
TraceProgramView view = (TraceProgramView) progOrView;
|
||||||
return view.getTrace();
|
return view.getTrace();
|
||||||
@@ -190,7 +261,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
long length = computeLengthFromContext(ctx);
|
long length = computeLengthFromContext(ctx);
|
||||||
if (length == 1) {
|
if (length == 1) {
|
||||||
ProgramLocation loc = getLocationFromContext(ctx);
|
ProgramLocation loc = getSingleLocationFromContext(ctx);
|
||||||
Listing listing = loc.getProgram().getListing();
|
Listing listing = loc.getProgram().getListing();
|
||||||
CodeUnit cu = listing.getCodeUnitContaining(loc.getAddress());
|
CodeUnit cu = listing.getCodeUnitContaining(loc.getAddress());
|
||||||
if (cu instanceof Instruction) {
|
if (cu instanceof Instruction) {
|
||||||
@@ -218,41 +289,23 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Color colorForState(State state) {
|
protected Color colorForState(State state) {
|
||||||
if (state.isEnabled()) {
|
return state.isEnabled()
|
||||||
if (state.isEffective()) {
|
? state.isEffective()
|
||||||
return breakpointEnabledMarkerColor;
|
? breakpointEnabledMarkerColor
|
||||||
}
|
: breakpointIneffEnMarkerColor
|
||||||
else {
|
: state.isEffective()
|
||||||
return breakpointIneffEnMarkerColor;
|
? breakpointDisabledMarkerColor
|
||||||
}
|
: breakpointIneffDisMarkerColor;
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (state.isEffective()) {
|
|
||||||
return breakpointDisabledMarkerColor;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return breakpointIneffDisMarkerColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean stateColorsBackground(State state) {
|
protected boolean stateColorsBackground(State state) {
|
||||||
if (state.isEnabled()) {
|
return state.isEnabled()
|
||||||
if (state.isEffective()) {
|
? state.isEffective()
|
||||||
return breakpointEnabledColoringBackground;
|
? breakpointEnabledColoringBackground
|
||||||
}
|
: breakpointIneffEnColoringBackground
|
||||||
else {
|
: state.isEffective()
|
||||||
return breakpointIneffEnColoringBackground;
|
? breakpointDisabledColoringBackground
|
||||||
}
|
: breakpointIneffDisColoringBackground;
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (state.isEffective()) {
|
|
||||||
return breakpointDisabledColoringBackground;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return breakpointIneffDisColoringBackground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -432,17 +485,18 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
return breakpoint.computeState();
|
return breakpoint.computeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected Set<LogicalBreakpoint> collectBreakpoints(Collection<ProgramLocation> locs) {
|
||||||
* It seems the purpose of this was to omit the program mode from the dynamic listing. I don't
|
return locs.stream()
|
||||||
* think we need that anymore, so I've just delegated to exactly the same as the breakpoint
|
.flatMap(l -> breakpointService.getBreakpointsAt(l).stream())
|
||||||
* service, which will include the program mode, if applicable. TODO: Remove this and just call
|
.collect(Collectors.toSet());
|
||||||
* the service's version directly?
|
}
|
||||||
*
|
|
||||||
* @param loc
|
protected State computeState(List<ProgramLocation> locs) {
|
||||||
* @return
|
if (locs.isEmpty()) {
|
||||||
*/
|
return State.NONE;
|
||||||
protected State computeState(ProgramLocation loc) {
|
}
|
||||||
return breakpointService.computeState(loc);
|
Set<LogicalBreakpoint> col = collectBreakpoints(locs);
|
||||||
|
return breakpointService.computeState(col, locs.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class ToggleBreakpointAction extends AbstractToggleBreakpointAction {
|
protected class ToggleBreakpointAction extends AbstractToggleBreakpointAction {
|
||||||
@@ -489,7 +543,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
ProgramLocation location = getSingleLocationFromContext(context);
|
||||||
long length = computeDefaultLength(context, kinds);
|
long length = computeDefaultLength(context, kinds);
|
||||||
placeBreakpointDialog.prompt(tool, breakpointService, NAME, location, length, kinds,
|
placeBreakpointDialog.prompt(tool, breakpointService, NAME, location, length, kinds,
|
||||||
"");
|
"");
|
||||||
@@ -500,7 +554,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ProgramLocation loc = getLocationFromContext(context);
|
ProgramLocation loc = getSingleLocationFromContext(context);
|
||||||
if (!(loc.getProgram() instanceof TraceProgramView)) {
|
if (!(loc.getProgram() instanceof TraceProgramView)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -530,8 +584,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
|
Set<LogicalBreakpoint> col = collectBreakpoints(locs);
|
||||||
Trace trace = getTraceFromContext(context);
|
Trace trace = getTraceFromContext(context);
|
||||||
String status = breakpointService.generateStatusEnable(col, trace);
|
String status = breakpointService.generateStatusEnable(col, trace);
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
@@ -548,8 +602,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
State state = computeState(location);
|
State state = computeState(locs);
|
||||||
if (state == State.ENABLED || state == State.NONE) {
|
if (state == State.ENABLED || state == State.NONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -572,8 +626,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
|
Set<LogicalBreakpoint> col = collectBreakpoints(locs);
|
||||||
breakpointService.disableAll(col, getTraceFromContext(context)).exceptionally(ex -> {
|
breakpointService.disableAll(col, getTraceFromContext(context)).exceptionally(ex -> {
|
||||||
breakpointError(NAME, "Could not disable breakpoint", ex);
|
breakpointError(NAME, "Could not disable breakpoint", ex);
|
||||||
return null;
|
return null;
|
||||||
@@ -585,8 +639,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
State state = computeState(location);
|
State state = computeState(locs);
|
||||||
if (state == State.DISABLED || state == State.NONE) {
|
if (state == State.DISABLED || state == State.NONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -611,8 +665,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
Set<LogicalBreakpoint> col = breakpointService.getBreakpointsAt(location);
|
Set<LogicalBreakpoint> col = collectBreakpoints(locs);
|
||||||
breakpointService.deleteAll(col, getTraceFromContext(context)).exceptionally(ex -> {
|
breakpointService.deleteAll(col, getTraceFromContext(context)).exceptionally(ex -> {
|
||||||
breakpointError(NAME, "Could not delete breakpoint", ex);
|
breakpointError(NAME, "Could not delete breakpoint", ex);
|
||||||
return null;
|
return null;
|
||||||
@@ -624,8 +678,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (!contextCanManipulateBreakpoints(context)) {
|
if (!contextCanManipulateBreakpoints(context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ProgramLocation location = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
State state = computeState(location);
|
State state = computeState(locs);
|
||||||
if (state == State.NONE) {
|
if (state == State.NONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -636,7 +690,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
// @AutoServiceConsumed via method
|
// @AutoServiceConsumed via method
|
||||||
private MarkerService markerService;
|
private MarkerService markerService;
|
||||||
// @AutoServiceConsumed via method
|
// @AutoServiceConsumed via method
|
||||||
private DebuggerLogicalBreakpointService breakpointService;
|
DebuggerLogicalBreakpointService breakpointService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerModelService modelService;
|
private DebuggerModelService modelService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
@@ -645,6 +699,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
private DebuggerTraceManagerService traceManager;
|
private DebuggerTraceManagerService traceManager;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerConsoleService consoleService;
|
private DebuggerConsoleService consoleService;
|
||||||
|
// @AutoServiceConsumed via method
|
||||||
|
DecompilerMarginService decompilerMarginService;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
@@ -730,8 +786,11 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
|
|
||||||
DebuggerPlaceBreakpointDialog placeBreakpointDialog = new DebuggerPlaceBreakpointDialog();
|
DebuggerPlaceBreakpointDialog placeBreakpointDialog = new DebuggerPlaceBreakpointDialog();
|
||||||
|
|
||||||
|
BreakpointsDecompilerMarginProvider decompilerMarginProvider;
|
||||||
|
|
||||||
public DebuggerBreakpointMarkerPlugin(PluginTool tool) {
|
public DebuggerBreakpointMarkerPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
this.decompilerMarginProvider = new BreakpointsDecompilerMarginProvider(this);
|
||||||
this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||||
this.autoOptionsWiring = AutoOptions.wireOptions(this);
|
this.autoOptionsWiring = AutoOptions.wireOptions(this);
|
||||||
|
|
||||||
@@ -824,12 +883,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (mappingService == null || modelService == null) {
|
if (mappingService == null || modelService == null) {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
ProgramLocation loc = getLocationFromContext(context);
|
ProgramLocation loc = getSingleLocationFromContext(context);
|
||||||
if (loc == null || loc.getProgram() instanceof TraceProgramView) {
|
if (loc == null || loc.getProgram() instanceof TraceProgramView) {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
Set<TraceLocation> mappedLocs = mappingService.getOpenMappedLocations(loc);
|
||||||
|
if (mappedLocs == null || mappedLocs.isEmpty()) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
Set<TraceRecorder> result = new HashSet<>();
|
Set<TraceRecorder> result = new HashSet<>();
|
||||||
for (TraceLocation tloc : mappingService.getOpenMappedLocations(loc)) {
|
for (TraceLocation tloc : mappedLocs) {
|
||||||
TraceRecorder rec = modelService.getRecorder(tloc.getTrace());
|
TraceRecorder rec = modelService.getRecorder(tloc.getTrace());
|
||||||
if (rec != null) {
|
if (rec != null) {
|
||||||
result.add(rec);
|
result.add(rec);
|
||||||
@@ -870,15 +933,17 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
|
|||||||
if (breakpointService == null) {
|
if (breakpointService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation loc = getLocationFromContext(context);
|
List<ProgramLocation> locs = getLocationsFromContext(context);
|
||||||
if (loc == null) {
|
if (locs == null || locs.isEmpty()) {
|
||||||
return;
|
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) {
|
if (status != null) {
|
||||||
tool.setStatusInfo(status, true);
|
tool.setStatusInfo(status, true);
|
||||||
}
|
}
|
||||||
breakpointService.toggleBreakpointsAt(loc, () -> {
|
breakpointService.toggleBreakpointsAt(col, loc, () -> {
|
||||||
Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context);
|
Set<TraceBreakpointKind> supported = getSupportedKindsFromContext(context);
|
||||||
if (supported.isEmpty()) {
|
if (supported.isEmpty()) {
|
||||||
breakpointError(title, "It seems this target does not support breakpoints.");
|
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);
|
Set<TraceBreakpointKind> kinds = computeDefaultKinds(context, supported);
|
||||||
long length = computeDefaultLength(context, kinds);
|
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
|
// Not great, but I'm not sticking around for the dialog
|
||||||
return CompletableFuture.completedFuture(Set.of());
|
return CompletableFuture.completedFuture(Set.of());
|
||||||
}).exceptionally(ex -> {
|
}).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() {
|
protected void createActions() {
|
||||||
actionSetSoftwareBreakpoint =
|
actionSetSoftwareBreakpoint =
|
||||||
new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE));
|
new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE));
|
||||||
|
|||||||
+7
-5
@@ -583,6 +583,10 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TraceLocation toDynamicLocation(ProgramLocation loc) {
|
public TraceLocation toDynamicLocation(ProgramLocation loc) {
|
||||||
|
if (mappingService == null) {
|
||||||
|
// Must be shutting down
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return mappingService.getOpenMappedLocation(trace, loc, recorder.getSnap());
|
return mappingService.getOpenMappedLocation(trace, loc, recorder.getSnap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1216,8 +1220,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateStatusToggleAt(ProgramLocation loc) {
|
public String generateStatusToggleAt(Set<LogicalBreakpoint> bs, ProgramLocation loc) {
|
||||||
Set<LogicalBreakpoint> bs = getBreakpointsAt(loc);
|
|
||||||
if (bs == null || bs.isEmpty()) {
|
if (bs == null || bs.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1242,9 +1245,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation loc,
|
public CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(Set<LogicalBreakpoint> bs,
|
||||||
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer) {
|
ProgramLocation loc, Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer) {
|
||||||
Set<LogicalBreakpoint> bs = getBreakpointsAt(loc);
|
|
||||||
if (bs == null || bs.isEmpty()) {
|
if (bs == null || bs.isEmpty()) {
|
||||||
return placer.get();
|
return placer.get();
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-6
@@ -370,20 +370,47 @@ public interface DebuggerLogicalBreakpointService {
|
|||||||
*/
|
*/
|
||||||
CompletableFuture<Void> deleteLocs(Collection<TraceBreakpoint> col);
|
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
|
* Generate an informational message when toggling the breakpoints at the given location
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This works in the same manner as {@link #generateStatusEnable(Collection)}, except it is for
|
* This works in the same manner as {@link #generateStatusEnable(Collection, Trace))}, except it
|
||||||
* toggling breakpoints at a given location. If there are no breakpoints at the location, this
|
* is for toggling breakpoints at a given location. If there are no breakpoints at the location,
|
||||||
* should return null, since the usual behavior in that case is to prompt to place a new
|
* this should return null, since the usual behavior in that case is to prompt to place a new
|
||||||
* breakpoint.
|
* breakpoint.
|
||||||
*
|
*
|
||||||
* @see #generateStatusEnable(Collection)
|
* @see #generateStatusEnable(Collection)
|
||||||
* @param loc the location
|
* @param loc the location
|
||||||
* @return the status message, or null
|
* @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
|
* 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
|
* @param placer if there are no breakpoints, a routine for placing a breakpoint
|
||||||
* @return a future which completes when the command has been processed
|
* @return a future which completes when the command has been processed
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation location,
|
default CompletableFuture<Set<LogicalBreakpoint>> toggleBreakpointsAt(ProgramLocation location,
|
||||||
Supplier<CompletableFuture<Set<LogicalBreakpoint>>> placer);
|
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.model.DebuggerModelServiceProxyPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
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.plugin.core.progmgr.ProgramManagerPlugin;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.LogicalBreakpoint.State;
|
import ghidra.app.services.LogicalBreakpoint.State;
|
||||||
@@ -42,6 +44,7 @@ import ghidra.trace.model.DefaultTraceLocation;
|
|||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import help.screenshot.GhidraScreenShotGenerator;
|
import help.screenshot.GhidraScreenShotGenerator;
|
||||||
@@ -130,6 +133,44 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
|
|||||||
captureProviderWithScreenShot(listing);
|
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
|
@Test
|
||||||
public void testCaptureDebuggerPlaceBreakpointDialog() throws Throwable {
|
public void testCaptureDebuggerPlaceBreakpointDialog() throws Throwable {
|
||||||
runSwing(
|
runSwing(
|
||||||
|
|||||||
+115
-4
@@ -15,8 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.breakpoint;
|
package ghidra.app.plugin.core.debug.gui.breakpoint;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -41,11 +40,15 @@ import docking.widgets.fieldpanel.FieldPanel;
|
|||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
import ghidra.app.context.ProgramLocationActionContext;
|
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.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
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.*;
|
||||||
import ghidra.app.services.LogicalBreakpoint.State;
|
import ghidra.app.services.LogicalBreakpoint.State;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
@@ -53,11 +56,12 @@ import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
|||||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
import ghidra.program.disassemble.Disassembler;
|
import ghidra.program.disassemble.Disassembler;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.address.AddressOverflowException;
|
|
||||||
import ghidra.program.model.data.ByteDataType;
|
import ghidra.program.model.data.ByteDataType;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryConflictException;
|
import ghidra.program.model.mem.MemoryConflictException;
|
||||||
|
import ghidra.program.model.symbol.SourceType;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.DefaultTraceLocation;
|
import ghidra.trace.model.DefaultTraceLocation;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
@@ -85,6 +89,10 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
|
|||||||
protected DebuggerStaticMappingService mappingService;
|
protected DebuggerStaticMappingService mappingService;
|
||||||
protected MarkerService markerService;
|
protected MarkerService markerService;
|
||||||
|
|
||||||
|
protected Function function;
|
||||||
|
protected Address entry;
|
||||||
|
protected DecompilerProvider decompilerProvider;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUpBreakpointMarkerPluginTest() throws Exception {
|
public void setUpBreakpointMarkerPluginTest() throws Exception {
|
||||||
breakpointMarkerPlugin = addPlugin(tool, DebuggerBreakpointMarkerPlugin.class);
|
breakpointMarkerPlugin = addPlugin(tool, DebuggerBreakpointMarkerPlugin.class);
|
||||||
@@ -96,6 +104,28 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
|
|||||||
markerService = tool.getService(MarkerService.class);
|
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)
|
protected void addLiveMemoryAndBreakpoint(TraceRecorder recorder)
|
||||||
throws InterruptedException, ExecutionException, TimeoutException {
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
|
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)));
|
waitForPass(() -> assertEquals(State.NONE, lb.computeStateForTrace(trace)));
|
||||||
// TODO: Same test but with multiple traces
|
// 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
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -26,17 +25,22 @@ package ghidra.app.decompiler;
|
|||||||
import ghidra.program.model.pcode.HighFunction;
|
import ghidra.program.model.pcode.HighFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* All the tokens making up one function in the display
|
||||||
*
|
|
||||||
* All the fields making up one function in the display
|
|
||||||
*/
|
*/
|
||||||
public class ClangFunction extends ClangTokenGroup {
|
public class ClangFunction extends ClangTokenGroup {
|
||||||
private HighFunction hfunc;
|
private final HighFunction hfunc;
|
||||||
|
|
||||||
|
public ClangFunction(ClangNode parent, HighFunction hfunc) {
|
||||||
|
super(parent);
|
||||||
|
this.hfunc = hfunc;
|
||||||
|
}
|
||||||
|
|
||||||
public ClangFunction(ClangNode par,HighFunction hf) { super(par); hfunc = hf; }
|
|
||||||
@Override
|
@Override
|
||||||
public ClangFunction getClangFunction() { return this; }
|
public ClangFunction getClangFunction() {
|
||||||
public HighFunction getHighFunction() { return hfunc; }
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HighFunction getHighFunction() {
|
||||||
|
return hfunc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ public class ClangTokenGroup implements ClangNode, Iterable<ClangNode> {
|
|||||||
return maxaddress;
|
return maxaddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTokenGroup(Object obj) {
|
public void AddTokenGroup(ClangNode obj) {
|
||||||
Address minaddr = ((ClangNode) obj).getMinAddress();
|
Address minaddr = obj.getMinAddress();
|
||||||
Address maxaddr = ((ClangNode) obj).getMaxAddress();
|
Address maxaddr = obj.getMaxAddress();
|
||||||
|
|
||||||
if (minaddr != null) {
|
if (minaddr != null) {
|
||||||
if (minaddress == null) {
|
if (minaddress == null) {
|
||||||
@@ -69,7 +69,7 @@ public class ClangTokenGroup implements ClangNode, Iterable<ClangNode> {
|
|||||||
maxaddress = maxaddr;
|
maxaddress = maxaddr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokgroup.add((ClangNode) obj);
|
tokgroup.add(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+8
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.decompiler;
|
package ghidra.app.decompiler;
|
||||||
|
|
||||||
|
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||||
import ghidra.app.decompiler.component.margin.DecompilerMarginProvider;
|
import ghidra.app.decompiler.component.margin.DecompilerMarginProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,4 +35,11 @@ public interface DecompilerMarginService {
|
|||||||
* @param provider the margin provider
|
* @param provider the margin provider
|
||||||
*/
|
*/
|
||||||
void removeMarginProvider(DecompilerMarginProvider 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();
|
clearHighlights();
|
||||||
|
|
||||||
ClangLayoutController layoutModel =
|
ClangLayoutController layoutModel = decompilerPanel.getLayoutController();
|
||||||
(ClangLayoutController) decompilerPanel.getLayoutModel();
|
|
||||||
ClangTokenGroup root = layoutModel.getRoot();
|
ClangTokenGroup root = layoutModel.getRoot();
|
||||||
|
|
||||||
Map<ClangToken, Color> highlights = new HashMap<>();
|
Map<ClangToken, Color> highlights = new HashMap<>();
|
||||||
|
|||||||
+41
-28
@@ -69,7 +69,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
private LineNumberDecompilerMarginProvider lineNumbersMargin;
|
private LineNumberDecompilerMarginProvider lineNumbersMargin;
|
||||||
|
|
||||||
private final DecompilerFieldPanel fieldPanel;
|
private final DecompilerFieldPanel fieldPanel;
|
||||||
private ClangLayoutController layoutMgr;
|
private ClangLayoutController layoutController;
|
||||||
private final IndexedScrollPane scroller;
|
private final IndexedScrollPane scroller;
|
||||||
private final JComponent taskMonitorComponent;
|
private final JComponent taskMonitorComponent;
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
hlFactory = new SearchHighlightFactory();
|
hlFactory = new SearchHighlightFactory();
|
||||||
|
|
||||||
layoutMgr = new ClangLayoutController(options, this, metrics, hlFactory);
|
layoutController = new ClangLayoutController(options, this, metrics, hlFactory);
|
||||||
fieldPanel = new DecompilerFieldPanel(layoutMgr);
|
fieldPanel = new DecompilerFieldPanel(layoutController);
|
||||||
setBackground(options.getCodeViewerBackgroundColor());
|
setBackground(options.getCodeViewerBackgroundColor());
|
||||||
|
|
||||||
scroller = new IndexedScrollPane(fieldPanel);
|
scroller = new IndexedScrollPane(fieldPanel);
|
||||||
@@ -144,11 +144,11 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<ClangLine> getLines() {
|
public List<ClangLine> getLines() {
|
||||||
return layoutMgr.getLines();
|
return layoutController.getLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Field> getFields() {
|
public List<Field> getFields() {
|
||||||
return Arrays.asList(layoutMgr.getFields());
|
return Arrays.asList(layoutController.getFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldPanel getFieldPanel() {
|
public FieldPanel getFieldPanel() {
|
||||||
@@ -265,12 +265,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addHighlights(Set<Varnode> varnodes, ColorProvider colorProvider) {
|
public void addHighlights(Set<Varnode> varnodes, ColorProvider colorProvider) {
|
||||||
ClangTokenGroup root = layoutMgr.getRoot();
|
ClangTokenGroup root = layoutController.getRoot();
|
||||||
highlightController.addPrimaryHighlights(root, colorProvider);
|
highlightController.addPrimaryHighlights(root, colorProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addHighlights(Set<PcodeOp> ops, Color hlColor) {
|
public void addHighlights(Set<PcodeOp> ops, Color hlColor) {
|
||||||
ClangTokenGroup root = layoutMgr.getRoot();
|
ClangTokenGroup root = layoutController.getRoot();
|
||||||
highlightController.addPrimaryHighlights(root, ops, hlColor);
|
highlightController.addPrimaryHighlights(root, ops, hlColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
* @param decompileData the new data
|
* @param decompileData the new data
|
||||||
*/
|
*/
|
||||||
void setDecompileData(DecompileData decompileData) {
|
void setDecompileData(DecompileData decompileData) {
|
||||||
if (layoutMgr == null) {
|
if (layoutController == null) {
|
||||||
// we've been disposed!
|
// we've been disposed!
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -404,14 +404,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
this.decompileData = decompileData;
|
this.decompileData = decompileData;
|
||||||
Function function = decompileData.getFunction();
|
Function function = decompileData.getFunction();
|
||||||
if (decompileData.hasDecompileResults()) {
|
if (decompileData.hasDecompileResults()) {
|
||||||
layoutMgr.buildLayouts(function, decompileData.getCCodeMarkup(), null, true);
|
layoutController.buildLayouts(function, decompileData.getCCodeMarkup(), null, true);
|
||||||
if (decompileData.getDebugFile() != null) {
|
if (decompileData.getDebugFile() != null) {
|
||||||
controller.setStatusMessage(
|
controller.setStatusMessage(
|
||||||
"Debug file generated: " + decompileData.getDebugFile().getAbsolutePath());
|
"Debug file generated: " + decompileData.getDebugFile().getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
layoutMgr.buildLayouts(null, null, decompileData.getErrorMessage(), true);
|
layoutController.buildLayouts(null, null, decompileData.getErrorMessage(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocation(oldData, decompileData);
|
setLocation(oldData, decompileData);
|
||||||
@@ -475,8 +475,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayoutModel getLayoutModel() {
|
public ClangLayoutController getLayoutController() {
|
||||||
return layoutMgr;
|
return layoutController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsLocation(ProgramLocation location) {
|
public boolean containsLocation(ProgramLocation location) {
|
||||||
@@ -520,7 +520,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ClangToken> tokens = DecompilerUtils.getTokensFromView(layoutMgr.getFields(), address);
|
List<ClangToken> tokens = DecompilerUtils.getTokensFromView(layoutController.getFields(), address);
|
||||||
goToBeginningOfLine(tokens);
|
goToBeginningOfLine(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,7 +535,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ClangLine> lines = layoutMgr.getLines();
|
List<ClangLine> lines = layoutController.getLines();
|
||||||
ClangLine signatureLine = getFunctionSignatureLine(lines);
|
ClangLine signatureLine = getFunctionSignatureLine(lines);
|
||||||
if (signatureLine == null) {
|
if (signatureLine == null) {
|
||||||
return false; // can happen when there is no function decompiled
|
return false; // can happen when there is no function decompiled
|
||||||
@@ -570,7 +570,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields());
|
int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutController.getFields());
|
||||||
if (firstLineNumber != -1) {
|
if (firstLineNumber != -1) {
|
||||||
fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false);
|
fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false);
|
||||||
}
|
}
|
||||||
@@ -629,7 +629,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
fieldSelection = new FieldSelection();
|
fieldSelection = new FieldSelection();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
List<ClangToken> tokens = DecompilerUtils.getTokens(layoutMgr.getRoot(), selection);
|
List<ClangToken> tokens = DecompilerUtils.getTokens(layoutController.getRoot(), selection);
|
||||||
fieldSelection = DecompilerUtils.getFieldSelection(tokens);
|
fieldSelection = DecompilerUtils.getFieldSelection(tokens);
|
||||||
}
|
}
|
||||||
fieldPanel.setSelection(fieldSelection);
|
fieldPanel.setSelection(fieldSelection);
|
||||||
@@ -652,7 +652,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
setDecompileData(new EmptyDecompileData("Disposed"));
|
setDecompileData(new EmptyDecompileData("Disposed"));
|
||||||
layoutMgr = null;
|
layoutController = null;
|
||||||
decompilerHoverProvider.dispose();
|
decompilerHoverProvider.dispose();
|
||||||
highlighCursorUpdater.dispose();
|
highlighCursorUpdater.dispose();
|
||||||
highlightController.dispose();
|
highlightController.dispose();
|
||||||
@@ -771,7 +771,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
ClangNode node = token.Parent();
|
ClangNode node = token.Parent();
|
||||||
if (node instanceof ClangStatement) {
|
if (node instanceof ClangStatement) {
|
||||||
// check for a goto label
|
// check for a goto label
|
||||||
ClangTokenGroup root = layoutMgr.getRoot();
|
ClangTokenGroup root = layoutController.getRoot();
|
||||||
ClangLabelToken destination = DecompilerUtils.getGoToTargetToken(root, token);
|
ClangLabelToken destination = DecompilerUtils.getGoToTargetToken(root, token);
|
||||||
if (destination != null) {
|
if (destination != null) {
|
||||||
goToToken(destination);
|
goToToken(destination);
|
||||||
@@ -909,7 +909,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
if (trigger != EventTrigger.API_CALL) {
|
if (trigger != EventTrigger.API_CALL) {
|
||||||
Program program = decompileData.getProgram();
|
Program program = decompileData.getProgram();
|
||||||
Field[] lines = layoutMgr.getFields();
|
Field[] lines = layoutController.getFields();
|
||||||
List<ClangToken> tokenList = DecompilerUtils.getTokensInSelection(selection, lines);
|
List<ClangToken> tokenList = DecompilerUtils.getTokensInSelection(selection, lines);
|
||||||
AddressSpace functionSpace = decompileData.getFunctionSpace();
|
AddressSpace functionSpace = decompileData.getFunctionSpace();
|
||||||
AddressSet addrset =
|
AddressSet addrset =
|
||||||
@@ -923,7 +923,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
public void layoutsChanged(List<AnchoredLayout> layouts) {
|
public void layoutsChanged(List<AnchoredLayout> layouts) {
|
||||||
pixmap.layoutsChanged(layouts);
|
pixmap.layoutsChanged(layouts);
|
||||||
for (DecompilerMarginProvider element : marginProviders) {
|
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);
|
Address address = DecompilerUtils.getClosestAddress(getProgram(), token);
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token);
|
address = DecompilerUtils.findAddressBefore(layoutController.getFields(), token);
|
||||||
}
|
}
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
address = decompileData.getFunction().getEntryPoint();
|
address = decompileData.getFunction().getEntryPoint();
|
||||||
@@ -954,12 +954,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
|
|
||||||
public SearchLocation searchText(String text, FieldLocation startLocation,
|
public SearchLocation searchText(String text, FieldLocation startLocation,
|
||||||
boolean forwardDirection) {
|
boolean forwardDirection) {
|
||||||
return layoutMgr.findNextTokenForSearch(text, startLocation, forwardDirection);
|
return layoutController.findNextTokenForSearch(text, startLocation, forwardDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchLocation searchTextRegex(String text, FieldLocation startLocation,
|
public SearchLocation searchTextRegex(String text, FieldLocation startLocation,
|
||||||
boolean forwardDirection) {
|
boolean forwardDirection) {
|
||||||
return layoutMgr.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
|
return layoutController.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSearchResults(SearchLocation searchLocation) {
|
public void setSearchResults(SearchLocation searchLocation) {
|
||||||
@@ -1057,7 +1057,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Field[] lines = layoutMgr.getFields();
|
Field[] lines = layoutController.getFields();
|
||||||
List<ClangToken> tokens = DecompilerUtils.getTokensInSelection(selection, lines);
|
List<ClangToken> tokens = DecompilerUtils.getTokensInSelection(selection, lines);
|
||||||
|
|
||||||
long count = tokens.stream().filter(t -> !t.getText().trim().isEmpty()).count();
|
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);
|
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() {
|
public DecompileOptions getOptions() {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
@@ -1104,7 +1117,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
|
|
||||||
public List<ClangToken> findTokensByName(String name) {
|
public List<ClangToken> findTokensByName(String name) {
|
||||||
List<ClangToken> tokens = new ArrayList<>();
|
List<ClangToken> tokens = new ArrayList<>();
|
||||||
doFindTokensByName(tokens, layoutMgr.getRoot(), name);
|
doFindTokensByName(tokens, layoutController.getRoot(), name);
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1139,7 +1152,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void selectAll(EventTrigger trigger) {
|
public void selectAll(EventTrigger trigger) {
|
||||||
BigInteger numIndexes = layoutMgr.getNumIndexes();
|
BigInteger numIndexes = layoutController.getNumIndexes();
|
||||||
FieldSelection selection = new FieldSelection();
|
FieldSelection selection = new FieldSelection();
|
||||||
selection.addRange(BigInteger.ZERO, numIndexes);
|
selection.addRange(BigInteger.ZERO, numIndexes);
|
||||||
fieldPanel.setSelection(selection, trigger);
|
fieldPanel.setSelection(selection, trigger);
|
||||||
@@ -1172,9 +1185,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addMarginProvider(DecompilerMarginProvider provider) {
|
public void addMarginProvider(DecompilerMarginProvider provider) {
|
||||||
marginProviders.add(provider);
|
marginProviders.add(0, provider);
|
||||||
provider.setOptions(options);
|
provider.setOptions(options);
|
||||||
provider.setProgram(getProgram(), layoutMgr, pixmap);
|
provider.setProgram(getProgram(), layoutController, pixmap);
|
||||||
buildPanels();
|
buildPanels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -51,7 +51,6 @@ public class LineNumberDecompilerMarginProvider extends JPanel
|
|||||||
this.model.removeLayoutModelListener(this);
|
this.model.removeLayoutModelListener(this);
|
||||||
}
|
}
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.model.addLayoutModelListener(this);
|
|
||||||
setWidthForLastLine();
|
setWidthForLastLine();
|
||||||
if (this.model != null) {
|
if (this.model != null) {
|
||||||
this.model.addLayoutModelListener(this);
|
this.model.addLayoutModelListener(this);
|
||||||
@@ -87,7 +86,7 @@ public class LineNumberDecompilerMarginProvider extends JPanel
|
|||||||
}
|
}
|
||||||
int lastLine = model.getNumIndexes().intValueExact();
|
int lastLine = model.getNumIndexes().intValueExact();
|
||||||
int width = getFontMetrics(getFont()).stringWidth(Integer.toString(lastLine));
|
int width = getFontMetrics(getFont()).stringWidth(Integer.toString(lastLine));
|
||||||
setPreferredSize(new Dimension(width, 0));
|
setPreferredSize(new Dimension(Math.max(16, width), 0));
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +98,9 @@ public class LineNumberDecompilerMarginProvider extends JPanel
|
|||||||
BigInteger endIdx = pixmap.getIndex(visible.y + visible.height);
|
BigInteger endIdx = pixmap.getIndex(visible.y + visible.height);
|
||||||
int ascent = g.getFontMetrics().getMaxAscent();
|
int ascent = g.getFontMetrics().getMaxAscent();
|
||||||
for (BigInteger i = startIdx; i.compareTo(endIdx) <= 0; i = i.add(BigInteger.ONE)) {
|
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);
|
pixmap.getPixel(i) + ascent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -49,7 +49,9 @@ import ghidra.util.task.SwingUpdateManager;
|
|||||||
GoToService.class, NavigationHistoryService.class, ClipboardService.class,
|
GoToService.class, NavigationHistoryService.class, ClipboardService.class,
|
||||||
DataTypeManagerService.class /*, ProgramManager.class */
|
DataTypeManagerService.class /*, ProgramManager.class */
|
||||||
},
|
},
|
||||||
servicesProvided = { DecompilerHighlightService.class },
|
servicesProvided = {
|
||||||
|
DecompilerHighlightService.class, DecompilerMarginService.class
|
||||||
|
},
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class,
|
ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class,
|
||||||
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
|
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
|
||||||
|
|||||||
+70
-9
@@ -21,8 +21,7 @@ import docking.ActionContext;
|
|||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import ghidra.app.context.NavigatableActionContext;
|
import ghidra.app.context.NavigatableActionContext;
|
||||||
import ghidra.app.context.RestrictedAddressSetContext;
|
import ghidra.app.context.RestrictedAddressSetContext;
|
||||||
import ghidra.app.decompiler.ClangToken;
|
import ghidra.app.decompiler.*;
|
||||||
import ghidra.app.decompiler.ClangTokenGroup;
|
|
||||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
@@ -36,15 +35,45 @@ public class DecompilerActionContext extends NavigatableActionContext
|
|||||||
implements RestrictedAddressSetContext {
|
implements RestrictedAddressSetContext {
|
||||||
private final Address functionEntryPoint;
|
private final Address functionEntryPoint;
|
||||||
private final boolean isDecompiling;
|
private final boolean isDecompiling;
|
||||||
|
private final int lineNumber;
|
||||||
|
|
||||||
private ClangToken tokenAtCursor = null;
|
private ClangToken tokenAtCursor = null;
|
||||||
private boolean tokenIsInitialized = false;
|
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,
|
public DecompilerActionContext(DecompilerProvider provider, Address functionEntryPoint,
|
||||||
boolean isDecompiling) {
|
boolean isDecompiling, int lineNumber) {
|
||||||
super(provider, provider);
|
super(provider, provider);
|
||||||
|
if (lineNumber < 0) {
|
||||||
|
throw new IllegalArgumentException("lineNumber must be >= 0. Got " + lineNumber);
|
||||||
|
}
|
||||||
this.functionEntryPoint = functionEntryPoint;
|
this.functionEntryPoint = functionEntryPoint;
|
||||||
this.isDecompiling = isDecompiling;
|
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() {
|
public Address getFunctionEntryPoint() {
|
||||||
@@ -72,6 +101,32 @@ public class DecompilerActionContext extends NavigatableActionContext
|
|||||||
return tokenAtCursor;
|
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() {
|
public DecompilerPanel getDecompilerPanel() {
|
||||||
return getComponentProvider().getDecompilerPanel();
|
return getComponentProvider().getDecompilerPanel();
|
||||||
}
|
}
|
||||||
@@ -98,9 +153,12 @@ public class DecompilerActionContext extends NavigatableActionContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The companion method of {@link #checkActionEnablement(Supplier)}. Decompiler actions
|
* The companion method of {@link #checkActionEnablement(Supplier)}.
|
||||||
* must call this method from their {@link DockingActionIf#actionPerformed(ActionContext)}
|
*
|
||||||
* if they require state from the Decompiler.
|
* <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
|
* @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
|
* The companion method of {@link #performAction(Callback)}.
|
||||||
* this method from their {@link DockingActionIf#isEnabledForContext(ActionContext)} if they
|
*
|
||||||
* require state from the Decompiler.
|
* <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
|
* @param actionBooleanSupplier the action's code to verify its enablement
|
||||||
* @return true if the action should be considered enabled
|
* @return true if the action should be considered enabled
|
||||||
|
|||||||
+2
-2
@@ -206,7 +206,7 @@ public class DecompilerClipboardProvider extends ByteCopier
|
|||||||
endRow = fieldRange.getEnd().getRow();
|
endRow = fieldRange.getEnd().getRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutModel model = provider.getDecompilerPanel().getLayoutModel();
|
LayoutModel model = provider.getDecompilerPanel().getLayoutController();
|
||||||
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
|
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
|
||||||
ClangTextField field = (ClangTextField) layout.getField(0);
|
ClangTextField field = (ClangTextField) layout.getField(0);
|
||||||
int numSpaces = field.getStartX() / spaceCharWidthInPixels;
|
int numSpaces = field.getStartX() / spaceCharWidthInPixels;
|
||||||
@@ -235,7 +235,7 @@ public class DecompilerClipboardProvider extends ByteCopier
|
|||||||
int startRow = fieldRange.getStart().getRow();
|
int startRow = fieldRange.getStart().getRow();
|
||||||
int endRow = fieldRange.getEnd().getRow();
|
int endRow = fieldRange.getEnd().getRow();
|
||||||
|
|
||||||
LayoutModel model = provider.getDecompilerPanel().getLayoutModel();
|
LayoutModel model = provider.getDecompilerPanel().getLayoutController();
|
||||||
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
|
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
|
||||||
ClangTextField field = (ClangTextField) layout.getField(0);
|
ClangTextField field = (ClangTextField) layout.getField(0);
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -197,7 +197,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
|||||||
|
|
||||||
Address entryPoint = function.getEntryPoint();
|
Address entryPoint = function.getEntryPoint();
|
||||||
boolean isDecompiling = controller.isDecompiling();
|
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
|
@Override
|
||||||
@@ -656,14 +658,15 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
|||||||
followUpWorkUpdater.update();
|
followUpWorkUpdater.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DecompilerPanel getDecompilerPanel() {
|
||||||
|
return controller.getDecompilerPanel();
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// methods called from other members
|
// methods called from other members
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
DecompilerPanel getDecompilerPanel() {
|
|
||||||
return controller.getDecompilerPanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// snapshot callback
|
// snapshot callback
|
||||||
public void cloneWindow() {
|
public void cloneWindow() {
|
||||||
DecompilerProvider newProvider = plugin.createNewDisconnectedProvider();
|
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
|
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
|
||||||
// model to finish loading
|
// model to finish loading
|
||||||
|
|
||||||
DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
|
DecompilerActionContext context =
|
||||||
|
new DecompilerActionContext(provider, addr(0x0), false);
|
||||||
performAction(findReferencesAction, context, true);
|
performAction(findReferencesAction, context, true);
|
||||||
|
|
||||||
ThreadedTableModel<?, ?> model = waitForSearchProvider();
|
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
|
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
|
||||||
// model to finish loading
|
// model to finish loading
|
||||||
|
|
||||||
DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
|
DecompilerActionContext context =
|
||||||
|
new DecompilerActionContext(provider, addr(0x0), false);
|
||||||
performAction(findReferencesToAddressAction, context, true);
|
performAction(findReferencesToAddressAction, context, true);
|
||||||
|
|
||||||
ThreadedTableModel<?, ?> model = waitForSearchProvider();
|
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
|
// tricky business - the 'finder' is being run in a thread pool, so we must wait for that
|
||||||
// model to finish loading
|
// model to finish loading
|
||||||
|
|
||||||
DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false);
|
DecompilerActionContext context =
|
||||||
|
new DecompilerActionContext(provider, addr(0x0), false);
|
||||||
performAction(findReferencesToSymbolAction, context, true);
|
performAction(findReferencesToSymbolAction, context, true);
|
||||||
|
|
||||||
ThreadedTableModel<?, ?> model = waitForSearchProvider();
|
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
|
* Simulate the action of "converting" the current token to the given format
|
||||||
|
*
|
||||||
* @param convertType is the given format
|
* @param convertType is the given format
|
||||||
*/
|
*/
|
||||||
private void convertToken(int convertType) {
|
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
|
* Simulate setting an equate on the current token with the given name
|
||||||
|
*
|
||||||
* @param nm is the given name
|
* @param nm is the given name
|
||||||
*/
|
*/
|
||||||
private void convertToken(String nm) {
|
private void convertToken(String nm) {
|
||||||
|
|||||||
Reference in New Issue
Block a user