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) {