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:
+
+
+
Iterating over every snapshot associated with the active thread
+
+
Reading the program counter (PC) register at each step
+
+
Mapping the dynamic address to a statically-analyzed program via the Static
+ Mappings service
+
+
Detecting function entries, call instructions, returns, and tail calls.
+
+
There are some limitations for how accurate the call tree will
+ be.
+
+
+
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:
+
+
+
A trace must be open and a thread must be active.
+
+
The trace must contain at least one snapshot whose event thread matches the active
+ thread.
+
+
The modules visited during execution must be imported, analyzed, and mapped to the trace
+ via the Static Mappings service.
+
+
Functions must be defined in the mapped program databases at the addresses visited.
+
+
+
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:
+
+
+
+ Function - The name of the function, displayed as a tree. Each row is indented under
+ its caller. The tree data prefix indicates the node type:
+
+
+
Plain name - a regular function call. The PC was observed at the entry point of a
+ function. The function's parameters are captured from registers, stack, or memory at this
+ snapshot.
+
+
Return: <name> - a return from the named function. A terminal
+ (return) instruction was executed inside a known function. The function's return value is
+ captured at this snapshot. Return nodes can be hidden with the Show/Hide Returns action.
+
+
Tail Call: <name> - a tail call from this named function to
+ another. A call-terminator instruction (e.g., a jump used as a tail call) was executed
+ inside a known function. Tail call nodes can be hidden with the Show/Hide Tail Calls action.
+
+
External: <name> - a call to an external (thunked) function. The
+ PC was observed at the entry point of a thunk function whose target is an external symbol
+ (e.g., a shared library function). External nodes are always shown.
+
+
+
+
Snapshot - The snapshot key when the function was entered, returned from, or
+ tail-called. Double-clicking a row activates this snapshot in the Debugger.
+
+
Module - The name of the module where the function is defined.
+
+
Return - The raw bytes of the return value, displayed as a hex string, captured at
+ the moment the function returned. Only populated for return nodes.
+
+
Parameter 0, Parameter 1, ... - One column per parameter, up to the maximum
+ number of parameters found across all functions in the tree. Each cell shows the parameter
+ name and its raw byte value captured at the moment the function was entered. The number of
+ parameter columns adjusts automatically when the tree is rebuilt.
+
+
+
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:
+
+
+
The PC register was null or could not be read.
+
+
The dynamic address could not be mapped to a static program location (e.g., the module is
+ not imported or not mapped).
+
+
The static address is not inside any known function.
+
+
A call instruction had no default fall-through address.
+
+
A return was encountered with an empty call stack.
+
+
+
Log entries are automatically removed when the condition that caused them is resolved on a
+ subsequent rebuild. The log table has the following columns:
+
+
+
Trace - The name of the trace in which the problem occurred.
+
+
Snap - The snapshot key at which the problem occurred.
+
+
Message - A human-readable description of the problem.
+
+
Dynamic Address - The raw dynamic (trace) address of the PC at the time of the
+ problem, if available.
+
+
Static Address - The mapped static address, if the mapping was successful.
+
+
+
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:
+
+
+
Go To Snapshot N - Activates snapshot N in the Debugger.
+
+
View <param> - For each parameter whose raw byte value can be interpreted as
+ a valid address, navigates the main Debugger Listing to that address.
+
+
View <param> -> View In <listing> - Navigates an additional open
+ Debugger Listing window to the parameter's address.
+
+
View <param> -> View In New Listing - Opens a new Debugger Listing window
+ and navigates it to the parameter's address.
+
+
View Return - Same as the parameter actions above, but for the function's return
+ value. Only available for return nodes whose return value can be interpreted as a valid
+ address.
+
+
Expand - Expands the selected node(s) one level.
+
+
Collapse - Collapses the selected node(s) one level.
+
+
Expand Recursively - Expands the selected node(s) and all descendants.
+
+
Collapse Recursively - Collapses the selected node(s) and all descendants.
+
+
+
Double-Click
+
+
Double-clicking any row in the call tree table activates the snapshot recorded in that row's
+ Snapshot column.
+
+
Limitations
+
+
+
The tree is built per-thread. Switching to a different thread triggers a separate build.
+ Previously built trees are cached per trace and thread and reused when switching back.
+
+
The tree relies on static mappings being present and correct. If a module is not imported
+ or not mapped, all snapshots where the PC falls in that module will be skipped and logged as
+ errors.
+
+
If the trace does not record every instruction, the tree may miss calls or returns that
+ occurred between recorded snapshots. The fall-through tracking heuristic attempts to recover
+ from missed returns, but it is not guaranteed to be correct.
+
+
Parameter and return values are read directly from registers, stack memory, or data
+ memory at the snapshot where the function entry or return was detected. If the calling
+ convention is not correctly defined in the program database, the values shown may be
+ incorrect or absent. Additionally, if the storage location of the parameter is stale/unknown
+ the values may be incorrect
+
+
The tree is marked stale (with a status bar message) when functions or symbols change in
+ an open program database. Use Rebuild Call Tree to refresh
+ it.
+
+
+
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;
}
/**