diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 4d989a1b13..c8363f723c 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -74,6 +74,11 @@ src/main/help/help/topics/DebuggerVariableViewerPlugin/images/DebuggerVariableVi src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END| src/main/help/help/topics/DynamicStaticSynchronizationPlugin/DynamicStaticSynchronizationPlugin.html||GHIDRA||||END| +src/main/help/help/topics/TraceCallTreePlugin/TraceCallTreePlugin.html||GHIDRA||||END| +src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePlugin.png||GHIDRA||||END| +src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginHideReturns.png||GHIDRA||||END| +src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginRightClick.png||GHIDRA||||END| +src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginUnfolded.png||GHIDRA||||END| src/main/help/help/topics/VariableValueHoverPlugin/VariableValueHoverPlugin.html||GHIDRA||||END| src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginBrowser.png||GHIDRA||||END| src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginDecompiler.png||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index e3ed917984..2ad8d21af6 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -131,9 +131,14 @@ sortgroup="y" target="help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html" /> + + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/TraceCallTreePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/TraceCallTreePlugin.html new file mode 100644 index 0000000000..764bafa090 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/TraceCallTreePlugin.html @@ -0,0 +1,251 @@ + + + + + + + Debugger: Trace Call Tree + + + + + +

Debugger: Trace Call Tree

+ +
+ +
+ +

The Trace Call Tree window reconstructs the call hierarchy of all functions executed by a + single thread over the entire recorded trace. It does this by:

+ + + +

The resulting tree shows the sequence of function invocations in the order they occurred, + with each call nested under its caller.

+
+ + +

For the tree to be populated, the following conditions must be met:

+ + + +

The window is divided into two panels: the Call Tree Table on top and the + (toggleable) Log Window on the bottom. The divider between them can be dragged to resize + each panel. The log window can be hidden using the Show/Hide Log + Window action.

+ +

Call Tree Table Columns

+ +

The call tree table has the following columns:

+ + + +

Log Window

+ +

The log window displays diagnostic messages generated while building the call tree. Each row + describes a snapshot at which the tree builder encountered a problem, such as:

+ + + +

Log entries are automatically removed when the condition that caused them is resolved on a + subsequent rebuild. The log table has the following columns:

+ + + +

Actions

+ +

Rebuild Call Tree

+ +

Cancels any in-progress build and restarts the call tree generation from scratch using the + current trace and thread. Use this action after changing static mappings, adding function + definitions, or any other change that may affect the tree's contents. The tree is rebuilt + automatically when new snapshots are appended to the trace.

+ +

Jump to Current

+ +

Scrolls the call tree table to the node that corresponds to the current debugger + coordinates. The node is selected and its ancestors are expanded so it is visible.

+ +

Expand from Selected Cell + Recursively

+ +
+ +
+ +

Expands the currently selected node and all of its descendants in the tree.

+ +

Collapse from Selected + Cell Recursively

+ +

Collapses the currently selected node and all of its descendants in the tree.

+ +

Show/Hide Returns

+ +
+ +
+ +

Toggles the visibility of return nodes in the tree.

+ +

Show/Hide Tail Calls

+ +

Toggles the visibility of tail call nodes in the tree.

+ +

Show/Hide Log Window

+ +

Toggles the visibility of the log panel.

+ +

Clear Log

+ +

Removes all entries from the log table.

+ +

Popup (Right-Click) Actions

+ +
+ +
+ +

Right-clicking a row in the call tree table provides the following context actions:

+ + + +

Double-Click

+ +

Double-clicking any row in the call tree table activates the snapshot recorded in that row's + Snapshot column.

+ +

Limitations

+ + + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePlugin.png new file mode 100644 index 0000000000..30cba04599 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginHideReturns.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginHideReturns.png new file mode 100644 index 0000000000..8ce2b12d7b Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginHideReturns.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginRightClick.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginRightClick.png new file mode 100644 index 0000000000..d0ad124b1e Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginRightClick.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginUnfolded.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginUnfolded.png new file mode 100644 index 0000000000..f778774835 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/TraceCallTreePlugin/images/TraceCallTreePluginUnfolded.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePlugin.java index 269aa238d0..444c9d3654 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePlugin.java @@ -35,7 +35,7 @@ import ghidra.trace.model.Trace; packageName = DebuggerPluginPackage.NAME, status = PluginStatus.UNSTABLE, servicesRequired = { DebuggerTraceManagerService.class, DebuggerStaticMappingService.class, - DebuggerListingService.class, ProgressService.class }, + DebuggerListingService.class, ProgressService.class, DebuggerConsoleService.class }, eventsConsumed = { TraceClosedPluginEvent.class, TraceActivatedPluginEvent.class, OpenProgramPluginEvent.class, CloseProgramPluginEvent.class } ) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreeProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreeProvider.java index 20fb5398aa..26560d0521 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreeProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreeProvider.java @@ -79,6 +79,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.CLEAR_ICON; setToolBarData(new ToolBarData(icon, "log")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "clear_log")); } @Override @@ -94,6 +95,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.COLLAPSE_ALL_ICON; setToolBarData(new ToolBarData(icon, "expand")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "collapse_recursive")); } @Override @@ -114,6 +116,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.HOME_ICON; setToolBarData(new ToolBarData(icon, "jump")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "jump_to_current")); } @Override @@ -134,6 +137,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.REFRESH_ICON; setToolBarData(new ToolBarData(icon, "refresh")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "rebuild_call_tree")); } @Override @@ -158,6 +162,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter Icon icon = Icons.WARNING_ICON; setToolBarData(new ToolBarData(icon, "log")); setSelected(showLogWindow); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "show_log_window")); } @Override @@ -188,6 +193,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.ARROW_UP_LEFT_ICON; setToolBarData(new ToolBarData(icon, "show")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "show_returns")); setSelected(showReturns); } @@ -212,6 +218,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON; setToolBarData(new ToolBarData(icon, "show")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "show_tail_calls")); setSelected(showTailCalls); } @@ -286,6 +293,7 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setEnabled(true); Icon icon = Icons.EXPAND_ALL_ICON; setToolBarData(new ToolBarData(icon, "expand")); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "expand_recursive")); } @Override @@ -299,6 +307,10 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter } } + // Package-private for screenshots + final UnfoldRecursiveAction unfoldRecursiveAction; + final ShowReturnsToggleAction showReturnsToggleAction; + protected static double computeResizeWeight(JSplitPane split) { final java.util.function.Function axis = switch (split.getOrientation()) { @@ -326,12 +338,12 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter private final Map traceToMaxSnap = new HashMap<>(); private final Map rootNodesForTraceThreadMap = new HashMap<>(); - private boolean rootNodeChanged = false; @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; private JComponent component; - private TraceCallTreeTable treeTable; + // Package-private for screenshots + TraceCallTreeTable treeTable; private AbstractTraceCallTreeNode currentRootNode; private AbstractTraceCallTreeNode currentNode; private TraceCallTreeModel treeTableModel; @@ -398,13 +410,16 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter setVisible(true); tool.addPopupActionProvider(this); tool.addLocalAction(this, new FoldRecursiveAction(this)); - tool.addLocalAction(this, new UnfoldRecursiveAction(this)); - tool.addLocalAction(this, new ShowReturnsToggleAction(this)); + unfoldRecursiveAction = new UnfoldRecursiveAction(this); + tool.addLocalAction(this, unfoldRecursiveAction); + showReturnsToggleAction = new ShowReturnsToggleAction(this); + tool.addLocalAction(this, showReturnsToggleAction); tool.addLocalAction(this, new ShowTailCallsToggleAction(this)); tool.addLocalAction(this, new RebuildCallTreeAction(this)); tool.addLocalAction(this, new JumpToCurrentAction(this)); tool.addLocalAction(this, new ShowLogWindowAction(this)); tool.addLocalAction(this, new ClearLogAction(this)); + setHelpLocation(new HelpLocation("TraceCallTreePlugin", "plugin")); } private void buildPanel() { @@ -905,10 +920,10 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter callTreeNodeCompletableFuture.whenComplete((result, ex) -> { if ((ex != null) && !(ex instanceof CancellationException)) { treeTable.setStatusMessage(ex.getMessage()); + return; } else if (result != null) { currentRootNode = result; - rootNodeChanged = true; final Map threadMap = rootNodesForTraceThreadMap .computeIfAbsent(coords.getTrace(), e -> new RootCache()) @@ -1255,7 +1270,6 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter final Map threadMap = rootNodesForTraceThreadMap.get(coords.getTrace()).rootNodesPerThread(); if (threadMap.containsKey(coords.getThread())) { - rootNodeChanged = threadMap.get(coords.getThread()) != currentRootNode; currentRootNode = threadMap.get(coords.getThread()); refresh(coords); } @@ -1289,12 +1303,8 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter private void updateModel(AbstractTraceCallTreeNode node) { SwingUtilities.invokeLater(() -> { - treeTableModel.setRootNode(node); - if (rootNodeChanged) { - rootNodeChanged = false; - if (node != null) { - treeTableModel.setNumberOfParameterColumns(node.getLargestParamSize()); - } + if (treeTableModel.setRootNode(node) && node != null) { + treeTableModel.setNumberOfParameterColumns(node.getLargestParamSize()); } }); } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePluginScreenShots.java new file mode 100644 index 0000000000..f41ffc9095 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/tracecalltree/TraceCallTreePluginScreenShots.java @@ -0,0 +1,285 @@ +/* ### + * 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.tracecalltree; + +import java.awt.*; +import java.io.IOException; +import java.math.BigInteger; +import java.util.concurrent.TimeUnit; + +import db.Transaction; +import generic.test.category.NightlyCategory; +import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.plugin.core.debug.service.progress.ProgressServicePlugin; +import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; +import ghidra.app.services.ProgressService; +import ghidra.async.AsyncTestUtils; +import ghidra.debug.api.progress.MonitorReceiver; +import ghidra.framework.model.DomainFolder; +import ghidra.framework.model.DomainObject; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.listing.ParameterImpl; +import ghidra.program.model.listing.Program; +import ghidra.test.ToyProgramBuilder; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.AssertException; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.ConsoleTaskMonitor; +import ghidra.util.task.TaskMonitor; +import help.screenshot.GhidraScreenShotGenerator; +import org.junit.*; +import org.junit.experimental.categories.Category; + +@Category(NightlyCategory.class) // this may actually be an @PortSensitive test +public class TraceCallTreePluginScreenShots extends GhidraScreenShotGenerator + implements AsyncTestUtils { + + private static final TaskMonitor MONITOR = new ConsoleTaskMonitor(); + ToyDBTraceBuilder tb; + Program progHw; + Program progLibc; + private ProgramManagerPlugin programManager; + private DebuggerTraceManagerServicePlugin traceManager; + private TraceCallTreeProvider provider; + private DebuggerStaticMappingServicePlugin mappingService; + private ProgressService progressService; + + @Before + public void setUpMine() throws Exception { + programManager = addPlugin(tool, ProgramManagerPlugin.class); + traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class); + mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); + progressService = addPlugin(tool, ProgressServicePlugin.class); + + addPlugin(tool, TraceCallTreePlugin.class); + + provider = waitForComponentProvider(TraceCallTreeProvider.class); + + populateTraceAndPrograms(); + } + + private void populateTraceAndPrograms() throws Exception { + ToyProgramBuilder helloworldBuilder = new ToyProgramBuilder("helloworld", false, this); + helloworldBuilder.createMemory(".text", "0x00400000", 0x50); + helloworldBuilder.createEmptyFunction("main", null, "__fastcall", false, "0x00400000", 6, + IntegerDataType.dataType, + new ParameterImpl("argc", IntegerDataType.dataType, + helloworldBuilder.getProgram()), + new ParameterImpl("argv", CharDataType.dataType, helloworldBuilder.getProgram())); + helloworldBuilder.createEmptyFunction("print", null, "__fastcall", false, "0x00400006", 2, + VoidDataType.dataType, + new ParameterImpl("string", CharDataType.dataType, + helloworldBuilder.getProgram())); + helloworldBuilder.createCallInstruction("0x00400000", "0x00400006"); + helloworldBuilder.createCallInstruction("0x00400002", "0x00400050"); + helloworldBuilder.createReturnInstruction("0x00400004"); + helloworldBuilder.createJmpInstruction("0x00400006", "0x00400054"); + progHw = helloworldBuilder.getProgram(); + + ToyProgramBuilder libcBuilder = new ToyProgramBuilder("libc", false, this); + libcBuilder.createMemory(".text", "0x00400050", 0x50); + libcBuilder.createEmptyFunction("gets", null, "__fastcall", false, "0x00400050", 3, + CharDataType.dataType); + libcBuilder.createEmptyFunction("printf", null, "__fastcall", false, "0x00400054", 3, + VoidDataType.dataType, + new ParameterImpl("string", CharDataType.dataType, + helloworldBuilder.getProgram())); + libcBuilder.createNOPInstruction("0x00400050", 1); + libcBuilder.createReturnInstruction("0x00400052"); + libcBuilder.createNOPInstruction("0x00400054", 1); + libcBuilder.createReturnInstruction("0x00400056"); + progLibc = libcBuilder.getProgram(); + + tb = new ToyDBTraceBuilder("toy", + libcBuilder.getLanguage().getLanguageID().getIdAsString()); + + intoProject(progHw); + intoProject(progLibc); + intoProject(tb.trace); + programManager.openProgram(progLibc); + programManager.openProgram(progHw); + + traceManager.openTrace(tb.trace); + mappingService.changesSettled().get(1, TimeUnit.SECONDS); + + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ProgramEmulationUtils.EMU_SESSION_SCHEMA); + + tb.trace.getModuleManager() + .addLoadedModule("Modules[helloword]", "helloworld", + tb.range(0x00400000, 0x00400050), 0); + tb.trace.getModuleManager() + .addLoadedModule("Modules[libc]", "libc", tb.range(0x00400050, 0x00400100), 0); + + tb.trace.getMemoryManager() + .addRegion("Memory[ALL]", Lifespan.nowOn(0), + tb.range(0x0, 0xFFFF_FFFF_FFFF_FFFFL), TraceMemoryFlag.READ, + TraceMemoryFlag.EXECUTE, TraceMemoryFlag.WRITE); + + mappingService.addIdentityMapping(tb.trace, progHw, Lifespan.nowOn(0), true); + mappingService.addIdentityMapping(tb.trace, progLibc, Lifespan.nowOn(0), true); + + TraceThread thread = tb.getOrAddThread("Threads[1]", 0); + tb.createObjectsRegsForThread(thread, Lifespan.nowOn(0), tb.host); + TraceMemorySpace regs = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + Register pc = tb.trace.getBaseLanguage().getProgramCounter(); + Register arg1 = tb.trace.getBaseLanguage().getRegister("r12"); + Register arg2 = tb.trace.getBaseLanguage().getRegister("r11"); + + TraceSnapshot snap = tb.trace.getTimeManager().createSnapshot("Start main"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400000))); + regs.setValue(snap.getKey(), new RegisterValue(arg1, BigInteger.valueOf(0x3))); + regs.setValue(snap.getKey(), new RegisterValue(arg2, BigInteger.valueOf(0x1337))); + + snap = tb.trace.getTimeManager().createSnapshot("in print"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400006))); + regs.setValue(snap.getKey(), new RegisterValue(arg1, BigInteger.valueOf(0x345807))); + + snap = tb.trace.getTimeManager().createSnapshot("in printf"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400054))); + regs.setValue(snap.getKey(), new RegisterValue(arg1, BigInteger.valueOf(0x345807))); + + snap = tb.trace.getTimeManager().createSnapshot("return printf"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400056))); + + snap = tb.trace.getTimeManager().createSnapshot("in main"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400002))); + + snap = tb.trace.getTimeManager().createSnapshot("in gets"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400050))); + + snap = tb.trace.getTimeManager().createSnapshot("return gets"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400052))); + regs.setValue(snap.getKey(), new RegisterValue(arg1, BigInteger.valueOf(0x1234))); + + snap = tb.trace.getTimeManager().createSnapshot("return main"); + snap.setEventThread(thread); + regs.setValue(snap.getKey(), new RegisterValue(pc, BigInteger.valueOf(0x00400004))); + regs.setValue(snap.getKey(), new RegisterValue(arg1, BigInteger.valueOf(-0x1))); + + } + + mappingService.changesSettled().get(1, TimeUnit.SECONDS); + + traceManager.activateTrace(tb.trace); + traceManager.activateSnap(0); + } + + protected void intoProject(DomainObject obj) { + waitForDomainObject(obj); + DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder(); + waitForCondition(() -> { + try { + rootFolder.createFile(obj.getName(), obj, MONITOR); + return true; + } + catch (InvalidNameException | CancelledException e) { + throw new AssertionError(e); + } + catch (IOException e) { + // Usually "object is busy". Try again. + return false; + } + }); + } + + public static void waitForDomainObject(DomainObject object) { + object.flushEvents(); + waitForSwing(); + } + + @After + public void tearDownMine() { + tb.close(); + + if (progHw != null) { + progHw.release(this); + progHw = null; + } + if (progLibc != null) { + progLibc.release(this); + progLibc = null; + } + } + + @Test + public void testCaptureTraceCallTreePluginUnfolded() { + selectRow(provider.treeTable.getTable(), 0); + performAction(provider.unfoldRecursiveAction, true); + for (MonitorReceiver e : progressService.getAllMonitors()) { + waitFor(() -> !e.isValid()); + } + captureIsolatedProvider(provider, 750, 500); + } + + @Test + public void testCaptureTraceCallTreePlugin() { + captureIsolatedProvider(provider, 750, 500); + } + + @Test + public void testCaptureTraceCallTreePluginRightClick() { + selectRow(provider.treeTable.getTable(), 0); + Rectangle cellRect = provider.treeTable.getTable().getCellRect(0, 0, true); + rightClick(provider.treeTable.getTable(), cellRect.x + cellRect.width / 2, + cellRect.y + cellRect.height / 2); + runSwing(() -> { + Window window = tool.getWindowManager().getProviderWindow(provider); + if (window == null) { + throw new AssertException( + "Could not find window for " + "provider--is it showing?: " + + provider.getName()); + } + + window.setSize(new Dimension(750, 500)); + window.toFront(); + provider.getComponent().requestFocus(); + paintFix(window); + }); + + waitForSwing(); + captureProviderWithScreenShot(provider); + } + + @Test + public void testCaptureTraceCallTreePluginHideReturns() { + selectRow(provider.treeTable.getTable(), 0); + performAction(provider.unfoldRecursiveAction, true); + performAction(provider.showReturnsToggleAction, true); + for (MonitorReceiver e : progressService.getAllMonitors()) { + waitFor(() -> !e.isValid()); + } + captureIsolatedProvider(provider, 750, 500); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/gtreetable/GTreeTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/gtreetable/GTreeTableModel.java index 91ef7d4966..d090b105a3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/gtreetable/GTreeTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/gtreetable/GTreeTableModel.java @@ -142,10 +142,13 @@ public class GTreeTableModel extends ThreadedTableMode * * @param node * Root node to set + * @return True if node is different, false otherwise */ - public void setRootNode(GTreeTableNode node) { + public boolean setRootNode(GTreeTableNode node) { + boolean ret = rootNode != node; rootNode = node; reload(); + return ret; } /**