diff --git a/Ghidra/Debug/Debugger/build.gradle b/Ghidra/Debug/Debugger/build.gradle index 2f3a865dc5..54783c069f 100644 --- a/Ghidra/Debug/Debugger/build.gradle +++ b/Ghidra/Debug/Debugger/build.gradle @@ -28,9 +28,11 @@ dependencies { api project(':Framework-TraceModeling') api project(':Base') api project(':ByteViewer') + api project(':Decompiler') api project(':ProposedUtils') helpPath project(path: ':Base', configuration: 'helpPath') + helpPath project(path: ':Decompiler', configuration: 'helpPath') helpPath project(path: ':ProgramDiff', configuration: 'helpPath') testImplementation project(path: ':Base', configuration: 'testArtifacts') diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 723e3acccf..e2ab6a3cc9 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -23,6 +23,7 @@ src/main/help/help/topics/Debugger/Troubleshooting.html||GHIDRA||||END| src/main/help/help/topics/DebuggerBots/DebuggerBots.html||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerPlaceBreakpointDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/breakpoint-clear.png||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/breakpoint-disable.png||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html index d79889ac73..eca15dcc08 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html @@ -32,6 +32,29 @@ the current target. NOTE: Depending on the connected debugger, locations resulting from a common specification may not be independently manipulated.

+

In the Decompiler

+ + + + + + + +
+ +

When active in the Debugger, the Decompiler will display breakpoints in + its margin. Keep in mind, the relationship between machine instructions and the decompiled code + is not always so simple. One line may correspond to a single address, many addresses, or no + addresses at all. If there is a breakpoint at any address for a given line, then that line will + have a marker indicating the breakpoint's state. If there are multiple, then that state may be + mixed. In most cases, Setting a breakpoint in the decompiler will + suggest a Software Execution breakpoint at the minimum address of the current line. If the + current token is a static variable, then it will suggest a Read/Write breakpoint at the + variable's address. (Note that Read/Write breakpoints are not indicated in the Decompiler's + margin.) The other actions affect all breakpoints on the current line.

+

Actions

The following actions are added to all disassembly listings by the breakpoint marker plugin. diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png new file mode 100644 index 0000000000..b0a0d584d3 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java new file mode 100644 index 0000000000..2921048c83 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java @@ -0,0 +1,140 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.breakpoint; + +import java.awt.*; +import java.awt.event.*; +import java.math.BigInteger; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import javax.swing.JPanel; + +import docking.widgets.fieldpanel.LayoutModel; +import docking.widgets.fieldpanel.listener.IndexMapper; +import docking.widgets.fieldpanel.listener.LayoutModelListener; +import ghidra.app.decompiler.ClangLine; +import ghidra.app.decompiler.component.margin.DecompilerMarginProvider; +import ghidra.app.decompiler.component.margin.LayoutPixelIndexMap; +import ghidra.app.services.LogicalBreakpoint; +import ghidra.app.services.LogicalBreakpoint.State; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; + +public class BreakpointsDecompilerMarginProvider extends JPanel + implements DecompilerMarginProvider, LayoutModelListener { + + private Program program; + private LayoutModel model; + private LayoutPixelIndexMap pixmap; + + private final DebuggerBreakpointMarkerPlugin plugin; + + public BreakpointsDecompilerMarginProvider(DebuggerBreakpointMarkerPlugin plugin) { + this.plugin = plugin; + setPreferredSize(new Dimension(16, 0)); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + doToggleBreakpoint(e); + } + }); + } + + @Override + public void setProgram(Program program, LayoutModel model, LayoutPixelIndexMap pixmap) { + this.program = program; + setLayoutManager(model); + this.pixmap = pixmap; + repaint(); + } + + private void setLayoutManager(LayoutModel model) { + if (this.model == model) { + return; + } + if (this.model != null) { + this.model.removeLayoutModelListener(this); + } + this.model = model; + if (this.model != null) { + this.model.addLayoutModelListener(this); + } + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void modelSizeChanged(IndexMapper indexMapper) { + repaint(); + } + + @Override + public void dataChanged(BigInteger start, BigInteger end) { + repaint(); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + if (plugin.breakpointService == null) { + return; + } + Rectangle visible = getVisibleRect(); + BigInteger startIdx = pixmap.getIndex(visible.y); + BigInteger endIdx = pixmap.getIndex(visible.y + visible.height); + + List lines = plugin.decompilerMarginService.getDecompilerPanel().getLines(); + for (BigInteger index = startIdx; index.compareTo(endIdx) <= 0; index = + index.add(BigInteger.ONE)) { + int i = index.intValue(); + if (i >= lines.size()) { + continue; + } + ClangLine line = lines.get(i); + List locs = + DebuggerBreakpointMarkerPlugin.getLocationsFromLine(program, line); + State state = plugin.computeState(locs); + if (state.icon != null) { + state.icon.paintIcon(this, g, 0, pixmap.getPixel(index)); + } + } + } + + private void doToggleBreakpoint(MouseEvent e) { + if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { + int i = pixmap.getIndex(e.getY()).intValue(); + List lines = plugin.decompilerMarginService.getDecompilerPanel().getLines(); + List locs = + DebuggerBreakpointMarkerPlugin.nearestLocationsToLine(program, i, lines); + if (locs == null || locs.isEmpty()) { + return; + } + Set col = plugin.collectBreakpoints(locs); + plugin.breakpointService.toggleBreakpointsAt(col, locs.get(0), () -> { + plugin.placeBreakpointDialog.prompt(plugin.getTool(), plugin.breakpointService, + "Set breakpoint", locs.get(0), 1, Set.of(TraceBreakpointKind.SW_EXECUTE), ""); + // Not great, but I'm not sticking around for the dialog + return CompletableFuture.completedFuture(Set.of()); + }); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java index c3d1c31f1b..7e309a8998 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java @@ -28,6 +28,8 @@ import docking.Tool; import docking.action.*; import docking.actions.PopupActionProvider; import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.decompiler.*; +import ghidra.app.decompiler.component.margin.LineNumberDecompilerMarginProvider; import ghidra.app.events.ProgramClosedPluginEvent; import ghidra.app.events.ProgramOpenedPluginEvent; import ghidra.app.plugin.PluginCategoryNames; @@ -36,6 +38,7 @@ import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; import ghidra.app.services.*; import ghidra.app.services.LogicalBreakpoint.State; import ghidra.app.util.viewer.listingpanel.MarkerClickedListener; @@ -49,6 +52,8 @@ import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; import ghidra.program.util.*; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceLocation; @@ -76,44 +81,22 @@ import ghidra.util.Msg; public class DebuggerBreakpointMarkerPlugin extends Plugin implements PopupActionProvider { - protected static Address computeAddressFromContext(ActionContext context) { + protected static ProgramLocation getSingleLocationFromContext(ActionContext context) { if (context == null) { return null; } - if (context instanceof ProgramLocationActionContext) { - ProgramLocationActionContext ctx = (ProgramLocationActionContext) context; - if (ctx.hasSelection()) { - ProgramSelection sel = ctx.getSelection(); - AddressRange range = sel.getRangeContaining(ctx.getAddress()); - if (range != null) { - return range.getMinAddress(); + if (context instanceof DecompilerActionContext ctx) { + // Use the token here, not the line + if (!(ctx.getSourceComponent() instanceof LineNumberDecompilerMarginProvider) && + ctx.getTokenAtCursor() instanceof ClangVariableToken tok) { + Varnode varnode = tok.getVarnode(); + Address address = varnode == null ? null : varnode.getAddress(); + if (address != null && address.isMemoryAddress()) { + return new ProgramLocation(ctx.getProgram(), address); } } - return ctx.getAddress(); } - Object obj = context.getContextObject(); - if (obj instanceof MarkerLocation) { - MarkerLocation ml = (MarkerLocation) obj; - return ml.getAddr(); - } - return null; - } - - /** - * Attempt to derive a location from the given context - * - *

- * Currently, this supports {@link ProgramLocationActionContext} and {@link MarkerLocation}. - * - * @param context a possible location context - * @return the program location, or {@code null} - */ - protected static ProgramLocation getLocationFromContext(ActionContext context) { - if (context == null) { - return null; - } - if (context instanceof ProgramLocationActionContext) { - ProgramLocationActionContext ctx = (ProgramLocationActionContext) context; + if (context instanceof ProgramLocationActionContext ctx) { if (ctx.hasSelection()) { ProgramSelection sel = ctx.getSelection(); AddressRange range = sel.getRangeContaining(ctx.getAddress()); @@ -124,13 +107,100 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin return ctx.getLocation(); } Object obj = context.getContextObject(); - if (obj instanceof MarkerLocation) { - MarkerLocation ml = (MarkerLocation) obj; + if (obj instanceof MarkerLocation ml) { return new ProgramLocation(ml.getProgram(), ml.getAddr()); } return null; } + protected static List

getAddressesFromLine(ClangLine line) { + Set
result = new TreeSet<>(); + for (int i = 0; i < line.getNumTokens(); i++) { + ClangToken tok = line.getToken(i); + if (tok instanceof ClangLabelToken) { + continue; + } + if (tok instanceof ClangCommentToken) { + /* + * Comment tokens should never have an address anyway, but sometimes the decompiler + * assigns the entry address to a warning comment that precedes the function header. + * This will filter that oddity. + */ + continue; + } + // Don't let line-wrapped calls display one breakpoint on all lines + // NOTE: The call itself will be represented by the ClangFuncNameToken + if (tok instanceof ClangVariableToken varTok && + varTok.getPcodeOp() != null && varTok.getPcodeOp().getOpcode() == PcodeOp.CALL) { + continue; + } + if (tok instanceof ClangOpToken opTok && + opTok.getPcodeOp() != null && opTok.getPcodeOp().getOpcode() == PcodeOp.CALL) { + continue; + } + // NOTE: I've seen no case where max != min + Address min = tok.getMinAddress(); + if (min == null) { + continue; + } + result.add(min); + } + return List.copyOf(result); + } + + protected static List getLocationsFromLine(Program program, ClangLine line) { + List result = new ArrayList<>(); + for (Address addr : getAddressesFromLine(line)) { + result.add(new ProgramLocation(program, addr)); + } + return result; + } + + /** + * Find the nearest line, only looking forward, having an address and get its addresses wrapped + * in program locations + * + * @param program the current program, for generating program locations + * @param index the index of the first line to consider, the current/context line + * @param lines the complete list of decompiled source lines of the current function + * @return the locations, or null if no such line is found + */ + protected static List nearestLocationsToLine(Program program, int index, + List lines) { + if (index < 0) { + return null; + } + for (int n = index; n < lines.size(); n++) { + ClangLine clangLine = lines.get(n); + List locs = getLocationsFromLine(program, clangLine); + if (locs != null && !locs.isEmpty()) { + return locs; + } + } + return null; + } + + /** + * Attempt to derive one or more locations from the given context + * + * @param context a possible location context + * @return the program location, or {@code null} + */ + protected static List getLocationsFromContext(ActionContext context) { + if (context == null) { + return null; + } + if (context instanceof DecompilerActionContext ctx) { + int lineNumber = ctx.getLineNumber(); + // Return even if null, to prevent token from being used + // Using the token might surprise the user, esp., if it's not on screen + return nearestLocationsToLine(ctx.getProgram(), lineNumber - 1, + ctx.getDecompilerPanel().getLines()); + } + ProgramLocation loc = getSingleLocationFromContext(context); + return loc == null ? null : List.of(loc); + } + protected static long computeLengthFromContext(ActionContext context) { if (context == null) { return 1; @@ -153,15 +223,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin } protected static boolean contextHasLocation(ActionContext context) { - return getLocationFromContext(context) != null; + List locs = getLocationsFromContext(context); + return locs != null && !locs.isEmpty(); } protected static Trace getTraceFromContext(ActionContext context) { - ProgramLocation loc = getLocationFromContext(context); - if (loc == null) { + List locs = getLocationsFromContext(context); + if (locs == null || locs.isEmpty()) { return null; } - Program progOrView = loc.getProgram(); + Program progOrView = locs.get(0).getProgram(); if (progOrView instanceof TraceProgramView) { TraceProgramView view = (TraceProgramView) progOrView; return view.getTrace(); @@ -190,7 +261,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin } long length = computeLengthFromContext(ctx); if (length == 1) { - ProgramLocation loc = getLocationFromContext(ctx); + ProgramLocation loc = getSingleLocationFromContext(ctx); Listing listing = loc.getProgram().getListing(); CodeUnit cu = listing.getCodeUnitContaining(loc.getAddress()); if (cu instanceof Instruction) { @@ -218,41 +289,23 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin } protected Color colorForState(State state) { - if (state.isEnabled()) { - if (state.isEffective()) { - return breakpointEnabledMarkerColor; - } - else { - return breakpointIneffEnMarkerColor; - } - } - else { - if (state.isEffective()) { - return breakpointDisabledMarkerColor; - } - else { - return breakpointIneffDisMarkerColor; - } - } + return state.isEnabled() + ? state.isEffective() + ? breakpointEnabledMarkerColor + : breakpointIneffEnMarkerColor + : state.isEffective() + ? breakpointDisabledMarkerColor + : breakpointIneffDisMarkerColor; } protected boolean stateColorsBackground(State state) { - if (state.isEnabled()) { - if (state.isEffective()) { - return breakpointEnabledColoringBackground; - } - else { - return breakpointIneffEnColoringBackground; - } - } - else { - if (state.isEffective()) { - return breakpointDisabledColoringBackground; - } - else { - return breakpointIneffDisColoringBackground; - } - } + return state.isEnabled() + ? state.isEffective() + ? breakpointEnabledColoringBackground + : breakpointIneffEnColoringBackground + : state.isEffective() + ? breakpointDisabledColoringBackground + : breakpointIneffDisColoringBackground; } /** @@ -432,17 +485,18 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin return breakpoint.computeState(); } - /** - * It seems the purpose of this was to omit the program mode from the dynamic listing. I don't - * think we need that anymore, so I've just delegated to exactly the same as the breakpoint - * service, which will include the program mode, if applicable. TODO: Remove this and just call - * the service's version directly? - * - * @param loc - * @return - */ - protected State computeState(ProgramLocation loc) { - return breakpointService.computeState(loc); + protected Set collectBreakpoints(Collection locs) { + return locs.stream() + .flatMap(l -> breakpointService.getBreakpointsAt(l).stream()) + .collect(Collectors.toSet()); + } + + protected State computeState(List locs) { + if (locs.isEmpty()) { + return State.NONE; + } + Set col = collectBreakpoints(locs); + return breakpointService.computeState(col, locs.get(0)); } protected class ToggleBreakpointAction extends AbstractToggleBreakpointAction { @@ -489,7 +543,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return; } - ProgramLocation location = getLocationFromContext(context); + ProgramLocation location = getSingleLocationFromContext(context); long length = computeDefaultLength(context, kinds); placeBreakpointDialog.prompt(tool, breakpointService, NAME, location, length, kinds, ""); @@ -500,7 +554,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return false; } - ProgramLocation loc = getLocationFromContext(context); + ProgramLocation loc = getSingleLocationFromContext(context); if (!(loc.getProgram() instanceof TraceProgramView)) { return true; } @@ -530,8 +584,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return; } - ProgramLocation location = getLocationFromContext(context); - Set col = breakpointService.getBreakpointsAt(location); + List locs = getLocationsFromContext(context); + Set col = collectBreakpoints(locs); Trace trace = getTraceFromContext(context); String status = breakpointService.generateStatusEnable(col, trace); if (status != null) { @@ -548,8 +602,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return false; } - ProgramLocation location = getLocationFromContext(context); - State state = computeState(location); + List locs = getLocationsFromContext(context); + State state = computeState(locs); if (state == State.ENABLED || state == State.NONE) { return false; } @@ -572,8 +626,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return; } - ProgramLocation location = getLocationFromContext(context); - Set col = breakpointService.getBreakpointsAt(location); + List locs = getLocationsFromContext(context); + Set col = collectBreakpoints(locs); breakpointService.disableAll(col, getTraceFromContext(context)).exceptionally(ex -> { breakpointError(NAME, "Could not disable breakpoint", ex); return null; @@ -585,8 +639,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return false; } - ProgramLocation location = getLocationFromContext(context); - State state = computeState(location); + List locs = getLocationsFromContext(context); + State state = computeState(locs); if (state == State.DISABLED || state == State.NONE) { return false; } @@ -611,8 +665,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return; } - ProgramLocation location = getLocationFromContext(context); - Set col = breakpointService.getBreakpointsAt(location); + List locs = getLocationsFromContext(context); + Set col = collectBreakpoints(locs); breakpointService.deleteAll(col, getTraceFromContext(context)).exceptionally(ex -> { breakpointError(NAME, "Could not delete breakpoint", ex); return null; @@ -624,8 +678,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (!contextCanManipulateBreakpoints(context)) { return false; } - ProgramLocation location = getLocationFromContext(context); - State state = computeState(location); + List locs = getLocationsFromContext(context); + State state = computeState(locs); if (state == State.NONE) { return false; } @@ -636,7 +690,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin // @AutoServiceConsumed via method private MarkerService markerService; // @AutoServiceConsumed via method - private DebuggerLogicalBreakpointService breakpointService; + DebuggerLogicalBreakpointService breakpointService; @AutoServiceConsumed private DebuggerModelService modelService; @AutoServiceConsumed @@ -645,6 +699,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin private DebuggerTraceManagerService traceManager; @AutoServiceConsumed private DebuggerConsoleService consoleService; + // @AutoServiceConsumed via method + DecompilerMarginService decompilerMarginService; @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; @@ -730,8 +786,11 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin DebuggerPlaceBreakpointDialog placeBreakpointDialog = new DebuggerPlaceBreakpointDialog(); + BreakpointsDecompilerMarginProvider decompilerMarginProvider; + public DebuggerBreakpointMarkerPlugin(PluginTool tool) { super(tool); + this.decompilerMarginProvider = new BreakpointsDecompilerMarginProvider(this); this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); this.autoOptionsWiring = AutoOptions.wireOptions(this); @@ -824,12 +883,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (mappingService == null || modelService == null) { return Set.of(); } - ProgramLocation loc = getLocationFromContext(context); + ProgramLocation loc = getSingleLocationFromContext(context); if (loc == null || loc.getProgram() instanceof TraceProgramView) { return Set.of(); } + Set mappedLocs = mappingService.getOpenMappedLocations(loc); + if (mappedLocs == null || mappedLocs.isEmpty()) { + return Set.of(); + } Set result = new HashSet<>(); - for (TraceLocation tloc : mappingService.getOpenMappedLocations(loc)) { + for (TraceLocation tloc : mappedLocs) { TraceRecorder rec = modelService.getRecorder(tloc.getTrace()); if (rec != null) { result.add(rec); @@ -870,15 +933,17 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin if (breakpointService == null) { return; } - ProgramLocation loc = getLocationFromContext(context); - if (loc == null) { + List locs = getLocationsFromContext(context); + if (locs == null || locs.isEmpty()) { return; } - String status = breakpointService.generateStatusToggleAt(loc); + Set col = collectBreakpoints(locs); + ProgramLocation loc = locs.get(0); + String status = breakpointService.generateStatusToggleAt(col, loc); if (status != null) { tool.setStatusInfo(status, true); } - breakpointService.toggleBreakpointsAt(loc, () -> { + breakpointService.toggleBreakpointsAt(col, loc, () -> { Set supported = getSupportedKindsFromContext(context); if (supported.isEmpty()) { breakpointError(title, "It seems this target does not support breakpoints."); @@ -886,7 +951,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin } Set kinds = computeDefaultKinds(context, supported); long length = computeDefaultLength(context, kinds); - placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds, ""); + placeBreakpointDialog.prompt(tool, breakpointService, title, loc, length, kinds, + ""); // Not great, but I'm not sticking around for the dialog return CompletableFuture.completedFuture(Set.of()); }).exceptionally(ex -> { @@ -985,6 +1051,17 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin } } + @AutoServiceConsumed + private void setDecompilerMarginService(DecompilerMarginService decompilerMarginService) { + if (this.decompilerMarginService != null) { + this.decompilerMarginService.removeMarginProvider(decompilerMarginProvider); + } + this.decompilerMarginService = decompilerMarginService; + if (this.decompilerMarginService != null) { + this.decompilerMarginService.addMarginProvider(decompilerMarginProvider); + } + } + protected void createActions() { actionSetSoftwareBreakpoint = new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE)); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java index db59ba4989..33f193f5fb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java @@ -583,6 +583,10 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } public TraceLocation toDynamicLocation(ProgramLocation loc) { + if (mappingService == null) { + // Must be shutting down + return null; + } return mappingService.getOpenMappedLocation(trace, loc, recorder.getSnap()); } } @@ -1216,8 +1220,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } @Override - public String generateStatusToggleAt(ProgramLocation loc) { - Set bs = getBreakpointsAt(loc); + public String generateStatusToggleAt(Set bs, ProgramLocation loc) { if (bs == null || bs.isEmpty()) { return null; } @@ -1242,9 +1245,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } @Override - public CompletableFuture> toggleBreakpointsAt(ProgramLocation loc, - Supplier>> placer) { - Set bs = getBreakpointsAt(loc); + public CompletableFuture> toggleBreakpointsAt(Set bs, + ProgramLocation loc, Supplier>> placer) { if (bs == null || bs.isEmpty()) { return placer.get(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java index dfd8947e6b..836162aef5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java @@ -370,20 +370,47 @@ public interface DebuggerLogicalBreakpointService { */ CompletableFuture deleteLocs(Collection col); + /** + * Generate an informational message when toggling the breakpoints + * + *

+ * This works in the same manner as {@link #generateStatusEnable(Collection, Trace)}, except it + * is for toggling breakpoints. If the breakpoint set is empty, this should return null, since + * the usual behavior in that case is to prompt to place a new breakpoint. + * + * @see #generateStatusEnable(Collection, Trace)) + * @param loc a representative location + * @return the status message, or null + */ + String generateStatusToggleAt(Set bs, ProgramLocation loc); + /** * Generate an informational message when toggling the breakpoints at the given location * *

- * This works in the same manner as {@link #generateStatusEnable(Collection)}, except it is for - * toggling breakpoints at a given location. If there are no breakpoints at the location, this - * should return null, since the usual behavior in that case is to prompt to place a new + * This works in the same manner as {@link #generateStatusEnable(Collection, Trace))}, except it + * is for toggling breakpoints at a given location. If there are no breakpoints at the location, + * this should return null, since the usual behavior in that case is to prompt to place a new * breakpoint. * * @see #generateStatusEnable(Collection) * @param loc the location * @return the status message, or null */ - String generateStatusToggleAt(ProgramLocation loc); + default String generateStatusToggleAt(ProgramLocation loc) { + return generateStatusToggleAt(getBreakpointsAt(loc), loc); + } + + /** + * Toggle the breakpoints at the given location + * + * @param bs the set of breakpoints to toggle + * @param location the location + * @param placer if the breakpoint set is empty, a routine for placing a breakpoint + * @return a future which completes when the command has been processed + */ + CompletableFuture> toggleBreakpointsAt(Set bs, + ProgramLocation location, Supplier>> placer); /** * Toggle the breakpoints at the given location @@ -392,6 +419,8 @@ public interface DebuggerLogicalBreakpointService { * @param placer if there are no breakpoints, a routine for placing a breakpoint * @return a future which completes when the command has been processed */ - CompletableFuture> toggleBreakpointsAt(ProgramLocation location, - Supplier>> placer); + default CompletableFuture> toggleBreakpointsAt(ProgramLocation location, + Supplier>> placer) { + return toggleBreakpointsAt(getBreakpointsAt(location), location, placer); + } } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java index 5e02f231e0..fb0e04e91f 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginScreenShots.java @@ -30,6 +30,8 @@ import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpoint import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerProvider; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.services.*; import ghidra.app.services.LogicalBreakpoint.State; @@ -42,6 +44,7 @@ import ghidra.trace.model.DefaultTraceLocation; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.util.Msg; +import ghidra.util.Swing; import ghidra.util.database.UndoableTransaction; import ghidra.util.task.TaskMonitor; import help.screenshot.GhidraScreenShotGenerator; @@ -130,6 +133,44 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG captureProviderWithScreenShot(listing); } + @Test + public void testCaptureDebuggerDecompilerBreakpointMargin() throws Throwable { + mb.createTestModel(); + modelService.addModel(mb.testModel); + mb.createTestProcessesAndThreads(); + TestDebuggerTargetTraceMapper mapper = new TestDebuggerTargetTraceMapper(mb.testProcess1); + TraceRecorder recorder = + modelService.recordTarget(mb.testProcess1, mapper, ActionSource.AUTOMATIC); + Trace trace = recorder.getTrace(); + + traceManager.openTrace(trace); + traceManager.activateTrace(trace); + + tool.getProject() + .getProjectData() + .getRootFolder() + .createFile("WinHelloCPP", program, TaskMonitor.DUMMY); + + try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add Mapping")) { + mappingService.addIdentityMapping(trace, program, Range.atLeast(0L), true); + } + waitForValue(() -> mappingService.getOpenMappedLocation( + new DefaultTraceLocation(trace, null, Range.singleton(0L), mb.addr(0x00401070)))); + + Msg.debug(this, "Placing breakpoint"); + breakpointService.placeBreakpointAt(program, addr(program, 0x00401070), 1, + Set.of(TraceBreakpointKind.SW_EXECUTE), ""); + + addPlugin(tool, DecompilePlugin.class); + + DecompilerProvider decompilerProvider = waitForComponentProvider(DecompilerProvider.class); + Swing.runNow(() -> tool.showComponentProvider(decompilerProvider, true)); + goTo(tool, program, addr(program, 0x00401070)); + waitForCondition(() -> decompilerProvider.getDecompilerPanel().getLines().size() > 4); + + captureIsolatedProvider(decompilerProvider, 500, 700); + } + @Test public void testCaptureDebuggerPlaceBreakpointDialog() throws Throwable { runSwing( diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java index fbdbbda177..7e11020f3d 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java @@ -15,8 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.breakpoint; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.awt.*; import java.awt.event.MouseEvent; @@ -41,11 +40,15 @@ import docking.widgets.fieldpanel.FieldPanel; import generic.Unique; import generic.test.category.NightlyCategory; import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.decompiler.component.DecompileData; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerProvider; import ghidra.app.services.*; import ghidra.app.services.LogicalBreakpoint.State; import ghidra.app.util.viewer.listingpanel.ListingPanel; @@ -53,11 +56,12 @@ import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpecContainer; import ghidra.framework.store.LockException; import ghidra.program.disassemble.Disassembler; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.address.*; import ghidra.program.model.data.ByteDataType; +import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryConflictException; +import ghidra.program.model.symbol.SourceType; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.DefaultTraceLocation; import ghidra.trace.model.Trace; @@ -85,6 +89,10 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu protected DebuggerStaticMappingService mappingService; protected MarkerService markerService; + protected Function function; + protected Address entry; + protected DecompilerProvider decompilerProvider; + @Before public void setUpBreakpointMarkerPluginTest() throws Exception { breakpointMarkerPlugin = addPlugin(tool, DebuggerBreakpointMarkerPlugin.class); @@ -96,6 +104,28 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu markerService = tool.getService(MarkerService.class); } + protected void prepareDecompiler() throws Throwable { + addPlugin(tool, DecompilePlugin.class); + decompilerProvider = waitForComponentProvider(DecompilerProvider.class); + runSwing(() -> tool.showComponentProvider(decompilerProvider, true)); + createProgram(); + programManager.openProgram(program); + waitForSwing(); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Create function")) { + entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, + TaskMonitor.DUMMY, false); + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, new AddressSet(entry)); + + function = program.getFunctionManager() + .createFunction("test", entry, + new AddressSet(entry), SourceType.USER_DEFINED); + } + } + protected void addLiveMemoryAndBreakpoint(TraceRecorder recorder) throws InterruptedException, ExecutionException, TimeoutException { mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx"); @@ -704,4 +734,85 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu waitForPass(() -> assertEquals(State.NONE, lb.computeStateForTrace(trace))); // TODO: Same test but with multiple traces } + + protected static DecompileData mockData(DecompileResults decompileResults) { + Function function = decompileResults.getFunction(); + Program program = function.getProgram(); + ProgramLocation location = new ProgramLocation(program, function.getEntryPoint()); + return new DecompileData(program, function, location, decompileResults, "", null, null); + } + + @Test + public void testToggleBreakpointDecompilerNoAddress() throws Throwable { + prepareDecompiler(); + DecompileResults results = new MockDecompileResults(function) { + { + root(function(token("token_no_addr"))); + } + }; + runSwing(() -> decompilerProvider.getController().setDecompileData(mockData(results))); + + runSwing(() -> assertFalse(breakpointMarkerPlugin.actionToggleBreakpoint + .isEnabledForContext(decompilerProvider.getActionContext(null)))); + } + + @Test + public void testToggleBreakpointDecompilerOneAddress() throws Throwable { + prepareDecompiler(); + DecompileResults results = new MockDecompileResults(function) { + { + root(function(token("token_with_addr", entry))); + } + }; + runSwing(() -> decompilerProvider.getController().setDecompileData(mockData(results))); + + performEnabledAction(decompilerProvider, breakpointMarkerPlugin.actionToggleBreakpoint, + false); + DebuggerPlaceBreakpointDialog dialog = + waitForDialogComponent(DebuggerPlaceBreakpointDialog.class); + runSwing(() -> dialog.okCallback()); + waitForPass(() -> { + LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); + assertEquals(State.INEFFECTIVE_ENABLED, lb.computeState()); + assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), lb.getKinds()); + }); + + performEnabledAction(decompilerProvider, breakpointMarkerPlugin.actionToggleBreakpoint, + true); + waitForPass(() -> { + LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); + assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState()); + assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), lb.getKinds()); + }); + } + + @Test + public void testToggleBreakpointDecompilerTwoAddresses() throws Throwable { + prepareDecompiler(); + DecompileResults results = new MockDecompileResults(function) { + { + root(function(token("token1_with_addr", entry), + token("token2_with_addr", entry.add(2)))); + } + }; + runSwing(() -> decompilerProvider.getController().setDecompileData(mockData(results))); + + waitOn(breakpointService.placeBreakpointAt(program, entry, 1, + Set.of(TraceBreakpointKind.SW_EXECUTE), "")); + waitOn(breakpointService.placeBreakpointAt(program, entry.add(2), 1, + Set.of(TraceBreakpointKind.SW_EXECUTE), "")); + waitForPass(() -> { + assertEquals(2, breakpointService.getAllBreakpoints().size()); + }); + + performEnabledAction(decompilerProvider, breakpointMarkerPlugin.actionToggleBreakpoint, + true); + waitForPass(() -> { + Set all = breakpointService.getAllBreakpoints(); + assertEquals(2, all.size()); + for (LogicalBreakpoint lb : all) { + assertEquals(State.INEFFECTIVE_DISABLED, lb.computeState()); + } + }); + } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java new file mode 100644 index 0000000000..8a573e3bc3 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java @@ -0,0 +1,133 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.breakpoint; + +import java.util.List; + +import ghidra.app.decompiler.*; +import ghidra.app.decompiler.DecompileProcess.DisposeState; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; + +public class MockDecompileResults extends DecompileResults { + private ClangTokenGroup root; + + public interface ClangBuilder { + T build(ClangTokenGroup parent); + + default T build() { + return build(null); + } + } + + public static class MockToken extends ClangToken { + private final Address address; + + public MockToken(ClangNode parent, String txt, Address address) { + super(parent, txt); + this.address = address; + } + + public MockToken(ClangNode parent, String txt) { + this(parent, txt, null); + } + + @Override + public Address getMinAddress() { + return address; + } + + @Override + public Address getMaxAddress() { + return address; + } + } + + public static class TokenBuilder implements ClangBuilder { + private final String text; + private final Address address; + + public TokenBuilder(String text, Address address) { + this.text = text; + this.address = address; + } + + public TokenBuilder(String text) { + this(text, null); + } + + @Override + public ClangToken build(ClangTokenGroup parent) { + return new MockToken(parent, text, address); + } + } + + public static class GroupBuilder implements ClangBuilder { + private final List> children; + + public GroupBuilder(ClangBuilder... children) { + this.children = List.of(children); + } + + protected ClangTokenGroup newGroup(ClangTokenGroup parent) { + return new ClangTokenGroup(parent); + } + + @Override + public ClangTokenGroup build(ClangTokenGroup parent) { + ClangTokenGroup group = newGroup(parent); + for (ClangBuilder child : children) { + group.AddTokenGroup(child.build(group)); + } + return group; + } + } + + public MockDecompileResults(Function function) { + super(function, function.getProgram().getLanguage(), + function.getProgram().getCompilerSpec(), null, "", null, DisposeState.NOT_DISPOSED); + } + + @Override + public ClangTokenGroup getCCodeMarkup() { + return root; + } + + protected void root(GroupBuilder group) { + root = group.build(); + } + + protected GroupBuilder function(ClangBuilder... children) { + return new GroupBuilder(children) { + @Override + protected ClangTokenGroup newGroup(ClangTokenGroup parent) { + return new ClangFunction(parent, null); + } + }; + } + + protected GroupBuilder group(ClangBuilder... children) { + return new GroupBuilder(children); + } + + protected TokenBuilder token(String text) { + return new TokenBuilder(text); + } + + protected TokenBuilder token(String text, Address address) { + return new TokenBuilder(text, address); + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java index b31e769738..e9188f0cc5 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangFunction.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,23 +19,28 @@ * To change the template for this generated file go to * Window>Preferences>Java>Code Generation>Code and Comments */ - + package ghidra.app.decompiler; import ghidra.program.model.pcode.HighFunction; /** - * - * - * All the fields making up one function in the display + * All the tokens making up one function in the display */ public class ClangFunction extends ClangTokenGroup { - private HighFunction hfunc; - - public ClangFunction(ClangNode par,HighFunction hf) { super(par); hfunc = hf; } - @Override - public ClangFunction getClangFunction() { return this; } - public HighFunction getHighFunction() { return hfunc; } -} - + private final HighFunction hfunc; + public ClangFunction(ClangNode parent, HighFunction hfunc) { + super(parent); + this.hfunc = hfunc; + } + + @Override + public ClangFunction getClangFunction() { + return this; + } + + public HighFunction getHighFunction() { + return hfunc; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java index 6309c1d448..2fbf530c18 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangTokenGroup.java @@ -49,9 +49,9 @@ public class ClangTokenGroup implements ClangNode, Iterable { return maxaddress; } - public void AddTokenGroup(Object obj) { - Address minaddr = ((ClangNode) obj).getMinAddress(); - Address maxaddr = ((ClangNode) obj).getMaxAddress(); + public void AddTokenGroup(ClangNode obj) { + Address minaddr = obj.getMinAddress(); + Address maxaddr = obj.getMaxAddress(); if (minaddr != null) { if (minaddress == null) { @@ -69,7 +69,7 @@ public class ClangTokenGroup implements ClangNode, Iterable { maxaddress = maxaddr; } } - tokgroup.add((ClangNode) obj); + tokgroup.add(obj); } @Override diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java index 4dc074aded..f2bc5271eb 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerMarginService.java @@ -15,6 +15,7 @@ */ package ghidra.app.decompiler; +import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.decompiler.component.margin.DecompilerMarginProvider; /** @@ -34,4 +35,11 @@ public interface DecompilerMarginService { * @param provider the margin provider */ void removeMarginProvider(DecompilerMarginProvider provider); + + /** + * Get the panel associated with this margin + * + * @return the panel + */ + DecompilerPanel getDecompilerPanel(); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java index d153587ba8..c77bea769c 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java @@ -82,8 +82,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter { clearHighlights(); - ClangLayoutController layoutModel = - (ClangLayoutController) decompilerPanel.getLayoutModel(); + ClangLayoutController layoutModel = decompilerPanel.getLayoutController(); ClangTokenGroup root = layoutModel.getRoot(); Map highlights = new HashMap<>(); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index 6ece053a8a..a37dadf9c6 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -69,7 +69,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field private LineNumberDecompilerMarginProvider lineNumbersMargin; private final DecompilerFieldPanel fieldPanel; - private ClangLayoutController layoutMgr; + private ClangLayoutController layoutController; private final IndexedScrollPane scroller; private final JComponent taskMonitorComponent; @@ -114,8 +114,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } hlFactory = new SearchHighlightFactory(); - layoutMgr = new ClangLayoutController(options, this, metrics, hlFactory); - fieldPanel = new DecompilerFieldPanel(layoutMgr); + layoutController = new ClangLayoutController(options, this, metrics, hlFactory); + fieldPanel = new DecompilerFieldPanel(layoutController); setBackground(options.getCodeViewerBackgroundColor()); scroller = new IndexedScrollPane(fieldPanel); @@ -144,11 +144,11 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public List getLines() { - return layoutMgr.getLines(); + return layoutController.getLines(); } public List getFields() { - return Arrays.asList(layoutMgr.getFields()); + return Arrays.asList(layoutController.getFields()); } public FieldPanel getFieldPanel() { @@ -265,12 +265,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public void addHighlights(Set varnodes, ColorProvider colorProvider) { - ClangTokenGroup root = layoutMgr.getRoot(); + ClangTokenGroup root = layoutController.getRoot(); highlightController.addPrimaryHighlights(root, colorProvider); } public void addHighlights(Set ops, Color hlColor) { - ClangTokenGroup root = layoutMgr.getRoot(); + ClangTokenGroup root = layoutController.getRoot(); highlightController.addPrimaryHighlights(root, ops, hlColor); } @@ -395,7 +395,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field * @param decompileData the new data */ void setDecompileData(DecompileData decompileData) { - if (layoutMgr == null) { + if (layoutController == null) { // we've been disposed! return; } @@ -404,14 +404,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field this.decompileData = decompileData; Function function = decompileData.getFunction(); if (decompileData.hasDecompileResults()) { - layoutMgr.buildLayouts(function, decompileData.getCCodeMarkup(), null, true); + layoutController.buildLayouts(function, decompileData.getCCodeMarkup(), null, true); if (decompileData.getDebugFile() != null) { controller.setStatusMessage( "Debug file generated: " + decompileData.getDebugFile().getAbsolutePath()); } } else { - layoutMgr.buildLayouts(null, null, decompileData.getErrorMessage(), true); + layoutController.buildLayouts(null, null, decompileData.getErrorMessage(), true); } setLocation(oldData, decompileData); @@ -475,8 +475,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } } - public LayoutModel getLayoutModel() { - return layoutMgr; + public ClangLayoutController getLayoutController() { + return layoutController; } public boolean containsLocation(ProgramLocation location) { @@ -520,7 +520,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return; } - List tokens = DecompilerUtils.getTokensFromView(layoutMgr.getFields(), address); + List tokens = DecompilerUtils.getTokensFromView(layoutController.getFields(), address); goToBeginningOfLine(tokens); } @@ -535,7 +535,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return false; } - List lines = layoutMgr.getLines(); + List lines = layoutController.getLines(); ClangLine signatureLine = getFunctionSignatureLine(lines); if (signatureLine == null) { return false; // can happen when there is no function decompiled @@ -570,7 +570,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return; } - int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields()); + int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutController.getFields()); if (firstLineNumber != -1) { fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false); } @@ -629,7 +629,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field fieldSelection = new FieldSelection(); } else { - List tokens = DecompilerUtils.getTokens(layoutMgr.getRoot(), selection); + List tokens = DecompilerUtils.getTokens(layoutController.getRoot(), selection); fieldSelection = DecompilerUtils.getFieldSelection(tokens); } fieldPanel.setSelection(fieldSelection); @@ -652,7 +652,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field public void dispose() { setDecompileData(new EmptyDecompileData("Disposed")); - layoutMgr = null; + layoutController = null; decompilerHoverProvider.dispose(); highlighCursorUpdater.dispose(); highlightController.dispose(); @@ -771,7 +771,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field ClangNode node = token.Parent(); if (node instanceof ClangStatement) { // check for a goto label - ClangTokenGroup root = layoutMgr.getRoot(); + ClangTokenGroup root = layoutController.getRoot(); ClangLabelToken destination = DecompilerUtils.getGoToTargetToken(root, token); if (destination != null) { goToToken(destination); @@ -909,7 +909,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } if (trigger != EventTrigger.API_CALL) { Program program = decompileData.getProgram(); - Field[] lines = layoutMgr.getFields(); + Field[] lines = layoutController.getFields(); List tokenList = DecompilerUtils.getTokensInSelection(selection, lines); AddressSpace functionSpace = decompileData.getFunctionSpace(); AddressSet addrset = @@ -923,7 +923,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field public void layoutsChanged(List layouts) { pixmap.layoutsChanged(layouts); for (DecompilerMarginProvider element : marginProviders) { - element.setProgram(getProgram(), layoutMgr, pixmap); + element.setProgram(getProgram(), layoutController, pixmap); } } @@ -937,7 +937,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } Address address = DecompilerUtils.getClosestAddress(getProgram(), token); if (address == null) { - address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token); + address = DecompilerUtils.findAddressBefore(layoutController.getFields(), token); } if (address == null) { address = decompileData.getFunction().getEntryPoint(); @@ -954,12 +954,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field public SearchLocation searchText(String text, FieldLocation startLocation, boolean forwardDirection) { - return layoutMgr.findNextTokenForSearch(text, startLocation, forwardDirection); + return layoutController.findNextTokenForSearch(text, startLocation, forwardDirection); } public SearchLocation searchTextRegex(String text, FieldLocation startLocation, boolean forwardDirection) { - return layoutMgr.findNextTokenForSearchRegex(text, startLocation, forwardDirection); + return layoutController.findNextTokenForSearchRegex(text, startLocation, forwardDirection); } public void setSearchResults(SearchLocation searchLocation) { @@ -1057,7 +1057,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return null; } - Field[] lines = layoutMgr.getFields(); + Field[] lines = layoutController.getFields(); List tokens = DecompilerUtils.getTokensInSelection(selection, lines); long count = tokens.stream().filter(t -> !t.getText().trim().isEmpty()).count(); @@ -1076,6 +1076,19 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return ((ClangTextField) field).getToken(cursorPosition); } + /** + * Get the line number for the given y position, relative to the scroll panel + * + *

+ * If the y position is below all the lines, the last line is returned. + * + * @param y the y position + * @return the line number, or 0 if not applicable + */ + public int getLineNumber(int y) { + return pixmap.getIndex(y).intValue() + 1; + } + public DecompileOptions getOptions() { return options; } @@ -1104,7 +1117,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field public List findTokensByName(String name) { List tokens = new ArrayList<>(); - doFindTokensByName(tokens, layoutMgr.getRoot(), name); + doFindTokensByName(tokens, layoutController.getRoot(), name); return tokens; } @@ -1139,7 +1152,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public void selectAll(EventTrigger trigger) { - BigInteger numIndexes = layoutMgr.getNumIndexes(); + BigInteger numIndexes = layoutController.getNumIndexes(); FieldSelection selection = new FieldSelection(); selection.addRange(BigInteger.ZERO, numIndexes); fieldPanel.setSelection(selection, trigger); @@ -1172,9 +1185,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public void addMarginProvider(DecompilerMarginProvider provider) { - marginProviders.add(provider); + marginProviders.add(0, provider); provider.setOptions(options); - provider.setProgram(getProgram(), layoutMgr, pixmap); + provider.setProgram(getProgram(), layoutController, pixmap); buildPanels(); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java index 1f4363d965..eb7ea75b78 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/margin/LineNumberDecompilerMarginProvider.java @@ -51,7 +51,6 @@ public class LineNumberDecompilerMarginProvider extends JPanel this.model.removeLayoutModelListener(this); } this.model = model; - this.model.addLayoutModelListener(this); setWidthForLastLine(); if (this.model != null) { this.model.addLayoutModelListener(this); @@ -87,7 +86,7 @@ public class LineNumberDecompilerMarginProvider extends JPanel } int lastLine = model.getNumIndexes().intValueExact(); int width = getFontMetrics(getFont()).stringWidth(Integer.toString(lastLine)); - setPreferredSize(new Dimension(width, 0)); + setPreferredSize(new Dimension(Math.max(16, width), 0)); invalidate(); } @@ -99,7 +98,9 @@ public class LineNumberDecompilerMarginProvider extends JPanel BigInteger endIdx = pixmap.getIndex(visible.y + visible.height); int ascent = g.getFontMetrics().getMaxAscent(); for (BigInteger i = startIdx; i.compareTo(endIdx) <= 0; i = i.add(BigInteger.ONE)) { - GraphicsUtils.drawString(this, g, i.add(BigInteger.ONE).toString(), 0, + String text = i.add(BigInteger.ONE).toString(); + int width = g.getFontMetrics().stringWidth(text); + GraphicsUtils.drawString(this, g, text, getWidth() - width, pixmap.getPixel(i) + ascent); } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java index f9382d2c3c..73ac48a123 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java @@ -49,7 +49,9 @@ import ghidra.util.task.SwingUpdateManager; GoToService.class, NavigationHistoryService.class, ClipboardService.class, DataTypeManagerService.class /*, ProgramManager.class */ }, - servicesProvided = { DecompilerHighlightService.class }, + servicesProvided = { + DecompilerHighlightService.class, DecompilerMarginService.class + }, eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java index 76163df5e3..ccde49eb95 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java @@ -21,8 +21,7 @@ import docking.ActionContext; import docking.action.DockingActionIf; import ghidra.app.context.NavigatableActionContext; import ghidra.app.context.RestrictedAddressSetContext; -import ghidra.app.decompiler.ClangToken; -import ghidra.app.decompiler.ClangTokenGroup; +import ghidra.app.decompiler.*; import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -36,15 +35,45 @@ public class DecompilerActionContext extends NavigatableActionContext implements RestrictedAddressSetContext { private final Address functionEntryPoint; private final boolean isDecompiling; + private final int lineNumber; private ClangToken tokenAtCursor = null; private boolean tokenIsInitialized = false; + /** + * Construct a context specifying the line number + * + *

+ * The specified line number may not necessarily correspond to that of the current token. This + * is usually the case when the user clicks somewhere where a token is not present, e.g., the + * margin. In these cases, the line number should be that under the mouse cursor. + * + * @param provider the decompiler provider producing the context + * @param functionEntryPoint the current function's entry, if applicable + * @param isDecompiling true if the decompiler is still working + * @param lineNumber non-zero to specify the line number + */ public DecompilerActionContext(DecompilerProvider provider, Address functionEntryPoint, - boolean isDecompiling) { + boolean isDecompiling, int lineNumber) { super(provider, provider); + if (lineNumber < 0) { + throw new IllegalArgumentException("lineNumber must be >= 0. Got " + lineNumber); + } this.functionEntryPoint = functionEntryPoint; this.isDecompiling = isDecompiling; + this.lineNumber = lineNumber; + } + + /** + * Construct a context using the current token's line number + * + * @param provider the decompiler provider producing the context + * @param functionEntryPoint the current function's entry, if applicable + * @param isDecompiling true if the decompiler is still working + */ + public DecompilerActionContext(DecompilerProvider provider, Address functionEntryPoint, + boolean isDecompiling) { + this(provider, functionEntryPoint, isDecompiling, 0); } public Address getFunctionEntryPoint() { @@ -72,6 +101,32 @@ public class DecompilerActionContext extends NavigatableActionContext return tokenAtCursor; } + /** + * Get the line number + * + *

+ * This may not always correspond to the line number of the token at the cursor. For example, if + * there is no token under the mouse, or if the context is produced by the margin. When + * generated by a mouse event, this is the line number determined by the mouse's vertical + * position. Otherwise, this is the line number of the current token. If there is no current + * token and the line number was not given at construction, this returns 0 to indicate this + * context has no line number. + * + *

+ * If the current token's line number is desired, regardless of the user's mouse position, then + * use {@code context.}{@link #getTokenAtCursor()}{@code .}{@link ClangToken#getLineParent() + * getLineParent()}{@code .}{@link ClangLine#getLineNumber() getLineNumber()}. + * + * @return the line number + */ + public int getLineNumber() { + if (lineNumber != 0) { + return lineNumber; + } + getTokenAtCursor(); + return tokenAtCursor == null ? 0 : tokenAtCursor.getLineParent().getLineNumber(); + } + public DecompilerPanel getDecompilerPanel() { return getComponentProvider().getDecompilerPanel(); } @@ -98,9 +153,12 @@ public class DecompilerActionContext extends NavigatableActionContext } /** - * The companion method of {@link #checkActionEnablement(Supplier)}. Decompiler actions - * must call this method from their {@link DockingActionIf#actionPerformed(ActionContext)} - * if they require state from the Decompiler. + * The companion method of {@link #checkActionEnablement(Supplier)}. + * + *

+ * Decompiler actions must call this method from their + * {@link DockingActionIf#actionPerformed(ActionContext)} if they require state from the + * Decompiler. * * @param actionCallback the action's code to execute */ @@ -117,9 +175,12 @@ public class DecompilerActionContext extends NavigatableActionContext } /** - * The companion method of {@link #performAction(Callback)}. Decompiler actions must call - * this method from their {@link DockingActionIf#isEnabledForContext(ActionContext)} if they - * require state from the Decompiler. + * The companion method of {@link #performAction(Callback)}. + * + *

+ * Decompiler actions must call this method from their + * {@link DockingActionIf#isEnabledForContext(ActionContext)} if they require state from the + * Decompiler. * * @param actionBooleanSupplier the action's code to verify its enablement * @return true if the action should be considered enabled diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java index 1851b9f2ee..1a2bab4b18 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java @@ -206,7 +206,7 @@ public class DecompilerClipboardProvider extends ByteCopier endRow = fieldRange.getEnd().getRow(); } - LayoutModel model = provider.getDecompilerPanel().getLayoutModel(); + LayoutModel model = provider.getDecompilerPanel().getLayoutController(); Layout layout = model.getLayout(BigInteger.valueOf(lineNumber)); ClangTextField field = (ClangTextField) layout.getField(0); int numSpaces = field.getStartX() / spaceCharWidthInPixels; @@ -235,7 +235,7 @@ public class DecompilerClipboardProvider extends ByteCopier int startRow = fieldRange.getStart().getRow(); int endRow = fieldRange.getEnd().getRow(); - LayoutModel model = provider.getDecompilerPanel().getLayoutModel(); + LayoutModel model = provider.getDecompilerPanel().getLayoutController(); Layout layout = model.getLayout(BigInteger.valueOf(lineNumber)); ClangTextField field = (ClangTextField) layout.getField(0); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index a5bcbf8ac5..47617b3d3a 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -197,7 +197,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter Address entryPoint = function.getEntryPoint(); boolean isDecompiling = controller.isDecompiling(); - return new DecompilerActionContext(this, entryPoint, isDecompiling); + int lineNumber = + event != null & !isDecompiling ? getDecompilerPanel().getLineNumber(event.getY()) : 0; + return new DecompilerActionContext(this, entryPoint, isDecompiling, lineNumber); } @Override @@ -656,14 +658,15 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter followUpWorkUpdater.update(); } + @Override + public DecompilerPanel getDecompilerPanel() { + return controller.getDecompilerPanel(); + } + //================================================================================================== // methods called from other members //================================================================================================== - DecompilerPanel getDecompilerPanel() { - return controller.getDecompilerPanel(); - } - // snapshot callback public void cloneWindow() { DecompilerProvider newProvider = plugin.createNewDisconnectedProvider(); diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java index d85c785d9f..d3c41f0108 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java @@ -112,7 +112,8 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac // tricky business - the 'finder' is being run in a thread pool, so we must wait for that // model to finish loading - DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false); + DecompilerActionContext context = + new DecompilerActionContext(provider, addr(0x0), false); performAction(findReferencesAction, context, true); ThreadedTableModel model = waitForSearchProvider(); @@ -123,7 +124,8 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac // tricky business - the 'finder' is being run in a thread pool, so we must wait for that // model to finish loading - DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false); + DecompilerActionContext context = + new DecompilerActionContext(provider, addr(0x0), false); performAction(findReferencesToAddressAction, context, true); ThreadedTableModel model = waitForSearchProvider(); @@ -134,7 +136,8 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac // tricky business - the 'finder' is being run in a thread pool, so we must wait for that // model to finish loading - DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false); + DecompilerActionContext context = + new DecompilerActionContext(provider, addr(0x0), false); performAction(findReferencesToSymbolAction, context, true); ThreadedTableModel model = waitForSearchProvider(); diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java index a4f56fce39..086dc7b143 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java @@ -62,6 +62,7 @@ public class EquateTest extends AbstractDecompilerTest { /** * Simulate the action of "converting" the current token to the given format + * * @param convertType is the given format */ private void convertToken(int convertType) { @@ -94,6 +95,7 @@ public class EquateTest extends AbstractDecompilerTest { /** * Simulate setting an equate on the current token with the given name + * * @param nm is the given name */ private void convertToken(String nm) {