GP-4101: Update Debugger help

This commit is contained in:
Dan 2023-12-04 16:04:47 -05:00
parent 58e22a6f7b
commit bb0ba16ab0
19 changed files with 229 additions and 116 deletions

View File

@ -11,10 +11,10 @@ src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/topics/Debugger/Debugger.html||GHIDRA||||END|
src/main/help/help/topics/Debugger/GettingStarted.html||GHIDRA||||END|
src/main/help/help/topics/Debugger/Troubleshooting.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBots/DebuggerBots.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerBreakpointMarkerPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerDecompilerBreakpointMargin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerFunctionGraphBreakpointMargin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointMarkerPlugin/images/DebuggerPlaceBreakpointDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/DebuggerBreakpointsPlugin.png||GHIDRA||||END|
@ -141,7 +141,6 @@ src/main/resources/images/stepover.png||GHIDRA||||END|
src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/thread.png||GHIDRA||||END|
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/write-disabled.png||GHIDRA||||END|
src/main/resources/images/write-emulator.png||GHIDRA||||END|
src/main/resources/images/write-target.png||GHIDRA||||END|
src/main/resources/images/write-trace.png||Tango Icons - Public Domain||||END|

View File

@ -176,10 +176,6 @@
<tocdef id="DebuggerPlatformPlugin" text="Platform Selection"
sortgroup="p2"
target="help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html" />
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
sortgroup="q"
target="help/topics/DebuggerBots/DebuggerBots.html" />
</tocdef>
</tocref>
</tocroot>

View File

@ -1,73 +0,0 @@
<!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 Bots</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>Debugger Bots: Workflow Automation</H1>
<P>Bots are a pluggable part of the Debugger's Workflow plugin and provide useful automation,
taking actions on the user's behalf that would otherwise be tedious, or that should occur
"under the hood." Each can be toggled from the <SPAN class="menu">Edit &rarr; Tool
Options</SPAN> menu in Ghidra's <EM>Project</EM> window.</P>
<H2><A name="show_interpreter"></A>Show Interpreter</H2>
<P>This bot automatically displays the interpreter console for new debugger connections. If a
connection does not present an interpreter, then this bot will take no action. This action can
be performed manually using the <A href=
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#console">Console</A> action in
the Commands and Objects window.</P>
<H2><A name="disassemble_at_pc"></A>Disassemble at Program Counter</H2>
<P>This bot automatically disassembles trace memory, starting at the program counter. It is
activated whenever an open trace's memory or program counters change. If the target presents a
stack, then the bot will prefer to use the program counter recorded in the innermost frame 0.
Otherwise, it will use the value of the program counter register. To accommodate multi-threaded
traces, the bot considers all threads when responding to memory changes. It considers only the
affected thread for program counter changes. This action can be performed manually using the <A
href="help/topics/DisassemblerPlugin/Disassembly.htm#Disassemble">Disassemble</A> command in
the Dynamic Listing window.</P>
<H2><A name="map_modules"></A>Map Modules</H2>
<P>This bot automatically maps trace modules to programs opened in the same tool. It is
activated whenever a trace or program is opened, or when an opened trace's modules change. For
a given trace, this bot considers only those modules not already mapped, seeking suitable
programs open in the same tool. If the trace is open in multiple tools (not common) the bot
will consider programs from all such tools where the workflow plugin is enabled. This action
can be performed manually using the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_modules">Map Modules</A>
action in the Modules and Sections window.</P>
<H2><A name="map_sections"></A>Map Sections</H2>
<P>This bot automatically maps trace sections to memory blocks of programs opened in the same
tool. Its operation is analogous to that of the Map Modules Bot, except that it creates the
mapped ranges by section. It is not commonly used, as it's less efficient than the Map Modules
Bot, but it is required whenever a target presents sections which can be relocated
independently of other sections in the same module. This is more common in abstract machines,
such as the Java Virtual Machine, where each method is a "section." This action can be
performed manually using the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_sections">Map Sections</A>
action in the Modules and Sections window.</P>
<H2><A name="map_regions"></A>Map Regions</H2>
<P>This bot automatically maps trace regions to memory blocks of programs opened in the same
tool. Its operation is analogous to that of the Map Modules Bot, except that it creates the
mapped ranges by region. It is not commonly used, as it's less efficient than the Map Modules
Bot, but it is required whenever a target fails to present modules. This action can be
performed manually using the <A href=
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html#map_regions">Map Regions</A>
action in the Regions window.</P>
</BODY>
</HTML>

View File

@ -35,6 +35,24 @@
<P>By default, enabled breakpoints are colored a desaturated blue, ineffective breakpoints are
colored grey, and disabled breakpoints have no background at all.</P>
<H2>In the Function Graph</H2>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerFunctionGraphBreakpointMargin.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>When active in the Debugger, the <A href=
"help/topics/FunctionGraphPlugin/Function_Graph.html">Function Graph</A> will display
breakpoints using background colors and markers in each vertex's margin. The margin behaves
exactly as it would in the static listing. A marker is displayed at each address with a
breakpoint indicating its state. Multiple breakpoints at an address may result in display of a
mixed state. Double clicking in the margin will set or toggle a breakpoint at that address.</P>
<H2>In the Decompiler</H2>
<TABLE width="100%">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -24,11 +24,8 @@
<P>The console logs messages from Ghidra related to the debugger. Depending on the exact
configuration, this can comprise a wide range of components, including all GUI views, active
connectors, and running agents. Currently, it implements an appender to gather all Log4J
messages emitted by Ghidra and filters for debugger-related packages and a level in the range
INFO through FATAL, inclusive. That feature will likely be removed as more components are
programmed to work directly with the console. Soon, it may also provide a command-line
interface to control Ghidra's debugging sessions and interact with traces.</P>
connectors, and running agents. Soon, it may also provide a command-line interface to control
Ghidra's debugging sessions and interact with traces.</P>
<P>Some log messages include an action context, allowing plug-ins to offer actions on that
message. These are said to be "actionable" messages. A noteworthy example is when navigating to
@ -68,5 +65,10 @@
<H3><A name="select_none"></A>Select None</H3>
<P>Resets the selection, usually so table scrolling can be restored to "normal."</P>
<H3><A name="cancel"></A>Cancel</H3>
<P>Some experimental features may display progress entries in the log for background tasks they
run. This action is displayed in the Actions column and will cancel the task.</P>
</BODY>
</HTML>

View File

@ -106,7 +106,7 @@
when the target is currently stopped. If successful the target may briefly enter the "running"
state.</P>
<H3><A name="target_step_finish"></A><IMG alt="" src="images/stepout.png"> Finish</H3>
<H3><A name="target_step_out"></A><IMG alt="" src="images/stepout.png"> Step Out</H3>
<P>Allow the current target to finish the current subroutine, pausing after. This is available
when the target is currently stopped. If successful the target may briefly enter the "running"

View File

@ -165,6 +165,16 @@
<LI><CODE>*:8 (RSP+8)</CODE> &mdash; The address pointed to by stack offset 8</LI>
</UL>
<H3><A name="auto_disassembly"></A>Auto-Disassembly</H3>
<P>This action is always available. It automatically disassembles starting at the current
program counter. It applies only when a "Track Program Counter" option is selected in the <A
href="#track_location">Track Location</A> action. Disassembly occurs whenever the program
counter is updated, when the memory at the program counter is updated, or when navigating to a
new context. It terminates at the first branch encountered. Disassembly can be performed
manually using the <A href=
"help/topics/DisassemblerPlugin/Disassembly.htm#Disassemble">Disassemble</A> command.</P>
<H3><A name="auto_sync_cursor_static"></A>Auto-Sync Cursor with Static Listing</H3>
<P>This action is always available, but only on the primary dynamic listing. It configures

View File

@ -81,20 +81,15 @@
<P>This action is available from a module's or section's pop-up menu. It prompts the user to
import the module from the local file system into Ghidra as a static image.</P>
<H3><A name="capture_symbols"></A>Capture Symbols</H3>
<H3><A name="auto_map"></A>Auto-Map</H3>
<P>This action is available when the current trace is associated with a live target and "at the
present," and at least one module is selected. It commands the recorder to copy all symbols
(that the debugger knows about) for the selected modules into the trace. These are often the
same symbols that Ghidra imports from the static image, anyway.</P>
<H3><A name="capture_types"></A>Capture Types</H3>
<P>This action is available when the current trace is associated with a live target and "at the
present," the target presents type information, and at least one module is selected. It
commands the recorder to copy and convert all types (that the debugger knows about) for the
selected modules to Ghidra data types in the trace. These are often the same types that Ghidra
imports from the static image, anyway.</P>
<P>This action is always available. It automatically maps trace memory to static images, using
Module, Section, or Region information. See the <A href="#map_modules">Map Modules</A>, <A
href="#map_sections">Map Sections</A>, and <A href=
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html#map_regions">Map Regions</A>
actions for more information. When enabled, this action will automatically perform the
corresponding action whenever the relevant table is updated. By default, it automatically maps
using Modules.</P>
<H3><A name="map_identically"></A>Map Identically</H3>
@ -115,10 +110,9 @@
<P>This action is available from the modules' or sections' pop-up menu. It searches the tool's
open programs for the selected modules and proposes new mappings. The user can examine and
tweak the proposal before confirming or canceling it. Typically, this is done automatically by
the <A href="help/topics/DebuggerBots/DebuggerBots.html#map_modules">Map Modules</A> debugger
bot. By selecting "Memorize" and confirming the dialog, the user can cause the mapper to re-use
the memorized mapping in future sessions. The memorized module name is saved to the program
database.</P>
the <A href="#auto_map">Auto-Map</A> action. By selecting "Memorize" and confirming the dialog,
the user can cause the mapper to re-use the memorized mapping in future sessions. The memorized
module name is saved to the program database.</P>
<TABLE width="100%">
<TBODY>
@ -140,8 +134,7 @@
<P>This action is analogous to the Map Modules action. It searches the tool's open programs for
blocks matching the selected sections and proposes new mappings. Users who prefer this to Map
Modules should also consider using the <A href=
"help/topics/DebuggerBots/DebuggerBots.html#map_sections">Map Sections</A> debugger bot.</P>
Modules should also consider setting <A href="#auto_map">Auto-Map</A> to use Sections.</P>
<TABLE width="100%">
<TBODY>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -60,12 +60,13 @@
<P>This action is analogous to the Map Modules and Map Sections actions from the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html">Modules window</A>. It searches
the tool's open programs for blocks matching the selected regions and proposes new mappings.
Users who prefer this should also consider using the <A href=
"help/topics/DebuggerBots/DebuggerBots.html#map_regions">Map Regions</A> debugger bot. For the
best result, the selection regions should comprise a complete module. In particular, it should
include the region containing the module's image base, as the offset from this base is used in
scoring the best-matched blocks. Additionally, the region names must include the module's file
name, otherwise the matcher has no means to identify a corresponding program.</P>
Users who prefer this should also consider setting the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#auto_map">Auto-Map</A> action to
use Regions. For the best result, the selection regions should comprise a complete module. In
particular, it should include the region containing the module's image base, because the offset
from this base is used in scoring the best-matched blocks. Additionally, the region names must
include the module's file name, otherwise the matcher has no means to identify a corresponding
program.</P>
<TABLE width="100%">
<TBODY>

View File

@ -25,12 +25,13 @@
<P>A static mapping refers to a range of addresses in the dynamic listing and its corresponding
range in the static listing. These mappings provide a flexible means of mapping imported
images, i.e., Ghidra Program Databases, into a trace. Typically, this table is populated by
automation, e.g., the <A href="help/topics/DebuggerBots/DebuggerBots.html#map_modules">Map
Modules</A> bot, or by higher-level user actions, e.g., the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_modules">Map Modules</A>
action. This under-the-hood static mapping window displays the mappings table, allowing users
or developers to diagnose image mapping issues and manually add mappings, regardless of
reported modules and/or sections. For most users, there is no reason to access this window.</P>
automation, e.g., the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#auto_map">Auto-Map</A> action, or
by manual user actions, e.g., the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_modules">Map Modules</A>.
This under-the-hood static mapping window displays the mappings table, allowing users or
developers to diagnose image mapping issues and manually add mappings, regardless of reported
modules and/or sections. For most users, there is no reason to access this window.</P>
<H2>Table Columns</H2>

View File

@ -334,10 +334,12 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
static class CancelAction extends DockingAction {
static final Icon ICON = Icons.STOP_ICON;
static final String HELP_ANCHOR = "cancel";
public CancelAction(Plugin owner) {
super("Cancel", owner.getName());
setToolBarData(new ToolBarData(ICON));
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
@Override

View File

@ -15,14 +15,22 @@
*/
package ghidra.app.plugin.core.debug.gui.breakpoint;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import db.Transaction;
import docking.DefaultActionContext;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import generic.Unique;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest.TestDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin;
@ -31,6 +39,11 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingService
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.decompile.DecompilePlugin;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.app.plugin.core.functiongraph.graph.*;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
@ -39,6 +52,7 @@ import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.graph.viewer.VisualGraphViewUpdater;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
@ -46,6 +60,7 @@ import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.RunManager;
import ghidra.util.task.TaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
@ -85,6 +100,8 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
public void testCaptureDebuggerBreakpointMarkerPlugin() throws Throwable {
ListingPanel panel = listing.getListingPanel();
moveProviderToItsOwnWindow(listing, 1024, 680);
mb.createTestModel();
modelService.addModel(mb.testModel);
mb.createTestProcessesAndThreads();
@ -114,6 +131,7 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
Msg.debug(this, "Disabling breakpoint");
LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00401c60))));
waitForCondition(() -> lb.computeState() == State.ENABLED);
lb.disable();
waitForCondition(() -> lb.computeState() == State.DISABLED);
@ -133,6 +151,152 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
captureProviderWithScreenShot(listing);
}
protected FGController getFunctionGraphController(FGProvider fgProvider) {
return (FGController) TestUtils.getInstanceField("controller", fgProvider);
}
protected void waitForBusyRunManager(FGController controller) {
FGModel model = controller.getModel();
// long start = System.nanoTime();
waitForSwing();
RunManager runManager = (RunManager) TestUtils.getInstanceField("runManager", model);
waitForCondition(() -> !runManager.isInProgress());
// long end = System.nanoTime();
// long total = end - start;
// Msg.debug(this,
// "Run manager wait time: " + TimeUnit.MILLISECONDS.convert(total, TimeUnit.NANOSECONDS));
}
protected FGComponent getGraphComponent(FGProvider fgProvider) {
FGController controller =
(FGController) TestUtils.getInstanceField("controller", fgProvider);
FGView view = (FGView) TestUtils.getInstanceField("view", controller);
return (FGComponent) TestUtils.getInstanceField("fgComponent", view);
}
protected FGPrimaryViewer getPrimaryGraphViewer(FGProvider fgProvider) {
FGComponent component = getGraphComponent(fgProvider);
if (component == null) {
return null; // this will be null if the graph has been disposed
}
assertNotNull("FG GraphComponent should not be null", component);
return (FGPrimaryViewer) getInstanceField("primaryViewer", component);
}
protected VisualGraphViewUpdater<FGVertex, FGEdge> getGraphUpdater(FGProvider fgProvider) {
FGPrimaryViewer viewer = getPrimaryGraphViewer(fgProvider);
if (viewer == null) {
return null; // this can happen when disposed or not on a function
}
VisualGraphViewUpdater<FGVertex, FGEdge> updater = viewer.getViewUpdater();
assertNotNull(updater);
return updater;
}
protected void waitForAnimation(FGController controller, FGProvider fgProvider) {
VisualGraphViewUpdater<FGVertex, FGEdge> updater = getGraphUpdater(fgProvider);
if (updater == null) {
return; // nothing to wait for; no active graph
}
// long start = System.nanoTime();
waitForSwing();
int tryCount = 3;
while (tryCount++ < 5 && updater.isBusy()) {
waitForConditionWithoutFailing(() -> !updater.isBusy());
}
waitForSwing();
assertFalse(updater.isBusy());
// long end = System.nanoTime();
// long total = end - start;
// Msg.debug(this,
// "Animation wait time: " + TimeUnit.MILLISECONDS.convert(total, TimeUnit.NANOSECONDS));
}
@SuppressWarnings("rawtypes")
private void setGraphLayout(FGProvider fgProvider) {
long start = System.currentTimeMillis();
Object actionManager = getInstanceField("actionManager", fgProvider);
final MultiStateDockingAction<?> action =
(MultiStateDockingAction<?>) getInstanceField("layoutAction", actionManager);
Object minCrossState = null;
List<?> states = action.getAllActionStates();
for (Object state : states) {
if (((ActionState) state).getName().indexOf("Nested Code Layout") != -1) {
minCrossState = state;
break;
}
}
assertNotNull("Could not find min cross layout!", minCrossState);
//@formatter:off
invokeInstanceMethod( "setCurrentActionState",
action,
new Class<?>[] { ActionState.class },
new Object[] { minCrossState });
//@formatter:on
runSwing(() -> action.actionPerformed(new DefaultActionContext()));
// wait for the threaded graph layout code
FGController controller = getFunctionGraphController(fgProvider);
waitForBusyRunManager(controller);
waitForAnimation(controller, fgProvider);
getPrimaryGraphViewer(fgProvider).repaint();
waitForSwing();
long end = System.currentTimeMillis();
Msg.debug(this, "relayout time: " + ((end - start) / 1000.0) + "s");
}
@Test
public void testCaptureDebuggerFunctionGraphBreakpointMargin() throws Throwable {
mb.createTestModel();
modelService.addModel(mb.testModel);
mb.createTestProcessesAndThreads();
TestDebuggerTargetTraceMapper mapper = new TestDebuggerTargetTraceMapper(mb.testProcess1);
TraceRecorder recorder =
modelService.recordTarget(mb.testProcess1, mapper, ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
tool.getProject()
.getProjectData()
.getRootFolder()
.createFile("WinHelloCPP", program, TaskMonitor.DUMMY);
try (Transaction tx = trace.openTransaction("Add Mapping")) {
mappingService.addIdentityMapping(trace, program, Lifespan.nowOn(0), true);
}
waitForValue(() -> mappingService.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(0), mb.addr(0x00401070))));
Msg.debug(this, "Placing breakpoint");
breakpointService.placeBreakpointAt(program, addr(program, 0x00401070), 1,
Set.of(TraceBreakpointKind.SW_EXECUTE), "");
addPlugin(tool, FunctionGraphPlugin.class);
FGProvider fgProvider = waitForComponentProvider(FGProvider.class);
Swing.runNow(() -> tool.showComponentProvider(fgProvider, true));
setGraphLayout(fgProvider);
goTo(tool, program, addr(program, 0x00401070));
captureIsolatedProvider(fgProvider, 700, 700);
}
@Test
public void testCaptureDebuggerDecompilerBreakpointMargin() throws Throwable {
mb.createTestModel();