From 518575b0759902ef2378b3d136da095e40faf28a Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:18:30 -0400 Subject: [PATCH] GP-1280: Added breakpoint margin to Decompiler in Debugger. --- Ghidra/Debug/Debugger/build.gradle | 2 + Ghidra/Debug/Debugger/certification.manifest | 1 + .../DebuggerBreakpointMarkerPlugin.html | 23 ++ .../DebuggerDecompilerBreakpointMargin.png | Bin 0 -> 28636 bytes .../BreakpointsDecompilerMarginProvider.java | 140 +++++++++ .../DebuggerBreakpointMarkerPlugin.java | 285 +++++++++++------- ...ebuggerLogicalBreakpointServicePlugin.java | 12 +- .../DebuggerLogicalBreakpointService.java | 41 ++- ...ggerBreakpointMarkerPluginScreenShots.java | 41 +++ .../DebuggerBreakpointMarkerPluginTest.java | 119 +++++++- .../gui/breakpoint/MockDecompileResults.java | 133 ++++++++ .../ghidra/app/decompiler/ClangFunction.java | 30 +- .../app/decompiler/ClangTokenGroup.java | 8 +- .../decompiler/DecompilerMarginService.java | 8 + .../component/ClangDecompilerHighlighter.java | 3 +- .../decompiler/component/DecompilerPanel.java | 69 +++-- .../LineNumberDecompilerMarginProvider.java | 7 +- .../core/decompile/DecompilePlugin.java | 4 +- .../decompile/DecompilerActionContext.java | 79 ++++- .../DecompilerClipboardProvider.java | 4 +- .../core/decompile/DecompilerProvider.java | 13 +- ...actDecompilerFindReferencesActionTest.java | 9 +- .../app/plugin/core/decompile/EquateTest.java | 2 + 23 files changed, 844 insertions(+), 189 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointsDecompilerMarginProvider.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/MockDecompileResults.java 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 0000000000000000000000000000000000000000..b0a0d584d36a00dfe1112962b5b80291c08337b8 GIT binary patch literal 28636 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 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) {