Merge remote-tracking branch

'origin/GP-6992_ghidra_red_TraceCallTreeHelp--SQUASHED'
This commit is contained in:
Ryan Kurtz
2026-08-28 09:58:10 -04:00
11 changed files with 573 additions and 14 deletions

View File

@@ -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|

View File

@@ -131,9 +131,14 @@
sortgroup="y"
target="help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html" />
<tocdef id="TraceCallTreePlugin" text="Trace Call Tree"
sortgroup="z"
target="help/topics/TraceCallTreePlugin/TraceCallTreePlugin.html" />
<tocdef id="DebuggerVariableViewerPlugin" text="Variable Viewer"
sortgroup="za"
target="help/topics/DebuggerVariableViewerPlugin/DebuggerVariableViewerPlugin.html" />
</tocdef>
</tocref>
</tocroot>

View File

@@ -0,0 +1,251 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Trace Call Tree</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Trace Call Tree</H1>
<DIV class="image">
<IMG alt="" src="images/TraceCallTreePlugin.png">
</DIV>
<P>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:</P>
<UL>
<LI>Iterating over every snapshot associated with the active thread</LI>
<LI>Reading the program counter (PC) register at each step</LI>
<LI>Mapping the dynamic address to a statically-analyzed program via the <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static
Mappings</A> service</LI>
<LI>Detecting function entries, call instructions, returns, and tail calls.</LI>
<LI>There are some <A href="#limitations">limitations</A> for how accurate the call tree will
be.</LI>
</UL>
<P>The resulting tree shows the sequence of function invocations in the order they occurred,
with each call nested under its caller.</P>
<BR>
<P>For the tree to be populated, the following conditions must be met:</P>
<UL>
<LI>A trace must be open and a thread must be active.</LI>
<LI>The trace must contain at least one snapshot whose event thread matches the active
thread.</LI>
<LI>The modules visited during execution must be imported, analyzed, and mapped to the trace
via the Static Mappings service.</LI>
<LI>Functions must be defined in the mapped program databases at the addresses visited.</LI>
</UL>
<P>The window is divided into two panels: the <B>Call Tree Table</B> on top and the
(toggleable) <B>Log Window</B> on the bottom. The divider between them can be dragged to resize
each panel. The log window can be hidden using the <A href="#show_log_window">Show/Hide Log
Window</A> action.</P>
<H2>Call Tree Table Columns</H2>
<P>The call tree table has the following columns:</P>
<UL>
<LI>
<B>Function</B> - The name of the function, displayed as a tree. Each row is indented under
its caller. The tree data prefix indicates the node type:
<UL>
<LI>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.</LI>
<LI><CODE>Return: &lt;name&gt;</CODE> - 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 <A href=
"#show_returns">Show/Hide Returns</A> action.</LI>
<LI><CODE>Tail Call: &lt;name&gt;</CODE> - 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 <A href=
"#show_tail_calls">Show/Hide Tail Calls</A> action.</LI>
<LI><CODE>External: &lt;name&gt;</CODE> - 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.</LI>
</UL>
</LI>
<LI><B>Snapshot</B> - The snapshot key when the function was entered, returned from, or
tail-called. Double-clicking a row activates this snapshot in the Debugger.</LI>
<LI><B>Module</B> - The name of the module where the function is defined.</LI>
<LI><B>Return</B> - The raw bytes of the return value, displayed as a hex string, captured at
the moment the function returned. Only populated for return nodes.</LI>
<LI><B>Parameter 0</B>, <B>Parameter 1</B>, ... - 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.</LI>
</UL>
<H2><A name="log_window"></A>Log Window</H2>
<P>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:</P>
<UL>
<LI>The PC register was null or could not be read.</LI>
<LI>The dynamic address could not be mapped to a static program location (e.g., the module is
not imported or not mapped).</LI>
<LI>The static address is not inside any known function.</LI>
<LI>A call instruction had no default fall-through address.</LI>
<LI>A return was encountered with an empty call stack.</LI>
</UL>
<P>Log entries are automatically removed when the condition that caused them is resolved on a
subsequent rebuild. The log table has the following columns:</P>
<UL>
<LI><B>Trace</B> - The name of the trace in which the problem occurred.</LI>
<LI><B>Snap</B> - The snapshot key at which the problem occurred.</LI>
<LI><B>Message</B> - A human-readable description of the problem.</LI>
<LI><B>Dynamic Address</B> - The raw dynamic (trace) address of the PC at the time of the
problem, if available.</LI>
<LI><B>Static Address</B> - The mapped static address, if the mapping was successful.</LI>
</UL>
<H2>Actions</H2>
<H3><A name="rebuild_call_tree"></A><IMG alt="" src="icon.refresh">Rebuild Call Tree</H3>
<P>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.</P>
<H3><A name="jump_to_current"></A><IMG alt="" src="icon.home">Jump to Current</H3>
<P>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.</P>
<H3><A name="expand_recursive"></A><IMG alt="" src="icon.expand.all">Expand from Selected Cell
Recursively</H3>
<DIV class="image">
<IMG alt="" src="images/TraceCallTreePluginUnfolded.png">
</DIV>
<P>Expands the currently selected node and all of its descendants in the tree.</P>
<H3><A name="collapse_recursive"></A><IMG alt="" src="icon.collapse.all">Collapse from Selected
Cell Recursively</H3>
<P>Collapses the currently selected node and all of its descendants in the tree.</P>
<H3><A name="show_returns"></A><IMG alt="" src="icon.arrow.up.left">Show/Hide Returns</H3>
<DIV class="image">
<IMG alt="" src="images/TraceCallTreePluginHideReturns.png">
</DIV>
<P>Toggles the visibility of return nodes in the tree.</P>
<H3><A name="show_tail_calls"></A><IMG alt="" src="icon.navigate.in">Show/Hide Tail Calls</H3>
<P>Toggles the visibility of tail call nodes in the tree.</P>
<H3><A name="show_log_window"></A><IMG alt="" src="icon.warning">Show/Hide Log Window</H3>
<P>Toggles the visibility of the log panel.</P>
<H3><A name="clear_log"></A><IMG alt="" src="icon.clear">Clear Log</H3>
<P>Removes all entries from the log table.</P>
<H2>Popup (Right-Click) Actions</H2>
<DIV class="image">
<IMG alt="" src="images/TraceCallTreePluginRightClick.png">
</DIV>
<P>Right-clicking a row in the call tree table provides the following context actions:</P>
<UL>
<LI><B>Go To Snapshot N</B> - Activates snapshot <EM>N</EM> in the Debugger.</LI>
<LI><B>View &lt;param&gt;</B> - For each parameter whose raw byte value can be interpreted as
a valid address, navigates the main Debugger Listing to that address.</LI>
<LI><B>View &lt;param&gt; -&gt; View In &lt;listing&gt;</B> - Navigates an additional open
Debugger Listing window to the parameter's address.</LI>
<LI><B>View &lt;param&gt; -&gt; View In New Listing</B> - Opens a new Debugger Listing window
and navigates it to the parameter's address.</LI>
<LI><B>View Return</B> - 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.</LI>
<LI><B>Expand</B> - Expands the selected node(s) one level.</LI>
<LI><B>Collapse</B> - Collapses the selected node(s) one level.</LI>
<LI><B>Expand Recursively</B> - Expands the selected node(s) and all descendants.</LI>
<LI><B>Collapse Recursively</B> - Collapses the selected node(s) and all descendants.</LI>
</UL>
<H2>Double-Click</H2>
<P>Double-clicking any row in the call tree table activates the snapshot recorded in that row's
<B>Snapshot</B> column.</P>
<H2><A name="limitations"></A>Limitations</H2>
<UL>
<LI>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.</LI>
<LI>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.</LI>
<LI>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.</LI>
<LI>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</LI>
<LI>The tree is marked stale (with a status bar message) when functions or symbols change in
an open program database. Use <A href="#rebuild_call_tree">Rebuild Call Tree</A> to refresh
it.</LI>
</UL>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -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 }
)

View File

@@ -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<Dimension, Integer> axis =
switch (split.getOrientation()) {
@@ -326,12 +338,12 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter
private final Map<Trace, Long> traceToMaxSnap = new HashMap<>();
private final Map<Trace, RootCache> 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<TraceThread, AbstractTraceCallTreeNode> threadMap =
rootNodesForTraceThreadMap
.computeIfAbsent(coords.getTrace(), e -> new RootCache())
@@ -1255,7 +1270,6 @@ public class TraceCallTreeProvider extends ComponentProviderAdapter
final Map<TraceThread, AbstractTraceCallTreeNode> 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());
}
});
}

View File

@@ -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);
}
}