mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-19 16:40:38 -09:00
Merge remote-tracking branch
'origin/GP-6992_ghidra_red_TraceCallTreeHelp--SQUASHED'
This commit is contained in:
@@ -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|
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: <name></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: <name></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: <name></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 <param></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 <param> -> View In <listing></B> - Navigates an additional open
|
||||
Debugger Listing window to the parameter's address.</LI>
|
||||
|
||||
<LI><B>View <param> -> 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 |
@@ -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 }
|
||||
)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user