Merge remote-tracking branch
'origin/GP-1595_Dan_globalControlActions--SQUASHED' (Closes #3742)
|
@ -48,9 +48,7 @@ public interface GdbManager extends AutoCloseable, GdbConsoleOperations, GdbBrea
|
|||
RETURN("return"),
|
||||
STEP("step"),
|
||||
STEPI("stepi", "step-instruction"),
|
||||
UNTIL("until"),
|
||||
/** User-defined */
|
||||
EXTENDED("echo extended-step?", "???"),;
|
||||
UNTIL("until");
|
||||
|
||||
public final String mi2;
|
||||
public final String cli;
|
||||
|
|
|
@ -19,9 +19,8 @@ import java.util.*;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.gdb.manager.*;
|
||||
import agent.gdb.manager.GdbManager.StepCmd;
|
||||
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
|
||||
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning;
|
||||
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
|
||||
import agent.gdb.manager.reason.*;
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
|
@ -43,7 +42,7 @@ public class GdbModelTargetInferior
|
|||
extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer>
|
||||
implements TargetProcess, TargetAggregate, TargetExecutionStateful, TargetAttacher,
|
||||
TargetDeletable, TargetDetachable, TargetKillable, TargetLauncher, TargetResumable,
|
||||
TargetSteppable, GdbModelSelectableObject {
|
||||
TargetSteppable, TargetInterruptible, GdbModelSelectableObject {
|
||||
|
||||
public static final String EXIT_CODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "exit_code";
|
||||
|
||||
|
@ -187,39 +186,18 @@ public class GdbModelTargetInferior
|
|||
return impl.gateFuture(inferior.cont());
|
||||
}
|
||||
|
||||
protected StepCmd convertToGdb(TargetStepKind kind) {
|
||||
switch (kind) {
|
||||
case FINISH:
|
||||
return StepCmd.FINISH;
|
||||
case INTO:
|
||||
return StepCmd.STEPI;
|
||||
case LINE:
|
||||
return StepCmd.STEP;
|
||||
case OVER:
|
||||
return StepCmd.NEXTI;
|
||||
case OVER_LINE:
|
||||
return StepCmd.NEXT;
|
||||
case RETURN:
|
||||
return StepCmd.RETURN;
|
||||
case UNTIL:
|
||||
return StepCmd.UNTIL;
|
||||
case EXTENDED:
|
||||
return StepCmd.EXTENDED;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||
switch (kind) {
|
||||
case SKIP:
|
||||
case EXTENDED:
|
||||
throw new UnsupportedOperationException(kind.name());
|
||||
case ADVANCE: // Why no exec-advance in GDB/MI?
|
||||
// TODO: This doesn't work, since advance requires a parameter
|
||||
return model.gateFuture(inferior.console("advance", CompletesWithRunning.MUST));
|
||||
default:
|
||||
return model.gateFuture(inferior.step(convertToGdb(kind)));
|
||||
return model.gateFuture(inferior.step(GdbModelTargetThread.convertToGdb(kind)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,6 +206,11 @@ public class GdbModelTargetInferior
|
|||
return model.gateFuture(inferior.kill());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> interrupt() {
|
||||
return impl.session.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> attach(TargetAttachable attachable) {
|
||||
GdbModelTargetAttachable mine = impl.assertMine(GdbModelTargetAttachable.class, attachable);
|
||||
|
|
|
@ -187,7 +187,7 @@ public class GdbModelTargetThread
|
|||
}
|
||||
}
|
||||
|
||||
protected StepCmd convertToGdb(TargetStepKind kind) {
|
||||
protected static StepCmd convertToGdb(TargetStepKind kind) {
|
||||
switch (kind) {
|
||||
case FINISH:
|
||||
return StepCmd.FINISH;
|
||||
|
@ -203,8 +203,6 @@ public class GdbModelTargetThread
|
|||
return StepCmd.RETURN;
|
||||
case UNTIL:
|
||||
return StepCmd.UNTIL;
|
||||
case EXTENDED:
|
||||
return StepCmd.EXTENDED;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
@ -214,6 +212,7 @@ public class GdbModelTargetThread
|
|||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||
switch (kind) {
|
||||
case SKIP:
|
||||
case EXTENDED:
|
||||
throw new UnsupportedOperationException(kind.name());
|
||||
case ADVANCE: // Why no exec-advance in GDB/MI?
|
||||
// TODO: This doesn't work, since advance requires a parameter
|
||||
|
|
|
@ -42,6 +42,23 @@ src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-enable-al
|
|||
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-make-effective.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/2leftarrow.png||Nuvola Icons - LGPL 2.1||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/2rightarrow.png||Nuvola Icons - LGPL 2.1||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/disconnect.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/interrupt.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/kill.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/resume.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/skipover.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/stepback.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/stepinto.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/steplast.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/stepout.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/stepover.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/write-disabled.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/write-emulator.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/write-target.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerControlPlugin/images/write-trace.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerDisassemblerPlugin/DebuggerDisassemblerPlugin.html||GHIDRA||||END|
|
||||
|
@ -109,11 +126,6 @@ src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerRegistersPlugin
|
|||
src/main/help/help/topics/DebuggerRegistersPlugin/images/select-registers.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png||Tango Icons - Public Domain||||END|
|
||||
src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END|
|
||||
|
|
|
@ -153,9 +153,9 @@
|
|||
sortgroup="n"
|
||||
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerStateEditingPlugin" text="Editing Machine State"
|
||||
<tocdef id="DebuggerControlPlugin" text="Control and Machine State"
|
||||
sortgroup="n1"
|
||||
target="help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html" />
|
||||
target="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"
|
||||
sortgroup="o"
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
<!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: Editing Machine State</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="plugin"></A>Debugger: Control and Machine State</H1>
|
||||
|
||||
<P>This plugin presents actions for controlling targets and modifying machine state. It
|
||||
provides a drop-down action in the main toolbar for choosing what to control for the current
|
||||
target: The live target, the integrated emulator, or the recorded trace. Only those control
|
||||
actions suitable for the selection are displayed. Machine-state edits throughout the UI are
|
||||
directed accordingly.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The plugin provides several actions, but only certain ones are displayed, depending on the
|
||||
current mode.</P>
|
||||
|
||||
<H3><A name="edit_mode"></A>Edit Mode</H3>
|
||||
|
||||
<P>This action changes the mode for the active trace and, if applicable, its associated live
|
||||
target. It is always displayed, but only available when a trace is active. The possible modes
|
||||
are:</P>
|
||||
|
||||
<UL>
|
||||
<LI><IMG alt="" src="images/write-disabled.png"> Control Target w/Edits Disabled - This
|
||||
presents actions for controlling the live target but rejects all machine-state edits.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-target.png"> Control Target - The default, this presents
|
||||
actions for controlling the live target and directs edits to the live target. To accept
|
||||
edits, the UI must be "live and at the present." If the trace has no associated target, i.e.,
|
||||
it is dead; or if the current view is in the past or includes any steps of emulation, i.e.,
|
||||
it is not at the present; then edits are rejected.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-trace.png"> Control Trace - This presents actions for
|
||||
navigating trace snapshots. It directs all edits to the trace database. Edits are generally
|
||||
always accepted, and they are applied directly to the trace.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-emulator.png"> Control Emulator - This presents actions for
|
||||
controlling the integrated emulator. This can be used for interpolating and extrapolating
|
||||
execution from the current snapshot, without affecting the live target. It directs edits to
|
||||
the integrated emulator by generating patch steps and appending them to the emulation
|
||||
schedule. See the <A href=
|
||||
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
|
||||
action. Essentially, the change is applied in the trace's scratch space, leaving the original
|
||||
recording in tact. Due to implementation details, a thread must be selected, even if edits
|
||||
only affect memory. Additionally, the disassembly context register cannot be modified.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Target Control Actions</H2>
|
||||
|
||||
<P>These actions are visible when the "Control Target" or "Control Target w/Edits Disabled"
|
||||
mode is selected. They are available only when the current trace has an associated live target.
|
||||
Commands are directed to the focused object or a suitable substitute.</P>
|
||||
|
||||
<H3><A name="target_resume"></A><IMG alt="" src="images/resume.png"> Resume</H3>
|
||||
|
||||
<P>Allow the current target to resume execution. Other debuggers may call this "continue" or
|
||||
"go." If successful, the target enters the "running" state until/if it is interrupted or
|
||||
terminated. This is available when the target is currently stopped.</P>
|
||||
|
||||
<H3><A name="target_interrupt"></A><IMG alt="" src="images/interrupt.png"> Interrupt</H3>
|
||||
|
||||
<P>Interrupt the current target's execution. Other debuggers may call this "break," "suspend,"
|
||||
or "stop." If successful, the target enters the "stopped" state. This is available when the
|
||||
target is currently running.</P>
|
||||
|
||||
<H3><A name="target_kill"></A><IMG alt="" src="images/kill.png"> Kill</H3>
|
||||
|
||||
<P>Kill the current target. Other debuggers may call this "terminate" or "stop." By default,
|
||||
this will consequently close the current trace. If successful, the target enters the
|
||||
"terminated" state. This is always available for a live target.</P>
|
||||
|
||||
<H3><A name="target_disconnect"></A><IMG alt="" src="images/disconnect.png"> Disconnect</H3>
|
||||
|
||||
<P>Disconnect from the current target's debugger. This usually causes the connected debugger to
|
||||
terminate and likely kill its targets. By default, this will consequently close the trace for
|
||||
all affected targets. This is always available for a live target.</P>
|
||||
|
||||
<H3><A name="target_step_into"></A><IMG alt="" src="images/stepinto.png"> Step Into</H3>
|
||||
|
||||
<P>Step the current target to the next instruction. This is available when the target is
|
||||
currently stopped. If successful the target may briefly enter the "running" state.</P>
|
||||
|
||||
<H3><A name="target_step_over"></A><IMG alt="" src="images/stepover.png"> Step Over</H3>
|
||||
|
||||
<P>Step the current target to the next instruction in the current subroutine. This is available
|
||||
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>
|
||||
|
||||
<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"
|
||||
state.</P>
|
||||
|
||||
<H3><A name="target_step_last"></A><IMG alt="" src="images/steplast.png"> Step Repeat Last /
|
||||
Extended</H3>
|
||||
|
||||
<P>Perform a target-defined step, often the last (possibly custom or extended) step. This is
|
||||
available when the target is currently stopped. If successful the target may briefly enter the
|
||||
"running" state.</P>
|
||||
|
||||
<H2>Trace Navigation Actions</H2>
|
||||
|
||||
<P>These actions are visible when the "Control Trace" mode is selected. They are available when
|
||||
there is an active trace.</P>
|
||||
|
||||
<H3><A name="trace_snap_backward"></A><IMG alt="" src="images/2leftarrow.png" width="16">
|
||||
Snapshot Backward</H3>
|
||||
|
||||
<P>This navigates the trace backward one snapshot. All windows displaying machine state will
|
||||
show that recorded in the current snapshot. This is available only when there exists a snapshot
|
||||
previous to the current.</P>
|
||||
|
||||
<H3><A name="trace_snap_forward"></A><IMG alt="" src="images/2rightarrow.png" width="16">
|
||||
Snapshot Forward</H3>
|
||||
|
||||
<P>This nativates the trace forward one snapshot. All windows displaying machine state will
|
||||
show that recorded in the current snapshot. This is available only when there exists a snapshot
|
||||
after the current.</P>
|
||||
|
||||
<H2>Emulation Actions</H2>
|
||||
|
||||
<P>These actions are visible when the "Control Emulator" mode is selected. They are available
|
||||
when there is an active trace. Commands are directed to the integrated emulator for the current
|
||||
trace.</P>
|
||||
|
||||
<H3><A name="emu_resume"></A><IMG alt="" src="images/resume.png"> Resume</H3>
|
||||
|
||||
<P>Allow the emulator to resume execution. This is available when no other integrated emulator
|
||||
is running. A monitor dialog is presented during execution, but the GUI remains responsive.
|
||||
Only one emulator can be run from the GUI at a time. If the current snapshot represents the
|
||||
live target, the emulator may read additional machine state from the live target. For
|
||||
non-contrived programs, the emulator will likely be interrupted, since some instructions and
|
||||
all system calls are not yet supported. It could also start executing from unmapped memory or
|
||||
enter a infinite loop. If it seems to carry on too long, interrupt it and examine.</P>
|
||||
|
||||
<H3><A name="emu_interrupt"></A><IMG alt="" src="images/interrupt.png"> Interrupt</H3>
|
||||
|
||||
<P>Interrupt the currently-running emulator. This is available when any integrated emulator is
|
||||
running. In most cases, this is the emulator for the current trace, but it may not be.
|
||||
Canceling the dialog for an emulation task will also interrupt the emulator. Upon interruption,
|
||||
the emulation schedule is recorded and the snapshot displayed in the GUI.</P>
|
||||
|
||||
<H3><A name="emu_step_back"></A><IMG alt="" src="images/stepback.png"> Step Back</H3>
|
||||
|
||||
<P>Steps the emulator to the previous instruction, by flow. This is available when the current
|
||||
snapshot includes emulated steps. This operates by repeating the current emulation schedule
|
||||
with one less step. Thus, it effectively steps backward, heeding the proper control flow. While
|
||||
not common, if emulation to the current snapshot took a good bit of time, then stepping
|
||||
backward will likely take about the same amount of time.</P>
|
||||
|
||||
<H3><A name="emu_step_into"></A><IMG alt="" src="images/stepinto.png"> Step Into</H3>
|
||||
|
||||
<P>Steps the emulator to the next instruction, by flow. This is available when there is an
|
||||
active thread. At worst, this operates by repeating the current emulation schedule with one
|
||||
more step of the current thread. In most cases, this can use the cached emulator for the
|
||||
current snapshot and advanced it a single step. Note that "Step Over" is not currently
|
||||
supported by the emulator.</P>
|
||||
|
||||
<H3><A name="emu_skip_over"></A><IMG alt="" src="images/skipover.png"> Skip Over</H3>
|
||||
|
||||
<P>Skips the emulator over the current instruction, ignoring flow. This is available when there
|
||||
is an active thread. At worst, this operates by repeating the current emulation schedule with
|
||||
an added skip for the current thread. In most cases, this can use the cached emulator for the
|
||||
current snapshot and advance it by skipping once. Note that this <EM>skips</EM> the
|
||||
instruction. Thus, when used on a "call" instruction, all effects and side-effects of the
|
||||
subroutine are averted. This is not the same as "<EM>Step</EM> Over," which is not currently
|
||||
supported by the emulator.</P>
|
||||
|
||||
<H2>Recommendations</H2>
|
||||
|
||||
<P>Write Target is the default mode, because in most cases, this is the desired behavior. When
|
||||
the target dies, Write Target essentially means Read Only. For the most part, modifying the
|
||||
recording itself is discouraged. There are few reasons, perhaps including 1) Hand-generating an
|
||||
experimental trace; 2) Generating a trace from a script, e.g., importing an event log,
|
||||
recording an emulated target; 3) Patching in state missed by the original recording. Be wary
|
||||
when switching from emulation to trace mode, since you could accidentally edit scratch space in
|
||||
the trace. It is allowed but can produce non-intuitive and erroneous results, since the
|
||||
emulator caches its snapshots in scratch space.</P>
|
||||
|
||||
<P>To prevent accidental edits to a live target, use the Control Target w/Edits Disabled mode.
|
||||
This is only effective against edits from the UI, and only when all plugins and scripts use the
|
||||
state editing service. This cannot prevent a component from accessing Ghidra's Target API to
|
||||
modify a target. Nor can it prevent edits via the connected debugger's command-line
|
||||
interpreter. The following components all use the service: <A href=
|
||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A>, <A href=
|
||||
"help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html">Memory (Dynamic
|
||||
Bytes)</A>, <A href=
|
||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>, and <A href=
|
||||
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A>.</P>
|
||||
</BODY>
|
||||
</HTML>
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 556 B |
After Width: | Height: | Size: 211 B |
After Width: | Height: | Size: 210 B |
After Width: | Height: | Size: 396 B |
After Width: | Height: | Size: 497 B |
After Width: | Height: | Size: 428 B |
After Width: | Height: | Size: 404 B |
After Width: | Height: | Size: 410 B |
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 428 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 826 B |
After Width: | Height: | Size: 694 B |
After Width: | Height: | Size: 865 B |
|
@ -58,9 +58,9 @@
|
|||
failed, the first address in the failed range is displayed with a pink background. Otherwise,
|
||||
up-to-date contents are displayed with the default background color.</P>
|
||||
|
||||
<P>The dynamic listing supports editing memory via the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
||||
Plugin and Service</A>. Such edits are performed as usual: Via the <A href=
|
||||
<P>The dynamic listing supports editing memory. See <A href=
|
||||
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State</A>.
|
||||
Such edits are performed as usual: Via the <A href=
|
||||
"help/topics/AssemblerPlugin/Assembler.htm">Patch</A> actions, or by pasting byte strings.
|
||||
These edits may be directed toward a live target, the trace, or the emulator.</P>
|
||||
|
||||
|
|
|
@ -44,13 +44,12 @@
|
|||
failed, the first address in the failed range is displayed with a pink background. Otherwise,
|
||||
up-to-date contents are displayed with the default background color.</P>
|
||||
|
||||
<P>The dynamic listing supports editing memory via the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
||||
Plugin and Service</A>. Such edits are performed as usual: Toggling edits and typing into the
|
||||
editor, or by pasting byte strings. These edits may be directed toward a live target, the
|
||||
trace, or the emulator. <B>NOTE:</B> Please be wary of hand-typing large edits into the
|
||||
emulator, since every keystroke may produce a unique scratch snapshot. It is better to paste
|
||||
such edits instead.</P>
|
||||
<P>The dynamic listing supports editing memory. See<A href=
|
||||
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State</A>.
|
||||
Such edits are performed as usual: Toggling edits and typing into the editor, or by pasting
|
||||
byte strings. These edits may be directed toward a live target, the trace, or the emulator.
|
||||
<B>NOTE:</B> Please be wary of hand-typing large edits into the emulator, since every keystroke
|
||||
may produce a unique scratch snapshot. It is better to paste such edits instead.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
|
||||
<P>Interrupt the current target's execution.</P>
|
||||
|
||||
<H3><A name="resume"></A><IMG alt="" src="images/continue.png"> Resume (Continue, Go)</H3>
|
||||
<H3><A name="resume"></A><IMG alt="" src="images/resume.png"> Resume (Continue, Go)</H3>
|
||||
|
||||
<P>Allow the current target to resume execution.</P>
|
||||
|
||||
|
@ -142,7 +142,7 @@
|
|||
|
||||
<P>Allow the current target to finish the current subroutine, pausing after.</P>
|
||||
|
||||
<H3><A name="step_last"></A><IMG alt="" src="images/stepout.png"> Step Last / Extended</H3>
|
||||
<H3><A name="step_last"></A><IMG alt="" src="images/steplast.png"> Step Last / Extended</H3>
|
||||
|
||||
<P>Perform a target-defined step, often the last (possibly custom or extended) step.</P>
|
||||
|
||||
|
|
|
@ -46,9 +46,7 @@
|
|||
<LI>Value - the value of the register as recorded in the trace. When the value refers to a
|
||||
valid memory offset, right-clicking the row allows the user to navigate to that offset in a
|
||||
selected memory space. This field is user modifiable when the <B>Enable Edits</B> toggle is
|
||||
on, and the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
||||
reports the register as modifiable. Edits may be directed toward a live target, the trace, or
|
||||
on, and the register is modifiable. Edits may be directed toward a live target, the trace, or
|
||||
the emulator. Values changed by the last event are displayed in <FONT color=
|
||||
"red">red</FONT>.</LI>
|
||||
|
||||
|
@ -102,9 +100,9 @@
|
|||
|
||||
<P>This toggle is a write protector for machine state. To modify register values, this toggle
|
||||
must be enabled. Edits are directed according the to <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||
column cannot be edited, yet.</P>
|
||||
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State
|
||||
Plugin</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr" column
|
||||
cannot be edited, yet.</P>
|
||||
|
||||
<H3><A name="clone_window"></A>Clone Window</H3>
|
||||
|
||||
|
|
|
@ -1,87 +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: Editing Machine State</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="plugin"></A>Debugger: Editing Machine State</H1>
|
||||
|
||||
<P>This plugin controls the modification of machine state. It provides a multi-state action in
|
||||
the main toolbar for controlling the editing mode of each trace and associated target. It is
|
||||
backed by a corresponding service plugin which manages the editing modes and dispatches
|
||||
machine-state edits accordingly. Scripts can also use the service to perform machine-state
|
||||
edits that behave consistently with the rest of the UI.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The plugin provides a single action:</P>
|
||||
|
||||
<H3><A name="edit_mode"></A>Edit Mode</H3>
|
||||
|
||||
<P>This action is available whenever a trace is active. It changes the machine-state editing
|
||||
mode for the active trace and, if applicable, its associated target. The possible modes
|
||||
are:</P>
|
||||
|
||||
<UL>
|
||||
<LI><IMG alt="" src="images/write-disabled.png"> Read-Only - Rejects all edits.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-target.png"> Write Target - The default, this directs edits
|
||||
to the live target. To accept changes, the UI must be "live and at the present." If the trace
|
||||
has no associated target, i.e., it is dead; or if the current view is in the past or includes
|
||||
any steps of emulation, i.e., it is not at the present; then edits are rejected.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-trace.png"> Write Trace - Directs all edits to the trace.
|
||||
Edits are generally always accepted, and they are applied directly to the trace.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-emulator.png"> Write Emulator - Materializes edits via
|
||||
emulation. Instead of editing the trace, this generates a patch and appends it to the current
|
||||
coordinates' emulation schedule. See the <A href=
|
||||
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
|
||||
action. Essentially, the change is applied in the trace's scratch space, leaving the original
|
||||
recording in tact. Due to implementation details, a thread must be selected, even if edits
|
||||
only affect memory. Additionally, the disassembly context register cannot be modified.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Recommendations</H2>
|
||||
|
||||
<P>Write Target is the default mode, because in most cases, this is the desired behavior. When
|
||||
the target dies, Write Target essentially means Read Only. For the most part, modifying the
|
||||
recording itself is discouraged. There are few reasons, perhaps including 1) Hand-generating an
|
||||
experimental trace; 2) Generating a trace from a script, e.g., importing an event log,
|
||||
recording an emulated target; 3) Patching in state missed by the original recording. More often
|
||||
than not, when experimenting with the emulator, the mode should be Write Emulator. Using Write
|
||||
Trace with the emulator will almost certainly result in issues with cache staleness.</P>
|
||||
|
||||
<P>Some background and an example: To display emulated machine state, the emulator executes a
|
||||
specified schedule and writes the resulting state into the trace's scratch space, keyed by the
|
||||
schedule. Suppose you emulate a step forward but then realize that some state was incorrect, or
|
||||
you just want to try the same step with an alternative initial state. If you step back then
|
||||
edit the trace and then repeat the step forward, the UI will simply recall the cached snapshot,
|
||||
rendering the state change ineffective. Instead, use Write Emulator to edit the state and then
|
||||
step forward. Because the patch is encoded in the emulation schedule, the UI will not recall
|
||||
the stale snapshot. Instead the emulator will execute the new schedule and generate a new
|
||||
scratch snapshot. Furthermore, the original trace recording remains in tact, while the modified
|
||||
state is stored in scratch space with the schedule explaining where it came from. Still better,
|
||||
the first scratch snapshot (for the step taken without first modifying the state) also remains
|
||||
in tact, and the two can be <A href=
|
||||
"help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html">compared</A>.</P>
|
||||
|
||||
<P>To prevent edits to a live target, use the Read-Only mode. This will prevent most accidental
|
||||
edits. This is only effective against edits from the UI, and only when all plugins and scripts
|
||||
use the state editing service. This cannot prevent a component from accessing Ghidra's Target
|
||||
API to modify a target. Nor can it prevent edits via the connected debugger's command-line
|
||||
interpreter. The following components all use the service: <A href=
|
||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A>, <A href=
|
||||
"help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html">Memory (Dynamic
|
||||
Bytes)</A>, <A href=
|
||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>, and <A href=
|
||||
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A>.</P>
|
||||
</BODY>
|
||||
</HTML>
|
Before Width: | Height: | Size: 397 B |
Before Width: | Height: | Size: 559 B |
Before Width: | Height: | Size: 414 B |
Before Width: | Height: | Size: 699 B |
|
@ -74,11 +74,9 @@
|
|||
|
||||
<LI>Value - the raw bytes of the watched buffer. If the expression is a register, then this
|
||||
is its hexadecimal value. This field is user modifiable when the <B>Enable Edits</B> toggle
|
||||
is on, and the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
||||
reports the register as modifiable. Edits may be directed toward a live target, the trace, or
|
||||
the emulator. If the value has changed since the last navigation event, this cell is rendered
|
||||
in <FONT color="red">red</FONT>.</LI>
|
||||
is on, and the variable is modifiable. Edits may be directed toward a live target, the trace,
|
||||
or the emulator. If the value has changed since the last navigation event, this cell is
|
||||
rendered in <FONT color="red">red</FONT>.</LI>
|
||||
|
||||
<LI>Type - the user-modifiable type of the watch. Note the type is not marked up in the
|
||||
trace. Clicking the Apply Data Type action will apply it to the current trace, if
|
||||
|
@ -157,9 +155,9 @@
|
|||
|
||||
<P>This toggle is a write protector for machine state. To modify a watch's value, this toggle
|
||||
must be enabled. Edits are directed according the to <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||
column cannot be edited, yet.</P>
|
||||
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State
|
||||
Plugin</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr" column
|
||||
cannot be edited, yet.</P>
|
||||
|
||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||
|
||||
|
|
|
@ -709,7 +709,7 @@ public class DebuggerCoordinates {
|
|||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return recorder != null;
|
||||
return recorder != null && recorder.isRecording();
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
|
|
|
@ -22,15 +22,12 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.*;
|
||||
import docking.menu.ActionState;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
|
@ -51,7 +48,6 @@ import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
|
|||
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
|
@ -183,10 +179,10 @@ public interface DebuggerResources {
|
|||
ImageIcon ICON_EDIT_MODE_WRITE_EMULATOR =
|
||||
ResourceManager.loadImage("images/write-emulator.png");
|
||||
|
||||
String NAME_EDIT_MODE_READ_ONLY = "Read Only";
|
||||
String NAME_EDIT_MODE_WRITE_TARGET = "Write Target";
|
||||
String NAME_EDIT_MODE_WRITE_TRACE = "Write Trace";
|
||||
String NAME_EDIT_MODE_WRITE_EMULATOR = "Write Emulator";
|
||||
String NAME_EDIT_MODE_READ_ONLY = "Control Target w/ Edits Disabled";
|
||||
String NAME_EDIT_MODE_WRITE_TARGET = "Control Target";
|
||||
String NAME_EDIT_MODE_WRITE_TRACE = "Control Trace";
|
||||
String NAME_EDIT_MODE_WRITE_EMULATOR = "Control Emulator";
|
||||
|
||||
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
||||
|
||||
|
@ -2089,26 +2085,6 @@ public interface DebuggerResources {
|
|||
}
|
||||
}
|
||||
|
||||
interface EditModeAction {
|
||||
String NAME = "Edit Mode";
|
||||
String DESCRIPTION = "Choose what to edit in dynamic views";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
Icon ICON = StateEditingMode.values()[0].icon;
|
||||
String HELP_ANCHOR = "edit_mode";
|
||||
|
||||
static MultiStateActionBuilder<StateEditingMode> builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new MultiStateActionBuilder<StateEditingMode>(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarGroup(GROUP)
|
||||
.toolBarIcon(ICON_EDIT_MODE_WRITE_TARGET)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR))
|
||||
.addStates(Stream.of(StateEditingMode.values())
|
||||
.map(m -> new ActionState<>(m.name, m.icon, m))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
interface LimitToCurrentSnapAction {
|
||||
String NAME = "Limit to Current Snap";
|
||||
String DESCRIPTION = "Choose whether displayed objects must be alive at the current snap";
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
/* ###
|
||||
* 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.editing;
|
||||
|
||||
import docking.menu.ActionState;
|
||||
import docking.menu.MultiStateDockingAction;
|
||||
import docking.widgets.EventTrigger;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.*;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EditModeAction;
|
||||
import ghidra.app.services.DebuggerStateEditingService;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger machine-state Editing GUI",
|
||||
description = "GUI to edit target, trace, and/or emulation machine state",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
TraceClosedPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerStateEditingService.class,
|
||||
})
|
||||
public class DebuggerStateEditingPlugin extends AbstractDebuggerPlugin {
|
||||
|
||||
private final StateEditingModeChangeListener listenerForModeChanges = this::modeChanged;
|
||||
|
||||
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
|
||||
protected MultiStateDockingAction<StateEditingMode> actionEditMode;
|
||||
|
||||
// @AutoServiceConsumed // via method
|
||||
private DebuggerStateEditingService editingService;
|
||||
|
||||
public DebuggerStateEditingPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
createActions();
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionEditMode = EditModeAction.builder(this)
|
||||
.enabled(false)
|
||||
.enabledWhen(c -> current.getTrace() != null)
|
||||
.onActionStateChanged(this::activateEditMode)
|
||||
.buildAndInstall(tool);
|
||||
}
|
||||
|
||||
protected void activateEditMode(ActionState<StateEditingMode> state, EventTrigger trigger) {
|
||||
if (current.getTrace() == null) {
|
||||
return;
|
||||
}
|
||||
if (editingService == null) {
|
||||
return;
|
||||
}
|
||||
editingService.setCurrentMode(current.getTrace(), state.getUserData());
|
||||
// TODO: Limit selectable modes?
|
||||
// No sense showing Write Target, if the trace can never be live, again....
|
||||
}
|
||||
|
||||
private void modeChanged(Trace trace, StateEditingMode mode) {
|
||||
if (current.getTrace() == trace) {
|
||||
refreshActionMode();
|
||||
}
|
||||
}
|
||||
|
||||
protected void coordinatesActivated(DebuggerCoordinates coords) {
|
||||
current = coords;
|
||||
refreshActionMode();
|
||||
// tool.contextChanged(null);
|
||||
}
|
||||
|
||||
private StateEditingMode computeCurrentEditingMode() {
|
||||
if (editingService == null) {
|
||||
return StateEditingMode.READ_ONLY;
|
||||
}
|
||||
if (current.getTrace() == null) {
|
||||
return StateEditingMode.READ_ONLY;
|
||||
}
|
||||
return editingService.getCurrentMode(current.getTrace());
|
||||
}
|
||||
|
||||
private void refreshActionMode() {
|
||||
actionEditMode.setCurrentActionStateByUserData(computeCurrentEditingMode());
|
||||
}
|
||||
|
||||
protected void traceClosed(Trace trace) {
|
||||
if (current.getTrace() == trace) {
|
||||
current = DebuggerCoordinates.NOWHERE;
|
||||
}
|
||||
refreshActionMode();
|
||||
// tool.contextChanged(null);
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
protected void setEditingService(DebuggerStateEditingService editingService) {
|
||||
if (this.editingService != null) {
|
||||
this.editingService.removeModeChangeListener(listenerForModeChanges);
|
||||
}
|
||||
this.editingService = editingService;
|
||||
if (this.editingService != null) {
|
||||
this.editingService.addModeChangeListener(listenerForModeChanges);
|
||||
}
|
||||
refreshActionMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
else if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
traceClosed(ev.getTrace());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,19 +43,22 @@ import ghidra.async.AsyncLazyMap;
|
|||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.pcode.emu.PcodeMachine.AccessKind;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.schedule.CompareResult;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.model.time.schedule.*;
|
||||
import ghidra.trace.model.time.schedule.Scheduler.RunResult;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -139,27 +142,19 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
|
||||
protected static class CachedEmulator {
|
||||
final DebuggerPcodeMachine<?> emulator;
|
||||
protected abstract class AbstractEmulateTask<T> extends Task {
|
||||
protected final CompletableFuture<T> future = new CompletableFuture<>();
|
||||
|
||||
public CachedEmulator(DebuggerPcodeMachine<?> emulator) {
|
||||
this.emulator = emulator;
|
||||
public AbstractEmulateTask(String title, boolean hasProgress) {
|
||||
super(title, true, hasProgress, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected class EmulateTask extends Task {
|
||||
protected final CacheKey key;
|
||||
protected final CompletableFuture<Long> future = new CompletableFuture<>();
|
||||
|
||||
public EmulateTask(CacheKey key) {
|
||||
super("Emulate " + key.time + " in " + key.trace, true, true, false, false);
|
||||
this.key = key;
|
||||
}
|
||||
protected abstract T compute(TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
future.complete(doEmulate(key, monitor));
|
||||
future.complete(compute(monitor));
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
future.completeExceptionally(e);
|
||||
|
@ -172,6 +167,36 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
|
||||
protected class EmulateTask extends AbstractEmulateTask<Long> {
|
||||
protected final CacheKey key;
|
||||
|
||||
public EmulateTask(CacheKey key) {
|
||||
super("Emulate " + key.time + " in " + key.trace, true);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long compute(TaskMonitor monitor) throws CancelledException {
|
||||
return doEmulate(key, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
protected class RunEmulatorTask extends AbstractEmulateTask<EmulationResult> {
|
||||
private final CacheKey from;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public RunEmulatorTask(CacheKey from, Scheduler scheduler) {
|
||||
super("Emulating...", false);
|
||||
this.from = from;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EmulationResult compute(TaskMonitor monitor) throws CancelledException {
|
||||
return doRun(from, monitor, scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
protected DebuggerPcodeEmulatorFactory emulatorFactory =
|
||||
new BytesDebuggerPcodeEmulatorFactory();
|
||||
|
||||
|
@ -181,6 +206,53 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
new AsyncLazyMap<>(new HashMap<>(), this::doBackgroundEmulate)
|
||||
.forgetErrors((key, t) -> true)
|
||||
.forgetValues((key, l) -> true);
|
||||
protected final Map<CachedEmulator, Integer> busy = new LinkedHashMap<>();
|
||||
protected final ListenerSet<EmulatorStateListener> stateListeners =
|
||||
new ListenerSet<>(EmulatorStateListener.class);
|
||||
|
||||
class BusyEmu implements AutoCloseable {
|
||||
private final CachedEmulator ce;
|
||||
|
||||
private BusyEmu(CachedEmulator ce) {
|
||||
this.ce = ce;
|
||||
boolean fire = false;
|
||||
synchronized (busy) {
|
||||
Integer count = busy.get(ce);
|
||||
if (count == null) {
|
||||
busy.put(ce, 1);
|
||||
fire = true;
|
||||
}
|
||||
else {
|
||||
busy.put(ce, count + 1);
|
||||
}
|
||||
}
|
||||
if (fire) {
|
||||
stateListeners.fire.running(ce);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
boolean fire = false;
|
||||
synchronized (busy) {
|
||||
int count = busy.get(ce);
|
||||
if (count == 1) {
|
||||
busy.remove(ce);
|
||||
fire = true;
|
||||
}
|
||||
else {
|
||||
busy.put(ce, count - 1);
|
||||
}
|
||||
}
|
||||
if (fire) {
|
||||
stateListeners.fire.stopped(ce);
|
||||
}
|
||||
}
|
||||
|
||||
public BusyEmu dup() {
|
||||
return new BusyEmu(ce);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
|
@ -429,17 +501,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
|
||||
@Override
|
||||
public CompletableFuture<Long> backgroundEmulate(TracePlatform platform, TraceSchedule time) {
|
||||
Trace trace = platform.getTrace();
|
||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot emulate a trace unless it's opened in the tool.");
|
||||
}
|
||||
requireOpen(platform.getTrace());
|
||||
if (time.isSnapOnly()) {
|
||||
return CompletableFuture.completedFuture(time.getSnap());
|
||||
}
|
||||
return requests.get(new CacheKey(platform, time));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<EmulationResult> backgroundRun(TracePlatform platform,
|
||||
TraceSchedule from, Scheduler scheduler) {
|
||||
requireOpen(platform.getTrace());
|
||||
CacheKey key = new CacheKey(platform, from);
|
||||
RunEmulatorTask task = new RunEmulatorTask(key, scheduler);
|
||||
tool.execute(task, 500);
|
||||
return task.future;
|
||||
}
|
||||
|
||||
protected TraceSnapshot findScratch(Trace trace, TraceSchedule time) {
|
||||
Collection<? extends TraceSnapshot> exist =
|
||||
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
||||
|
@ -461,13 +539,43 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
return snapshot;
|
||||
}
|
||||
|
||||
protected long doEmulate(CacheKey key, TaskMonitor monitor) throws CancelledException {
|
||||
protected void installBreakpoints(Trace trace, long snap, DebuggerPcodeMachine<?> emu) {
|
||||
Range<Long> span = Range.singleton(snap);
|
||||
TraceBreakpointManager bm = trace.getBreakpointManager();
|
||||
for (AddressSpace as : trace.getBaseAddressFactory().getAddressSpaces()) {
|
||||
for (TraceBreakpoint bpt : bm.getBreakpointsIntersecting(span,
|
||||
new AddressRangeImpl(as.getMinAddress(), as.getMaxAddress()))) {
|
||||
if (!bpt.isEnabled(snap)) {
|
||||
continue;
|
||||
}
|
||||
Set<TraceBreakpointKind> kinds = bpt.getKinds();
|
||||
boolean isExecute =
|
||||
kinds.contains(TraceBreakpointKind.HW_EXECUTE) ||
|
||||
kinds.contains(TraceBreakpointKind.SW_EXECUTE);
|
||||
boolean isRead = kinds.contains(TraceBreakpointKind.READ);
|
||||
boolean isWrite = kinds.contains(TraceBreakpointKind.WRITE);
|
||||
if (isExecute) {
|
||||
emu.addBreakpoint(bpt.getMinAddress(), "1:1");
|
||||
}
|
||||
if (isRead && isWrite) {
|
||||
emu.addAccessBreakpoint(bpt.getRange(), AccessKind.RW);
|
||||
}
|
||||
else if (isRead) {
|
||||
emu.addAccessBreakpoint(bpt.getRange(), AccessKind.R);
|
||||
}
|
||||
else if (isWrite) {
|
||||
emu.addAccessBreakpoint(bpt.getRange(), AccessKind.W);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected BusyEmu doEmulateFromCached(CacheKey key, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
Trace trace = key.trace;
|
||||
TracePlatform platform = key.platform;
|
||||
TraceSchedule time = key.time;
|
||||
|
||||
CachedEmulator ce;
|
||||
DebuggerPcodeMachine<?> emu;
|
||||
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
|
||||
if (ancestor != null) {
|
||||
CacheKey prevKey = ancestor.getKey();
|
||||
|
@ -479,28 +587,32 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
|
||||
// TODO: Handle errors, and add to proper place in cache?
|
||||
// TODO: Finish partially-executed instructions?
|
||||
ce = ancestor.getValue();
|
||||
emu = ce.emulator;
|
||||
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
|
||||
createRegisterSpaces(trace, time, monitor);
|
||||
monitor.setMessage("Emulating");
|
||||
time.finish(trace, prevKey.time, emu, monitor);
|
||||
try (BusyEmu be = new BusyEmu(ancestor.getValue())) {
|
||||
DebuggerPcodeMachine<?> emu = be.ce.emulator();
|
||||
|
||||
emu.clearAllInjects();
|
||||
emu.clearAccessBreakpoints();
|
||||
emu.setSuspended(false);
|
||||
|
||||
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
|
||||
createRegisterSpaces(trace, time, monitor);
|
||||
monitor.setMessage("Emulating");
|
||||
time.finish(trace, prevKey.time, emu, monitor);
|
||||
return be.dup();
|
||||
}
|
||||
}
|
||||
else {
|
||||
emu = emulatorFactory.create(tool, platform, time.getSnap(),
|
||||
modelService == null ? null : modelService.getRecorder(trace));
|
||||
ce = new CachedEmulator(emu);
|
||||
DebuggerPcodeMachine<?> emu = emulatorFactory.create(tool, platform, time.getSnap(),
|
||||
modelService == null ? null : modelService.getRecorder(trace));
|
||||
try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu))) {
|
||||
monitor.initialize(time.totalTickCount());
|
||||
createRegisterSpaces(trace, time, monitor);
|
||||
monitor.setMessage("Emulating");
|
||||
time.execute(trace, emu, monitor);
|
||||
return be.dup();
|
||||
}
|
||||
TraceSnapshot destSnap;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
|
||||
destSnap = findScratch(trace, time);
|
||||
emu.writeDown(platform, destSnap.getKey(), time.getSnap());
|
||||
}
|
||||
}
|
||||
|
||||
protected void cacheEmulator(CacheKey key, CachedEmulator ce) {
|
||||
synchronized (cache) {
|
||||
cache.put(key, ce);
|
||||
eldest.add(key);
|
||||
|
@ -511,8 +623,35 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
cache.remove(expired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return destSnap.getKey();
|
||||
protected TraceSnapshot writeToScratch(CacheKey key, CachedEmulator ce) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(key.trace, "Emulate")) {
|
||||
TraceSnapshot destSnap = findScratch(key.trace, key.time);
|
||||
ce.emulator().writeDown(key.platform, destSnap.getKey(), key.time.getSnap());
|
||||
return destSnap;
|
||||
}
|
||||
}
|
||||
|
||||
protected long doEmulate(CacheKey key, TaskMonitor monitor) throws CancelledException {
|
||||
try (BusyEmu be = doEmulateFromCached(key, monitor)) {
|
||||
TraceSnapshot destSnap = writeToScratch(key, be.ce);
|
||||
cacheEmulator(key, be.ce);
|
||||
return destSnap.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
protected EmulationResult doRun(CacheKey key, TaskMonitor monitor, Scheduler scheduler)
|
||||
throws CancelledException {
|
||||
try (BusyEmu be = doEmulateFromCached(key, monitor)) {
|
||||
installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
|
||||
TraceThread eventThread = key.time.getEventThread(key.trace);
|
||||
RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor);
|
||||
key = new CacheKey(key.platform, key.time.advanced(result.schedule()));
|
||||
TraceSnapshot destSnap = writeToScratch(key, be.ce);
|
||||
cacheEmulator(key, be.ce);
|
||||
return new RecordEmulationResult(key.time, destSnap.getKey(), result.error());
|
||||
}
|
||||
}
|
||||
|
||||
protected void createRegisterSpaces(Trace trace, TraceSchedule time, TaskMonitor monitor) {
|
||||
|
@ -529,25 +668,53 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
Trace trace = platform.getTrace();
|
||||
protected void requireOpen(Trace trace) {
|
||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot emulate a trace unless it's opened in the tool.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
requireOpen(platform.getTrace());
|
||||
if (time.isSnapOnly()) {
|
||||
return time.getSnap();
|
||||
}
|
||||
return doEmulate(new CacheKey(platform, time), monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmulationResult run(TracePlatform platform, TraceSchedule from, TaskMonitor monitor,
|
||||
Scheduler scheduler) throws CancelledException {
|
||||
Trace trace = platform.getTrace();
|
||||
requireOpen(trace);
|
||||
return doRun(new CacheKey(platform, from), monitor, scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
|
||||
CachedEmulator ce =
|
||||
cache.get(new CacheKey(trace.getPlatformManager().getHostPlatform(), time));
|
||||
return ce == null ? null : ce.emulator;
|
||||
return ce == null ? null : ce.emulator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CachedEmulator> getBusyEmulators() {
|
||||
synchronized (busy) {
|
||||
return List.copyOf(busy.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStateListener(EmulatorStateListener listener) {
|
||||
stateListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeStateListener(EmulatorStateListener listener) {
|
||||
stateListeners.remove(listener);
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
|
|
|
@ -523,7 +523,6 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
throws IOException {
|
||||
String traceName = nameTrace(target);
|
||||
Trace trace = new DBTrace(traceName, mapper.getTraceCompilerSpec(), this);
|
||||
//DefaultTraceRecorder recorder = new DefaultTraceRecorder(this, trace, target, mapper);
|
||||
TraceRecorder recorder = mapper.startRecording(this, trace);
|
||||
trace.release(this); // The recorder now owns it (on behalf of the service)
|
||||
return recorder;
|
||||
|
|
|
@ -47,6 +47,7 @@ import ghidra.trace.model.memory.TraceMemoryRegion;
|
|||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -107,6 +108,16 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
|
||||
/*---------------- OBJECT MANAGER METHODS -------------------*/
|
||||
|
||||
@Override
|
||||
public TargetObject getTargetObject(TraceObject obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObject getTraceObject(TargetObject obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
|
||||
return objectManager.getTargetBreakpoint(bpt);
|
||||
|
|
|
@ -351,6 +351,16 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
|||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObject getTargetObject(TraceObject obj) {
|
||||
return objectRecorder.toTarget(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObject getTraceObject(TargetObject obj) {
|
||||
return objectRecorder.toTrace(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
|
||||
return objectRecorder.getTargetInterface(bpt, TraceObjectBreakpointLocation.class,
|
||||
|
|
|
@ -19,8 +19,7 @@ import java.io.IOException;
|
|||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.ConnectException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import docking.ActionContext;
|
||||
|
@ -38,6 +37,7 @@ import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
|
|||
import ghidra.dbg.target.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.data.DomainObjectAdapterDB;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
@ -131,6 +131,42 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
}
|
||||
}
|
||||
|
||||
static class TransactionEndFuture extends CompletableFuture<Void>
|
||||
implements TransactionListener {
|
||||
final Trace trace;
|
||||
|
||||
public TransactionEndFuture(Trace trace) {
|
||||
this.trace = trace;
|
||||
this.trace.addTransactionListener(this);
|
||||
if (this.trace.getCurrentTransaction() == null) {
|
||||
complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactionStarted(DomainObjectAdapterDB domainObj, Transaction tx) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(Void value) {
|
||||
trace.removeTransactionListener(this);
|
||||
return super.complete(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactionEnded(DomainObjectAdapterDB domainObj) {
|
||||
complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoStackChanged(DomainObjectAdapterDB domainObj) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoRedoOccurred(DomainObjectAdapterDB domainObj) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is a bit out of this manager's bounds, but acceptable for now.
|
||||
class ForRecordersListener implements CollectionChangeListener<TraceRecorder> {
|
||||
@Override
|
||||
|
@ -138,9 +174,25 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
Swing.runLater(() -> updateCurrentRecorder());
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> waitUnlockedDebounced(TraceRecorder recorder) {
|
||||
Trace trace = recorder.getTrace();
|
||||
return new TransactionEndFuture(trace)
|
||||
.thenCompose(__ -> AsyncTimer.DEFAULT_TIMER.mark().after(100))
|
||||
.thenComposeAsync(__ -> {
|
||||
if (trace.isLocked()) {
|
||||
return waitUnlockedDebounced(recorder);
|
||||
}
|
||||
return AsyncUtils.NIL;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementRemoved(TraceRecorder recorder) {
|
||||
Swing.runLater(() -> {
|
||||
boolean save = isSaveTracesByDefault();
|
||||
CompletableFuture<Void> flush = save
|
||||
? waitUnlockedDebounced(recorder)
|
||||
: AsyncUtils.NIL;
|
||||
flush.thenRunAsync(() -> {
|
||||
updateCurrentRecorder();
|
||||
if (!isAutoCloseOnTerminate()) {
|
||||
return;
|
||||
|
@ -151,13 +203,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (!isSaveTracesByDefault()) {
|
||||
closeTrace(trace);
|
||||
return;
|
||||
if (save) {
|
||||
// Errors already handled by saveTrace
|
||||
saveTrace(trace);
|
||||
}
|
||||
// Errors already handled by saveTrace
|
||||
tryHarder(() -> saveTrace(trace), 3, 100).thenRun(() -> closeTrace(trace));
|
||||
});
|
||||
closeTrace(trace);
|
||||
}, AsyncUtils.SWING_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,22 +259,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return t;
|
||||
}
|
||||
|
||||
protected <T> CompletableFuture<T> tryHarder(Supplier<CompletableFuture<T>> action, int retries,
|
||||
long retryAfterMillis) {
|
||||
Executor exe = CompletableFuture.delayedExecutor(retryAfterMillis, TimeUnit.MILLISECONDS);
|
||||
// NB. thenCompose(f -> f) also ensures exceptions are handled here, not passed through
|
||||
CompletableFuture<T> result =
|
||||
CompletableFuture.supplyAsync(action, AsyncUtils.SWING_EXECUTOR).thenCompose(f -> f);
|
||||
if (retries > 0) {
|
||||
return result.thenApply(CompletableFuture::completedFuture).exceptionally(ex -> {
|
||||
return CompletableFuture
|
||||
.supplyAsync(() -> tryHarder(action, retries - 1, retryAfterMillis), exe)
|
||||
.thenCompose(f -> f);
|
||||
}).thenCompose(f -> f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
|
|
@ -22,6 +22,8 @@ import ghidra.app.plugin.core.debug.service.emulation.*;
|
|||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.time.schedule.Scheduler;
|
||||
import ghidra.trace.model.time.schedule.Scheduler.RunResult;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -37,6 +39,71 @@ import ghidra.util.task.TaskMonitor;
|
|||
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
|
||||
public interface DebuggerEmulationService {
|
||||
|
||||
interface EmulationResult extends RunResult {
|
||||
/**
|
||||
* Get the (scratch) snapshot where the emulated state is stored
|
||||
*
|
||||
* @return the snapshot
|
||||
*/
|
||||
public long snapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of letting the emulator "run free"
|
||||
*/
|
||||
record RecordEmulationResult(TraceSchedule schedule, long snapshot, Throwable error)
|
||||
implements EmulationResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* An emulator managed by this service
|
||||
*/
|
||||
record CachedEmulator(Trace trace, DebuggerPcodeMachine<?> emulator) {
|
||||
/**
|
||||
* Get the trace to which the emulator is bound
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
@Override
|
||||
public Trace trace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the emulator
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> This emulator belongs to this service. You may interrupt it, but stepping
|
||||
* it, or otherwise manipulating it without the service's knowledge can lead to unintended
|
||||
* consequences.
|
||||
*
|
||||
* @return the emulator
|
||||
*/
|
||||
@Override
|
||||
public DebuggerPcodeMachine<?> emulator() {
|
||||
return emulator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for changes in emulator state
|
||||
*/
|
||||
interface EmulatorStateListener {
|
||||
/**
|
||||
* An emulator is running
|
||||
*
|
||||
* @param emu the emulator
|
||||
*/
|
||||
void running(CachedEmulator emu);
|
||||
|
||||
/**
|
||||
* An emulator has stopped
|
||||
*
|
||||
* @param emu the emulator
|
||||
*/
|
||||
void stopped(CachedEmulator emu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available emulator factories
|
||||
*
|
||||
|
@ -53,7 +120,7 @@ public interface DebuggerEmulationService {
|
|||
* the tool, but the config options for each factory to the program/trace.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Should there be some opinion service for choosing default configs? Seem overly
|
||||
* TODO: Should there be some opinion service for choosing default configs? Seems overly
|
||||
* complicated for what it offers. For now, we won't save anything, we'll default to the
|
||||
* (built-in) {@link BytesDebuggerPcodeEmulatorFactory}, and we won't have configuration
|
||||
* options.
|
||||
|
@ -97,35 +164,74 @@ public interface DebuggerEmulationService {
|
|||
* Emulate using the trace's "host" platform
|
||||
*
|
||||
* @see #emulate(TracePlatform, TraceSchedule, TaskMonitor)
|
||||
* @param trace
|
||||
* @param time
|
||||
* @param monitor
|
||||
* @return
|
||||
* @throws CancelledException
|
||||
* @param trace the trace containing the initial state
|
||||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||
* @param monitor a monitor for cancellation and progress reporting
|
||||
* @return the snap in the trace's scratch space where the realize state is stored
|
||||
* @throws CancelledException if the emulation is cancelled
|
||||
*/
|
||||
default long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
return emulate(trace.getPlatformManager().getHostPlatform(), time, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the emulator to "run free" until it is interrupted or encounters an error
|
||||
*
|
||||
* <p>
|
||||
* The service may perform some preliminary emulation to realize the machine's initial state. If
|
||||
* the monitor cancels during preliminary emulation, this method throws a
|
||||
* {@link CancelledException}. If the monitor cancels the emulation during the run, it is
|
||||
* treated the same as interruption. The machine state will be written to the trace in a scratch
|
||||
* snap and the result returned. Note that the machine could be interrupted having only
|
||||
* partially executed an instruction. Thus, the schedule may specify p-code operations. The
|
||||
* schedule will place the program counter on the instruction (or p-code op) causing the
|
||||
* interruption. Thus, except for breakpoints, attempting to step again will interrupt the
|
||||
* emulator again.
|
||||
*
|
||||
* @param platform the trace platform containing the initial state
|
||||
* @param from a schedule for the machine's initial state
|
||||
* @param monitor a monitor cancellation
|
||||
* @param scheduler a thread scheduler for the emulator
|
||||
* @return the result of emulation
|
||||
*/
|
||||
EmulationResult run(TracePlatform platform, TraceSchedule from, TaskMonitor monitor,
|
||||
Scheduler scheduler) throws CancelledException;
|
||||
|
||||
/**
|
||||
* Invoke {@link #emulate(Trace, TraceSchedule, TaskMonitor)} in the background
|
||||
*
|
||||
* <p>
|
||||
* This is the preferred means of performing emulation. Because the underlying emulator may
|
||||
* request a <em>blocking</em> read from a target, it is important that
|
||||
* {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)} is <em>never</em> called by the
|
||||
* Swing thread.
|
||||
* This is the preferred means of performing definite emulation. Because the underlying emulator
|
||||
* may request a <em>blocking</em> read from a target, it is important that
|
||||
* {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor) emulate} is <em>never</em> called
|
||||
* by the Swing thread.
|
||||
*
|
||||
* @param platform the trace platform containing the initial state
|
||||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||
* @return a future which completes with the result of
|
||||
* {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)}
|
||||
* {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor) emulate}
|
||||
*/
|
||||
CompletableFuture<Long> backgroundEmulate(TracePlatform platform, TraceSchedule time);
|
||||
|
||||
/**
|
||||
* The the cached emulator for the given trace and time
|
||||
* Invoke {@link #run(TracePlatform, TraceSchedule, TaskMonitor, Scheduler)} in the background
|
||||
*
|
||||
* <p>
|
||||
* This is the preferred means of performing indefinite emulation, for the same reasons as
|
||||
* {@link #backgroundEmulate(TracePlatform, TraceSchedule) emulate}.
|
||||
*
|
||||
* @param platform the trace platform containing the initial state
|
||||
* @param from a schedule for the machine's initial state
|
||||
* @param scheduler a thread scheduler for the emulator
|
||||
* @return a future which completes with the result of
|
||||
* {@link #run(TracePlatform, TraceSchedule, TaskMonitor, Scheduler) run}.
|
||||
*/
|
||||
CompletableFuture<EmulationResult> backgroundRun(TracePlatform platform, TraceSchedule from,
|
||||
Scheduler scheduler);
|
||||
|
||||
/**
|
||||
* Get the cached emulator for the given trace and time
|
||||
*
|
||||
* <p>
|
||||
* To guarantee the emulator is present, call {@link #backgroundEmulate(Trace, TraceSchedule)}
|
||||
|
@ -142,4 +248,25 @@ public interface DebuggerEmulationService {
|
|||
* @return the copied p-code frame
|
||||
*/
|
||||
DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time);
|
||||
|
||||
/**
|
||||
* Get the emulators which are current executing
|
||||
*
|
||||
* @return the collection
|
||||
*/
|
||||
Collection<CachedEmulator> getBusyEmulators();
|
||||
|
||||
/**
|
||||
* Add a listener for emulator state changes
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addStateListener(EmulatorStateListener listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for emulator state changes
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeStateListener(EmulatorStateListener listener);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
|||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
|
@ -37,6 +38,7 @@ import ghidra.trace.model.memory.TraceMemorySpace;
|
|||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
|
@ -50,7 +52,17 @@ import ghidra.util.task.TaskMonitor;
|
|||
* The recorder is the glue from a portion of a debugger's model into a Ghidra trace. As such, this
|
||||
* object maintains a mapping between corresponding objects of interest in the model tree to the
|
||||
* trace, and that mapping can be queried. In most cases, UI components which deal with tracing need
|
||||
* only read the trace in order to populate their display.
|
||||
* only read the trace in order to populate their display. Several methods are provided for
|
||||
* retrieving corresponding objects from the target or trace given that object in the other. These
|
||||
* methods may return null for a variety of reasons:
|
||||
*
|
||||
* <ol>
|
||||
* <li>The particular type may not be supported or of interest to the recorder.</li>
|
||||
* <li>The recorder may not have actually recorded the object yet, despite receiving notice.
|
||||
* Recording is asynchronous, and it may also be waiting for additional dependencies or attributes
|
||||
* before it can create the corresponding trace object.</li>
|
||||
* <li>The target object may not longer exist for a given trace object.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* The recorder copies information in one direction; thus, if a trace UI component needs to affect
|
||||
|
@ -205,48 +217,210 @@ public interface TraceRecorder {
|
|||
*/
|
||||
void removeListener(TraceRecorderListener listener);
|
||||
|
||||
/**
|
||||
* Get the target object corresponding to the given trace object
|
||||
*
|
||||
* @param obj the trace object
|
||||
* @return the target object, or null
|
||||
*/
|
||||
TargetObject getTargetObject(TraceObject obj);
|
||||
|
||||
/**
|
||||
* Get the trace object corresponding to the given target object
|
||||
*
|
||||
* @param obj the target object
|
||||
* @return the trace object, or null
|
||||
*/
|
||||
TraceObject getTraceObject(TargetObject obj);
|
||||
|
||||
/**
|
||||
* Get the target breakpoint location corresponding to the given trace breakpoint
|
||||
*
|
||||
* @param obj the trace breakpoint
|
||||
* @return the target breakpoint location, or null
|
||||
*/
|
||||
TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt);
|
||||
|
||||
/**
|
||||
* Get the trace breakpoint corresponding to the given target breakpoint location
|
||||
*
|
||||
* @param obj the target breakpoint location
|
||||
* @return the trace breakpoint, or null
|
||||
*/
|
||||
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
|
||||
|
||||
/**
|
||||
* Get the target memory region corresponding to the given trace memory region
|
||||
*
|
||||
* @param obj the trace memory region
|
||||
* @return the target memory region, or null
|
||||
*/
|
||||
TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region);
|
||||
|
||||
/**
|
||||
* Get the trace memory region corresponding to the given target memory region
|
||||
*
|
||||
* @param obj the target memory region
|
||||
* @return the trace memory region, or null
|
||||
*/
|
||||
TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region);
|
||||
|
||||
/**
|
||||
* Get the target module corresponding to the given trace module
|
||||
*
|
||||
* @param obj the trace module
|
||||
* @return the target module, or null
|
||||
*/
|
||||
TargetModule getTargetModule(TraceModule module);
|
||||
|
||||
/**
|
||||
* Get the trace module corresponding to the given target module
|
||||
*
|
||||
* @param obj the target module
|
||||
* @return the trace module, or null
|
||||
*/
|
||||
TraceModule getTraceModule(TargetModule module);
|
||||
|
||||
/**
|
||||
* Get the target section corresponding to the given trace section
|
||||
*
|
||||
* @param obj the trace section
|
||||
* @return the target section, or null
|
||||
*/
|
||||
TargetSection getTargetSection(TraceSection section);
|
||||
|
||||
/**
|
||||
* Get the trace section corresponding to the given target section
|
||||
*
|
||||
* @param obj the target section
|
||||
* @return the trace section, or null
|
||||
*/
|
||||
TraceSection getTraceSection(TargetSection section);
|
||||
|
||||
/**
|
||||
* Get the target thread corresponding to the given trace thread
|
||||
*
|
||||
* @param obj the trace thread
|
||||
* @return the target thread, or null
|
||||
*/
|
||||
TargetThread getTargetThread(TraceThread thread);
|
||||
|
||||
/**
|
||||
* Get the execution state of the given target thread
|
||||
*
|
||||
* @param thread the target thread
|
||||
* @return the execution state, or null
|
||||
*/
|
||||
TargetExecutionState getTargetThreadState(TargetThread thread);
|
||||
|
||||
/**
|
||||
* Get the execution state of the given trace thread
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @return the execution state, or null
|
||||
*/
|
||||
TargetExecutionState getTargetThreadState(TraceThread thread);
|
||||
|
||||
/**
|
||||
* Get the target register bank for the given trace thread and frame level
|
||||
*
|
||||
* <p>
|
||||
* If the model doesn't provide a bank for every frame, then this should only return non-null
|
||||
* for frame level 0, in which case it should return the bank for the given thread.
|
||||
*
|
||||
* @param thread the thread
|
||||
* @param frameLevel the frame level
|
||||
* @return the bank, or null
|
||||
*/
|
||||
TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel);
|
||||
|
||||
/**
|
||||
* Get the trace thread corresponding to the given target thread
|
||||
*
|
||||
* @param obj the target thread
|
||||
* @return the trace thread, or null
|
||||
*/
|
||||
TraceThread getTraceThread(TargetThread thread);
|
||||
|
||||
/**
|
||||
* Find the trace thread containing the given successor target object
|
||||
*
|
||||
* @param successor the target object
|
||||
* @return the trace thread containing the object, or null
|
||||
*/
|
||||
TraceThread getTraceThreadForSuccessor(TargetObject successor);
|
||||
|
||||
/**
|
||||
* Get the trace stack frame for the given target stack frame
|
||||
*
|
||||
* @param frame the target stack frame
|
||||
* @return the trace stack frame, or null
|
||||
*/
|
||||
TraceStackFrame getTraceStackFrame(TargetStackFrame frame);
|
||||
|
||||
/**
|
||||
* Get the trace stack frame containing the given successor target object
|
||||
*
|
||||
* @param successor the target object
|
||||
* @return the trace stack frame containing the object, or null
|
||||
*/
|
||||
TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor);
|
||||
|
||||
/**
|
||||
* Get the target stack frame for the given trace thread and frame level
|
||||
*
|
||||
* @param thread the thread
|
||||
* @param frameLevel the frame level
|
||||
* @return the stack frame, or null
|
||||
*/
|
||||
TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel);
|
||||
|
||||
/**
|
||||
* Get all the target's threads that are currently alive
|
||||
*
|
||||
* @return the set of live target threads
|
||||
*/
|
||||
Set<TargetThread> getLiveTargetThreads();
|
||||
|
||||
/**
|
||||
* Get the register mapper for the given trace thread
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @return the mapper, or null
|
||||
*/
|
||||
DebuggerRegisterMapper getRegisterMapper(TraceThread thread);
|
||||
|
||||
/**
|
||||
* Get the memory mapper for the target
|
||||
*
|
||||
* @return the mapper, or null
|
||||
*/
|
||||
DebuggerMemoryMapper getMemoryMapper();
|
||||
|
||||
/**
|
||||
* Check if the given register bank is accessible
|
||||
*
|
||||
* @param bank the target register bank
|
||||
* @return true if accessible
|
||||
* @deprecated the accessibility concept was never really implemented nor offered anything of
|
||||
* value. It has no replacement. Instead a model should reject requests its not
|
||||
* prepared to handle, or queue them up to be processed when it can. If the latter,
|
||||
* then ideally it should only allow one instance of a given request to be queued.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isRegisterBankAccessible(TargetRegisterBank bank);
|
||||
|
||||
/**
|
||||
* Check if the register bank for the given trace thread and frame level is accessible
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @param frameLevel the frame level
|
||||
* @see #getTargetStackFrame(TraceThread, int)
|
||||
* @see #isRegisterBankAccessible(TargetRegisterBank)
|
||||
* @return true if accessible
|
||||
* @deprecated for the same reasons as {@link #isRegisterBankAccessible(TargetRegisterBank)}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isRegisterBankAccessible(TraceThread thread, int frameLevel);
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<PACKAGE NAME="Ghidra Core">
|
||||
<INCLUDE CLASS="ghidra.app.plugin.core.editor.TextEditorManagerPlugin" />
|
||||
<INCLUDE CLASS="ghidra.app.plugin.core.interpreter.InterpreterPanelPlugin" />
|
||||
<EXCLUDE CLASS="ghidra.plugins.fsbrowser.FileSystemBrowserPlugin" />
|
||||
</PACKAGE>
|
||||
<ROOT_NODE X_POS="671" Y_POS="447" WIDTH="1920" HEIGHT="1017" EX_STATE="0" FOCUSED_OWNER="DebuggerConsolePlugin" FOCUSED_NAME="Debug Console" FOCUSED_TITLE="Debug Console">
|
||||
<SPLIT_NODE WIDTH="1918" HEIGHT="910" DIVIDER_LOCATION="767" ORIENTATION="VERTICAL">
|
||||
|
|
|
@ -25,8 +25,7 @@ import java.io.IOException;
|
|||
import java.math.BigInteger;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -455,7 +454,9 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
protected static void performEnabledAction(ActionContextProvider provider,
|
||||
DockingActionIf action, boolean wait) {
|
||||
ActionContext context = waitForValue(() -> {
|
||||
ActionContext ctx = provider.getActionContext(null);
|
||||
ActionContext ctx = provider == null
|
||||
? new ActionContext()
|
||||
: provider.getActionContext(null);
|
||||
if (!action.isEnabledForContext(ctx)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -529,8 +530,18 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
if (recorder == null) {
|
||||
return;
|
||||
}
|
||||
waitOn(recorder.getTarget().getModel().flushEvents());
|
||||
waitOn(recorder.flushTransactions());
|
||||
try {
|
||||
waitOn(recorder.getTarget().getModel().flushEvents());
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
// Whatever
|
||||
}
|
||||
try {
|
||||
waitOn(recorder.flushTransactions());
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
// Whatever
|
||||
}
|
||||
waitForDomainObject(recorder.getTrace());
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,583 @@
|
|||
/* ###
|
||||
* 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.control;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.dnd.GClipboard;
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.app.plugin.core.assembler.AssemblerPlugin;
|
||||
import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper;
|
||||
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
|
||||
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPluginTestHelper;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
||||
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
|
||||
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
|
||||
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.dbg.model.*;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.pcode.exec.SuspendedPcodeExecutionException;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.ShortDataType;
|
||||
import ghidra.program.model.lang.CompilerSpecID;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.Scheduler;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
/**
|
||||
* Tests for target control and state editing
|
||||
*
|
||||
* <p>
|
||||
* In these and other machine-state-editing integration tests, we use
|
||||
* {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
|
||||
* {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
|
||||
* if bugs crop up in various combinations.
|
||||
*/
|
||||
public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
|
||||
DebuggerListingPlugin listingPlugin;
|
||||
DebuggerStateEditingService editingService;
|
||||
DebuggerEmulationService emulationService;
|
||||
DebuggerControlPlugin controlPlugin;
|
||||
|
||||
List<String> commands = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Before
|
||||
public void setUpControlTest() throws Exception {
|
||||
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||
emulationService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
|
||||
controlPlugin = addPlugin(tool, DebuggerControlPlugin.class);
|
||||
|
||||
mb = new TestDebuggerModelBuilder() {
|
||||
@Override
|
||||
protected TestDebuggerObjectModel newModel(String typeHint) {
|
||||
commands.clear();
|
||||
return new TestDebuggerObjectModel(typeHint) {
|
||||
@Override
|
||||
protected TestTargetThread newTestTargetThread(
|
||||
TestTargetThreadContainer container, int tid) {
|
||||
return new TestTargetThread(container, tid) {
|
||||
{
|
||||
setState(TargetExecutionState.STOPPED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> resume() {
|
||||
commands.add("resume");
|
||||
setState(TargetExecutionState.RUNNING);
|
||||
return super.resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> interrupt() {
|
||||
commands.add("interrupt");
|
||||
setState(TargetExecutionState.STOPPED);
|
||||
return super.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> kill() {
|
||||
commands.add("kill");
|
||||
setState(TargetExecutionState.TERMINATED);
|
||||
return super.kill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||
commands.add("step(" + kind + ")");
|
||||
setState(TargetExecutionState.RUNNING);
|
||||
setState(TargetExecutionState.STOPPED);
|
||||
return super.step(kind);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> close() {
|
||||
commands.add("close");
|
||||
return super.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target)
|
||||
throws Exception {
|
||||
return new ObjectBasedDebuggerTargetTraceMapper(target,
|
||||
new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TraceRecorder recordAndWaitSync() throws Throwable {
|
||||
TraceRecorder recorder = super.recordAndWaitSync();
|
||||
useTrace(recorder.getTrace());
|
||||
return recorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TargetObject chooseTarget() {
|
||||
return mb.testModel.session;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetResumeAction() throws Throwable {
|
||||
createTestModel();
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitOn(recorder.requestFocus(mb.testThread1));
|
||||
waitRecorder(recorder);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionTargetResume, true);
|
||||
waitRecorder(recorder);
|
||||
assertEquals(List.of("resume"), commands);
|
||||
waitForSwing();
|
||||
assertFalse(controlPlugin.actionTargetResume.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetInterruptAction() throws Throwable {
|
||||
createTestModel();
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitOn(recorder.requestFocus(mb.testThread1));
|
||||
waitRecorder(recorder);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(controlPlugin.actionTargetInterrupt.isEnabled());
|
||||
waitOn(mb.testThread1.resume());
|
||||
waitRecorder(recorder);
|
||||
commands.clear();
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionTargetInterrupt, true);
|
||||
waitRecorder(recorder);
|
||||
assertEquals(List.of("interrupt"), commands);
|
||||
waitForSwing();
|
||||
assertFalse(controlPlugin.actionTargetInterrupt.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetKillAction() throws Throwable {
|
||||
createTestModel();
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitOn(recorder.requestFocus(mb.testThread1));
|
||||
waitRecorder(recorder);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionTargetKill, true);
|
||||
waitRecorder(recorder);
|
||||
assertEquals(List.of("kill"), commands);
|
||||
waitForSwing();
|
||||
assertFalse(controlPlugin.actionTargetKill.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetDisconnectAction() throws Throwable {
|
||||
createTestModel();
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionTargetDisconnect, true);
|
||||
waitRecorder(recorder);
|
||||
assertEquals(List.of("close"), commands);
|
||||
waitForSwing();
|
||||
waitForPass(() -> assertFalse(controlPlugin.actionTargetDisconnect.isEnabled()));
|
||||
}
|
||||
|
||||
protected void runTestTargetStepAction(DockingAction action, TargetStepKind expected)
|
||||
throws Throwable {
|
||||
createTestModel();
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitOn(recorder.requestFocus(mb.testThread1));
|
||||
waitRecorder(recorder);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, action, true);
|
||||
waitRecorder(recorder);
|
||||
assertEquals(List.of("step(" + expected + ")"), commands);
|
||||
waitForSwing();
|
||||
assertTrue(action.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetStepIntoAction() throws Throwable {
|
||||
runTestTargetStepAction(controlPlugin.actionTargetStepInto, TargetStepKind.INTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetStepOverAction() throws Throwable {
|
||||
runTestTargetStepAction(controlPlugin.actionTargetStepOver, TargetStepKind.OVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetStepFinishAction() throws Throwable {
|
||||
runTestTargetStepAction(controlPlugin.actionTargetStepFinish, TargetStepKind.FINISH);
|
||||
}
|
||||
|
||||
TraceThread createToyLoopTrace() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
|
||||
Address start = tb.addr(0x00400000);
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
Assembler asm = Assemblers.getAssembler(tb.language);
|
||||
AssemblyBuffer buf = new AssemblyBuffer(asm, start);
|
||||
buf.assemble("br 0x" + start);
|
||||
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.exec(0, thread, 0, "pc = 0x" + start + ";");
|
||||
tb.trace.getMemoryManager().putBytes(0, start, ByteBuffer.wrap(buf.getBytes()));
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulateResumeAction() throws Throwable {
|
||||
TraceThread thread = createToyLoopTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionEmulateResume, true);
|
||||
waitForPass(() -> assertFalse(controlPlugin.actionEmulateResume.isEnabled()));
|
||||
|
||||
CachedEmulator ce = Unique.assertOne(emulationService.getBusyEmulators());
|
||||
ce.emulator().setSuspended(true);
|
||||
waitForTasks();
|
||||
assertTrue(controlPlugin.actionEmulateResume.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulateInterruptAction() throws Throwable {
|
||||
TraceThread thread = createToyLoopTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(controlPlugin.actionEmulateInterrupt.isEnabled());
|
||||
|
||||
CompletableFuture<EmulationResult> future = emulationService.backgroundRun(tb.host,
|
||||
TraceSchedule.snap(0), Scheduler.oneThread(thread));
|
||||
waitForPass(() -> assertTrue(controlPlugin.actionEmulateInterrupt.isEnabled()));
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionEmulateInterrupt, true);
|
||||
EmulationResult result = waitOn(future);
|
||||
assertTrue(result.error() instanceof SuspendedPcodeExecutionException);
|
||||
waitForTasks();
|
||||
|
||||
assertFalse(controlPlugin.actionEmulateInterrupt.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulateStepBackAction() throws Throwable {
|
||||
TraceThread thread = createToyLoopTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(controlPlugin.actionEmulateStepBack.isEnabled());
|
||||
|
||||
traceManager.activateTime(TraceSchedule.parse("0:t0-1"));
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionEmulateStepBack, true);
|
||||
|
||||
assertEquals(TraceSchedule.snap(0), traceManager.getCurrent().getTime());
|
||||
assertFalse(controlPlugin.actionEmulateStepBack.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulateStepIntoAction() throws Throwable {
|
||||
TraceThread thread = createToyLoopTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionEmulateStepInto, true);
|
||||
|
||||
assertEquals(TraceSchedule.parse("0:t0-1"), traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulateSkipOverAction() throws Throwable {
|
||||
TraceThread thread = createToyLoopTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionEmulateSkipOver, true);
|
||||
|
||||
assertEquals(TraceSchedule.parse("0:t0-s1"), traceManager.getCurrent().getTime());
|
||||
}
|
||||
|
||||
protected void create2SnapTrace() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getTimeManager().getSnapshot(1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTraceSnapBackwardAction() throws Throwable {
|
||||
create2SnapTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(controlPlugin.actionTraceSnapBackward.isEnabled());
|
||||
|
||||
traceManager.activateTime(TraceSchedule.snap(1));
|
||||
performEnabledAction(null, controlPlugin.actionTraceSnapBackward, true);
|
||||
|
||||
assertEquals(TraceSchedule.snap(0), traceManager.getCurrent().getTime());
|
||||
assertFalse(controlPlugin.actionTraceSnapBackward.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTraceSnapForwardAction() throws Throwable {
|
||||
create2SnapTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(null, controlPlugin.actionTraceSnapForward, true);
|
||||
|
||||
assertEquals(TraceSchedule.snap(1), traceManager.getCurrent().getTime());
|
||||
assertFalse(controlPlugin.actionTraceSnapForward.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchInstructionActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerDisassemblerPlugin disassemblerPlugin =
|
||||
addPlugin(tool, DebuggerDisassemblerPlugin.class);
|
||||
|
||||
assertFalse(controlPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
// Dynamic Patch Instruction requires existing code unit for context
|
||||
tb.addInstruction(0, tb.addr(0x00400123), tb.host);
|
||||
}
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
DebuggerDisassemblerPluginTestHelper helper =
|
||||
new DebuggerDisassemblerPluginTestHelper(disassemblerPlugin, listingProvider, view);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
Swing.runNow(
|
||||
() -> listingProvider.goTo(view, new ProgramLocation(view, tb.addr(0x00400123))));
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(controlPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> controlPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
assertFalse(
|
||||
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
runSwing(() -> controlPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
assertTrue(
|
||||
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
Instruction ins =
|
||||
helper.patchInstructionAt(tb.addr(0x00400123), "imm r0,#0x0", "imm r0,#0x3d2");
|
||||
assertEquals(2, ins.getLength());
|
||||
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
byte[] bytes = new byte[2];
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0x30, 0xd2), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchDataActionInDynamicListingEmu() throws Throwable {
|
||||
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
||||
|
||||
assertFalse(controlPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
tb.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Range.atLeast(0L), tb.addr(0x00400123), ShortDataType.dataType);
|
||||
}
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
AssemblerPluginTestHelper helper =
|
||||
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(controlPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> controlPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
runSwing(() -> controlPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
|
||||
assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
/**
|
||||
* TODO: There's a bug in the trace forking: Data units are not replaced when bytes changed.
|
||||
* Thus, we'll make no assertions about the data unit.
|
||||
*/
|
||||
/*Data data =*/ helper.patchDataAt(tb.addr(0x00400123), "0h", "5h");
|
||||
// assertEquals(2, data.getLength());
|
||||
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
byte[] bytes = new byte[2];
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0, 5), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasteActionInDynamicListingEmu() throws Throwable {
|
||||
addPlugin(tool, ClipboardPlugin.class);
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
DockingActionIf pasteAction = getLocalAction(listingProvider, "Paste");
|
||||
|
||||
assertFalse(controlPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
}
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
ActionContext ctx;
|
||||
|
||||
assertTrue(controlPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> controlPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertFalse(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
runSwing(() -> controlPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertFalse(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
Clipboard clipboard = GClipboard.getSystemClipboard();
|
||||
clipboard.setContents(new StringSelection("12 34 56 78"), null);
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertTrue(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
performAction(pasteAction, listingProvider, false);
|
||||
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
|
||||
pressButtonByText(confirm, "Yes");
|
||||
|
||||
byte[] bytes = new byte[4];
|
||||
waitForPass(noExc(() -> {
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0x12, 0x34, 0x56, 0x78), bytes);
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
/* ###
|
||||
* 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.editing;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.dnd.GClipboard;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.assembler.AssemblerPlugin;
|
||||
import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper;
|
||||
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
|
||||
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPluginTestHelper;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.services.DebuggerStateEditingService;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.program.model.data.ShortDataType;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
/**
|
||||
* Tests for editing machine state that don't naturally fit elsewhere.
|
||||
*
|
||||
* <p>
|
||||
* In these and other machine-state-editing integration tests, we use
|
||||
* {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
|
||||
* {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
|
||||
* if bugs crop up in various combinations.
|
||||
*/
|
||||
public class DebuggerStateEditingPluginIntegrationTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
@Test
|
||||
public void testPatchInstructionActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
DebuggerDisassemblerPlugin disassemblerPlugin =
|
||||
addPlugin(tool, DebuggerDisassemblerPlugin.class);
|
||||
DebuggerStateEditingPlugin editingPlugin =
|
||||
addPlugin(tool, DebuggerStateEditingPlugin.class);
|
||||
DebuggerStateEditingService editingService =
|
||||
tool.getService(DebuggerStateEditingService.class);
|
||||
|
||||
assertFalse(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
// Dynamic Patch Instruction requires existing code unit for context
|
||||
tb.addInstruction(0, tb.addr(0x00400123), tb.host);
|
||||
}
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
DebuggerDisassemblerPluginTestHelper helper =
|
||||
new DebuggerDisassemblerPluginTestHelper(disassemblerPlugin, listingProvider, view);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
Swing.runNow(
|
||||
() -> listingProvider.goTo(view, new ProgramLocation(view, tb.addr(0x00400123))));
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
assertFalse(
|
||||
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
assertTrue(
|
||||
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
Instruction ins =
|
||||
helper.patchInstructionAt(tb.addr(0x00400123), "imm r0,#0x0", "imm r0,#0x3d2");
|
||||
assertEquals(2, ins.getLength());
|
||||
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
byte[] bytes = new byte[2];
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0x30, 0xd2), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchDataActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
||||
DebuggerStateEditingPlugin editingPlugin =
|
||||
addPlugin(tool, DebuggerStateEditingPlugin.class);
|
||||
DebuggerStateEditingService editingService =
|
||||
tool.getService(DebuggerStateEditingService.class);
|
||||
|
||||
assertFalse(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
tb.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Range.atLeast(0L), tb.addr(0x00400123), ShortDataType.dataType);
|
||||
}
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
AssemblerPluginTestHelper helper =
|
||||
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
|
||||
assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
/**
|
||||
* TODO: There's a bug in the trace forking: Data units are not replaced when bytes changed.
|
||||
* Thus, we'll make no assertions about the data unit.
|
||||
*/
|
||||
/*Data data =*/ helper.patchDataAt(tb.addr(0x00400123), "0h", "5h");
|
||||
// assertEquals(2, data.getLength());
|
||||
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
byte[] bytes = new byte[2];
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0, 5), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasteActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
DebuggerStateEditingPlugin editingPlugin =
|
||||
addPlugin(tool, DebuggerStateEditingPlugin.class);
|
||||
addPlugin(tool, ClipboardPlugin.class);
|
||||
DebuggerStateEditingService editingService =
|
||||
tool.getService(DebuggerStateEditingService.class);
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
DockingActionIf pasteAction = getLocalAction(listingProvider, "Paste");
|
||||
|
||||
assertFalse(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
}
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
ActionContext ctx;
|
||||
|
||||
assertTrue(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertFalse(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertFalse(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
Clipboard clipboard = GClipboard.getSystemClipboard();
|
||||
clipboard.setContents(new StringSelection("12 34 56 78"), null);
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertTrue(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
performAction(pasteAction, listingProvider, false);
|
||||
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
|
||||
pressButtonByText(confirm, "Yes");
|
||||
|
||||
byte[] bytes = new byte[4];
|
||||
waitForPass(noExc(() -> {
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0x12, 0x34, 0x56, 0x78), bytes);
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import static org.junit.Assert.*;
|
|||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Before;
|
||||
|
@ -35,20 +36,26 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
|||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion;
|
||||
import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin;
|
||||
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.pcode.exec.InterruptPcodeExecutionException;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.InstructionIterator;
|
||||
import ghidra.program.model.listing.ProgramContext;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.DefaultTraceLocation;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.Scheduler;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -335,4 +342,130 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
|
|||
.getUnsignedValue()
|
||||
.toString(16));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecutionBreakpoint() throws Exception {
|
||||
createProgram();
|
||||
intoProject(program);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
Memory memory = program.getMemory();
|
||||
Address addrText = addr(program, 0x000400000);
|
||||
Register regPC = program.getRegister("pc");
|
||||
Register regR0 = program.getRegister("r0");
|
||||
Register regR1 = program.getRegister("r1");
|
||||
Register regR2 = program.getRegister("r2");
|
||||
Address addrI2;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
|
||||
(byte) 0, TaskMonitor.DUMMY, false);
|
||||
blockText.setExecute(true);
|
||||
InstructionIterator ii = asm.assemble(addrText,
|
||||
"mov r0, r1",
|
||||
"mov r2, r0");
|
||||
ii.next();
|
||||
addrI2 = ii.next().getMinAddress();
|
||||
program.getProgramContext()
|
||||
.setValue(regR1, addrText, addrText, new BigInteger("1234", 16));
|
||||
}
|
||||
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
codeBrowser.goTo(new ProgramLocation(program, addrText));
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
|
||||
|
||||
Trace trace = traceManager.getCurrentTrace();
|
||||
assertNotNull(trace);
|
||||
|
||||
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
|
||||
TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
|
||||
trace.getBreakpointManager()
|
||||
.addBreakpoint("Breakpoints[0]", Range.atLeast(0L), addrI2, Set.of(thread),
|
||||
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
|
||||
}
|
||||
|
||||
EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
|
||||
TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread));
|
||||
|
||||
assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule());
|
||||
assertTrue(result.error() instanceof InterruptPcodeExecutionException);
|
||||
|
||||
long scratch = result.snapshot();
|
||||
|
||||
assertEquals(new BigInteger("00400002", 16),
|
||||
regs.getViewValue(scratch, regPC).getUnsignedValue());
|
||||
assertEquals(new BigInteger("1234", 16),
|
||||
regs.getViewValue(scratch, regR0).getUnsignedValue());
|
||||
assertEquals(new BigInteger("1234", 16),
|
||||
regs.getViewValue(scratch, regR1).getUnsignedValue());
|
||||
assertEquals(new BigInteger("0", 16),
|
||||
regs.getViewValue(scratch, regR2).getUnsignedValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccessBreakpoint() throws Exception {
|
||||
createProgram();
|
||||
intoProject(program);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
Memory memory = program.getMemory();
|
||||
Address addrText = addr(program, 0x000400000);
|
||||
Register regPC = program.getRegister("pc");
|
||||
Register regR0 = program.getRegister("r0");
|
||||
Register regR1 = program.getRegister("r1");
|
||||
Register regR2 = program.getRegister("r2");
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
|
||||
(byte) 0, TaskMonitor.DUMMY, false);
|
||||
blockText.setExecute(true);
|
||||
asm.assemble(addrText,
|
||||
"store [r0], r1",
|
||||
"load r2, [r0]");
|
||||
ProgramContext ctx = program.getProgramContext();
|
||||
ctx.setValue(regR0, addrText, addrText, new BigInteger("1234", 16));
|
||||
ctx.setValue(regR1, addrText, addrText, new BigInteger("5678", 16));
|
||||
}
|
||||
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
codeBrowser.goTo(new ProgramLocation(program, addrText));
|
||||
waitForSwing();
|
||||
|
||||
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
|
||||
|
||||
Trace trace = traceManager.getCurrentTrace();
|
||||
assertNotNull(trace);
|
||||
|
||||
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
|
||||
TraceMemoryManager mem = trace.getMemoryManager();
|
||||
TraceMemorySpace regs = mem.getMemoryRegisterSpace(thread, false);
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
|
||||
trace.getBreakpointManager()
|
||||
.addBreakpoint("Breakpoints[0]", Range.atLeast(0L), addr(trace, 0x1234),
|
||||
Set.of(thread), Set.of(TraceBreakpointKind.READ), true, "test");
|
||||
}
|
||||
|
||||
EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
|
||||
TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread));
|
||||
|
||||
assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule());
|
||||
assertTrue(result.error() instanceof InterruptPcodeExecutionException);
|
||||
|
||||
long scratch = result.snapshot();
|
||||
|
||||
assertEquals(new BigInteger("00400002", 16),
|
||||
regs.getViewValue(scratch, regPC).getUnsignedValue());
|
||||
assertEquals(new BigInteger("1234", 16),
|
||||
regs.getViewValue(scratch, regR0).getUnsignedValue());
|
||||
assertEquals(new BigInteger("5678", 16),
|
||||
regs.getViewValue(scratch, regR1).getUnsignedValue());
|
||||
byte[] arr = new byte[8];
|
||||
mem.getViewBytes(scratch, addr(trace, 0x1234), ByteBuffer.wrap(arr));
|
||||
assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0x56, 0x78 }, arr);
|
||||
assertEquals(new BigInteger("0", 16),
|
||||
regs.getViewValue(scratch, regR2).getUnsignedValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,22 +37,7 @@ public interface TargetExecutionStateful extends TargetObject {
|
|||
* This may apply, e.g., to a GDB "Inferior," which has no yet been used to launch or attach
|
||||
* to a process.
|
||||
*/
|
||||
INACTIVE {
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
INACTIVE(false, false, false),
|
||||
|
||||
/**
|
||||
* The object is alive, but its execution state is unspecified
|
||||
|
@ -64,42 +49,12 @@ public interface TargetExecutionStateful extends TargetObject {
|
|||
* when <em>all</em> of its threads are stopped. For the clients' sakes, all models should
|
||||
* implement these conventions internally.
|
||||
*/
|
||||
ALIVE {
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
ALIVE(true, false, false),
|
||||
|
||||
/**
|
||||
* The object is alive, but not executing
|
||||
*/
|
||||
STOPPED {
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
STOPPED(true, false, true),
|
||||
|
||||
/**
|
||||
* The object is alive and executing
|
||||
|
@ -109,22 +64,7 @@ public interface TargetExecutionStateful extends TargetObject {
|
|||
* thread is currently executing, waiting on an event, or scheduled for execution. It does
|
||||
* not necessarily mean it is executing on a CPU at this exact moment.
|
||||
*/
|
||||
RUNNING {
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
RUNNING(true, true, false),
|
||||
|
||||
/**
|
||||
* The object is no longer alive
|
||||
|
@ -134,43 +74,44 @@ public interface TargetExecutionStateful extends TargetObject {
|
|||
* stale handles to objects which may still be queried (e.g., for a process exit code), or
|
||||
* e.g., a GDB "Inferior," which could be re-used to launch or attach to another process.
|
||||
*/
|
||||
TERMINATED {
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return false;
|
||||
}
|
||||
TERMINATED(false, false, false);
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
private final boolean alive;
|
||||
private final boolean running;
|
||||
private final boolean stopped;
|
||||
|
||||
@Override
|
||||
public boolean isStopped() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
private TargetExecutionState(boolean alive, boolean running, boolean stopped) {
|
||||
this.alive = alive;
|
||||
this.running = running;
|
||||
this.stopped = stopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this state implies the object is alive
|
||||
*
|
||||
* @return true if alive
|
||||
*/
|
||||
public abstract boolean isAlive();
|
||||
public boolean isAlive() {
|
||||
return alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this state implies the object is running
|
||||
*
|
||||
* @return true if running
|
||||
*/
|
||||
public abstract boolean isRunning();
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this state implies the object is stopped
|
||||
*
|
||||
* @return true if stopped
|
||||
*/
|
||||
public abstract boolean isStopped();
|
||||
public boolean isStopped() {
|
||||
return stopped;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1050,6 +1050,22 @@ public interface TargetObject extends Comparable<TargetObject> {
|
|||
return ValueUtils.expectType(obj, cls, this, name, fallback, required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the model for a suitable-related object of the given interface
|
||||
*
|
||||
* @see TargetObjectSchema#searchForSuitable(Class, List)
|
||||
* @param <T> the expected type of the interface
|
||||
* @param cls the class giving the expected type
|
||||
* @return the found object, or null
|
||||
*/
|
||||
public default <T extends TargetObject> T getCachedSuitable(Class<T> cls) {
|
||||
List<String> found = getModel().getRootSchema().searchForSuitable(cls, getPath());
|
||||
if (found == null) {
|
||||
return null;
|
||||
}
|
||||
return cls.cast(getModel().getModelValue(found));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate caches associated with this object, other than those for cached children
|
||||
*
|
||||
|
|
|
@ -22,8 +22,8 @@ import java.util.stream.Stream;
|
|||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
|
@ -570,4 +570,29 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the execution state, if applicable, of this object
|
||||
*
|
||||
* <p>
|
||||
* This searches for the conventional stateful object defining this object's execution state. If
|
||||
* such an object does not exist, null is returned. If one does exist, then its execution state
|
||||
* at the given snap is returned. If that state is null, it is assumed
|
||||
* {@link TargetExecutionState#INACTIVE}.
|
||||
*
|
||||
* @param snap the snap
|
||||
* @return the state or null
|
||||
*/
|
||||
default TargetExecutionState getExecutionState(long snap) {
|
||||
TraceObject stateful = querySuitableTargetInterface(TargetExecutionStateful.class);
|
||||
if (stateful == null) {
|
||||
return null;
|
||||
}
|
||||
TraceObjectValue stateVal =
|
||||
stateful.getAttribute(snap, TargetExecutionStateful.STATE_ATTRIBUTE_NAME);
|
||||
if (stateVal == null) {
|
||||
return TargetExecutionState.INACTIVE;
|
||||
}
|
||||
return TargetExecutionState.valueOf((String) stateVal.getValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -312,7 +312,7 @@ public class PatchStep implements Step {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
|
||||
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", sleigh + ";");
|
||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/* ###
|
||||
* 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.trace.model.time.schedule;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A generator of an emulator's thread schedule
|
||||
*/
|
||||
public interface Scheduler {
|
||||
|
||||
/**
|
||||
* Create a scheduler that allocates all slices to a single thread
|
||||
*
|
||||
* @param thread the thread to schedule
|
||||
* @return the scheduler
|
||||
*/
|
||||
static Scheduler oneThread(TraceThread thread) {
|
||||
long key = thread == null ? -1 : thread.getKey();
|
||||
return new Scheduler() {
|
||||
@Override
|
||||
public TickStep nextSlice(Trace trace) {
|
||||
return new TickStep(key, 1000);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface RunResult {
|
||||
/**
|
||||
* Get the actual schedule executed
|
||||
*
|
||||
* <p>
|
||||
* It is possible for the machine to be interrupted mid-instruction. If this is the case,
|
||||
* the trace schedule will indicate the p-code steps taken.
|
||||
*
|
||||
* @return the schedule
|
||||
*/
|
||||
public TraceSchedule schedule();
|
||||
|
||||
/**
|
||||
* Get the error that interrupted execution
|
||||
*
|
||||
* <p>
|
||||
* Ideally, this is a {@link InterruptPcodeExecutionException}, indicating a breakpoint
|
||||
* trapped the emulator, but it could be a number of things:
|
||||
*
|
||||
* <ul>
|
||||
* <li>An instruction decode error</li>
|
||||
* <li>An unimplemented instruction</li>
|
||||
* <li>An unimplemented p-code userop</li>
|
||||
* <li>An error accessing the machine state</li>
|
||||
* <li>A runtime error in the implementation of a p-code userop</li>
|
||||
* <li>A runtime error in the implementation of the emulator, in which case, a bug should be
|
||||
* filed</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the error
|
||||
*/
|
||||
public Throwable error();
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of running a machine
|
||||
*/
|
||||
record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next step to schedule
|
||||
*
|
||||
* @return the (instruction-level) thread and tick count
|
||||
*/
|
||||
TickStep nextSlice(Trace trace);
|
||||
|
||||
/**
|
||||
* Run a machine according to the given schedule until it is interrupted
|
||||
*
|
||||
* <p>
|
||||
* This method will drop p-code steps from injections, including those from execution
|
||||
* breakpoints. The goal is to ensure that the returned schedule can be used to recover the same
|
||||
* state on a machine without injections. Unfortunately, injections which modify the machine
|
||||
* state, other than unique variables, will defeat that goal.
|
||||
*
|
||||
* @param trace the trace whose threads to schedule
|
||||
* @param eventThread the first thread to schedule if the scheduler doesn't specify
|
||||
* @param machine the machine to run
|
||||
* @param monitor a monitor for cancellation
|
||||
* @return the result of execution
|
||||
*/
|
||||
default RunResult run(Trace trace, TraceThread eventThread, PcodeMachine<?> machine,
|
||||
TaskMonitor monitor) {
|
||||
TraceThreadManager tm = trace.getThreadManager();
|
||||
TraceSchedule completedSteps = TraceSchedule.snap(0);
|
||||
PcodeThread<?> emuThread = null;
|
||||
int completedTicks = 0;
|
||||
try {
|
||||
while (true) {
|
||||
TickStep slice = nextSlice(trace);
|
||||
eventThread = slice.getThread(tm, eventThread);
|
||||
emuThread = machine.getThread(eventThread.getPath(), true);
|
||||
for (int i = 0; i < slice.tickCount; i++) {
|
||||
monitor.checkCanceled();
|
||||
emuThread.stepInstruction();
|
||||
completedTicks++;
|
||||
}
|
||||
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
|
||||
completedTicks = 0;
|
||||
}
|
||||
}
|
||||
catch (PcodeExecutionException e) {
|
||||
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
|
||||
if (emuThread.getInstruction() == null) {
|
||||
return new RecordRunResult(completedSteps, e);
|
||||
}
|
||||
PcodeFrame frame = e.getFrame();
|
||||
// Rewind one so stepping retries the op causing the error
|
||||
int count = frame.count() - 1;
|
||||
if (frame == null || count == 0) {
|
||||
// If we've decoded, but could execute the first op, just drop the p-code steps
|
||||
return new RecordRunResult(completedSteps, e);
|
||||
}
|
||||
// The +1 accounts for the decode step
|
||||
return new RecordRunResult(
|
||||
completedSteps.steppedPcodeForward(eventThread, count + 1), e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return new RecordRunResult(
|
||||
completedSteps.steppedForward(eventThread, completedTicks), e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -384,8 +384,8 @@ public class Sequence implements Comparable<Sequence> {
|
|||
* @return the last trace thread stepped during execution
|
||||
* @throws CancelledException if execution is cancelled
|
||||
*/
|
||||
public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine,
|
||||
Stepper<T> stepper, TaskMonitor monitor) throws CancelledException {
|
||||
public TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<?> machine,
|
||||
Stepper stepper, TaskMonitor monitor) throws CancelledException {
|
||||
TraceThreadManager tm = trace.getThreadManager();
|
||||
TraceThread thread = eventThread;
|
||||
for (Step step : steps) {
|
||||
|
|
|
@ -66,7 +66,7 @@ public class SkipStep extends AbstractStep {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
|
||||
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
for (int i = 0; i < tickCount; i++) {
|
||||
monitor.incrementProgress(1);
|
||||
|
|
|
@ -170,20 +170,20 @@ public interface Step extends Comparable<Step> {
|
|||
return compareStep(that).compareTo;
|
||||
}
|
||||
|
||||
default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
|
||||
PcodeMachine<T> machine, Stepper<T> stepper, TaskMonitor monitor)
|
||||
default TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
|
||||
PcodeMachine<?> machine, Stepper stepper, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
TraceThread thread = getThread(tm, eventThread);
|
||||
if (machine == null) {
|
||||
// Just performing validation (specifically thread parts)
|
||||
return thread;
|
||||
}
|
||||
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
||||
PcodeThread<?> emuThread = machine.getThread(thread.getPath(), true);
|
||||
execute(emuThread, stepper, monitor);
|
||||
return thread;
|
||||
}
|
||||
|
||||
<T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
|
||||
<T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
|
||||
throws CancelledException;
|
||||
|
||||
long coalescePatches(Language language, List<Step> steps);
|
||||
|
|
|
@ -17,44 +17,41 @@ package ghidra.trace.model.time.schedule;
|
|||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
|
||||
public interface Stepper<T> {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public interface Stepper {
|
||||
enum Enum implements Stepper {
|
||||
INSTRUCTION {
|
||||
@Override
|
||||
public void tick(PcodeThread thread) {
|
||||
public void tick(PcodeThread<?> thread) {
|
||||
thread.stepInstruction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(PcodeThread thread) {
|
||||
public void skip(PcodeThread<?> thread) {
|
||||
thread.skipInstruction();
|
||||
}
|
||||
},
|
||||
PCODE {
|
||||
@Override
|
||||
public void tick(PcodeThread thread) {
|
||||
public void tick(PcodeThread<?> thread) {
|
||||
thread.stepPcodeOp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(PcodeThread thread) {
|
||||
public void skip(PcodeThread<?> thread) {
|
||||
thread.skipPcodeOp();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Stepper<T> instruction() {
|
||||
static Stepper instruction() {
|
||||
return Enum.INSTRUCTION;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Stepper<T> pcode() {
|
||||
static Stepper pcode() {
|
||||
return Enum.PCODE;
|
||||
}
|
||||
|
||||
void tick(PcodeThread<T> thread);
|
||||
void tick(PcodeThread<?> thread);
|
||||
|
||||
void skip(PcodeThread<T> thread);
|
||||
void skip(PcodeThread<?> thread);
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class TickStep extends AbstractStep {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
|
||||
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
for (int i = 0; i < tickCount; i++) {
|
||||
monitor.incrementProgress(1);
|
||||
|
|
|
@ -561,6 +561,32 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
return new TraceSchedule(snap, ticks, new Sequence());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the schedule resulting from this schedule advanced by the given schedule
|
||||
*
|
||||
* <p>
|
||||
* This operation cannot be used to append instruction steps after p-code steps. Thus, if this
|
||||
* schedule contains any p-code steps and {@code} next has instruction steps, an error will be
|
||||
*
|
||||
* @param next the schedule to append. Its snap is ignored.
|
||||
* @return the complete schedule
|
||||
* @throws IllegalArgumentException if the result would have instruction steps following p-code
|
||||
* steps
|
||||
*/
|
||||
public TraceSchedule advanced(TraceSchedule next) {
|
||||
if (this.pSteps.isNop()) {
|
||||
Sequence ticks = this.steps.clone();
|
||||
ticks.advance(next.steps);
|
||||
return new TraceSchedule(this.snap, ticks, next.pSteps.clone());
|
||||
}
|
||||
else if (next.steps.isNop()) {
|
||||
Sequence pTicks = this.steps.clone();
|
||||
pTicks.advance(next.pSteps);
|
||||
return new TraceSchedule(this.snap, this.steps.clone(), pTicks);
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot have instructions steps following p-code steps");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the threads involved in the schedule
|
||||
*
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.util.*;
|
|||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
|
@ -70,7 +70,10 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
|
|||
protected final Collection<PcodeThread<T>> threadsView =
|
||||
Collections.unmodifiableCollection(threads.values());
|
||||
|
||||
protected volatile boolean suspended = false;
|
||||
protected final Map<Address, PcodeProgram> injects = new HashMap<>();
|
||||
protected final SparseAddressRangeMap<AccessKind> accessBreakpoints =
|
||||
new SparseAddressRangeMap<>();
|
||||
|
||||
/**
|
||||
* Construct a p-code machine with the given language and arithmetic
|
||||
|
@ -246,6 +249,11 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
|
|||
return sharedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuspended(boolean suspended) {
|
||||
this.suspended = suspended;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a p-code injection (override) at the given address
|
||||
*
|
||||
|
@ -300,4 +308,44 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
|
|||
""", sleighCondition));
|
||||
injects.put(address, pcode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAccessBreakpoint(AddressRange range, AccessKind kind) {
|
||||
accessBreakpoints.put(range, kind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAccessBreakpoints() {
|
||||
accessBreakpoints.clear();
|
||||
}
|
||||
|
||||
protected void checkLoad(AddressSpace space, T offset) {
|
||||
if (accessBreakpoints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long concrete = arithmetic.toLong(offset, Purpose.LOAD);
|
||||
if (accessBreakpoints.hasEntry(space.getAddress(concrete), AccessKind::trapsRead)) {
|
||||
throw new InterruptPcodeExecutionException(null, null);
|
||||
}
|
||||
}
|
||||
catch (ConcretionError e) {
|
||||
// Consider this not hitting any breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkStore(AddressSpace space, T offset) {
|
||||
if (accessBreakpoints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long concrete = arithmetic.toLong(offset, Purpose.LOAD);
|
||||
if (accessBreakpoints.hasEntry(space.getAddress(concrete), AccessKind::trapsWrite)) {
|
||||
throw new InterruptPcodeExecutionException(null, null);
|
||||
}
|
||||
}
|
||||
catch (ConcretionError e) {
|
||||
// Consider this not hitting any breakpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.pcode.exec.*;
|
|||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
@ -157,12 +158,22 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
|
||||
@Override
|
||||
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
|
||||
if (suspended) {
|
||||
if (suspended || thread.machine.suspended) {
|
||||
throw new SuspendedPcodeExecutionException(frame, null);
|
||||
}
|
||||
super.stepOp(op, frame, library);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkLoad(AddressSpace space, T offset) {
|
||||
thread.checkLoad(space, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkStore(AddressSpace space, T offset) {
|
||||
thread.checkStore(space, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void branchToAddress(Address target) {
|
||||
thread.overrideCounter(target);
|
||||
|
@ -606,4 +617,12 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
public void clearAllInjects() {
|
||||
injects.clear();
|
||||
}
|
||||
|
||||
protected void checkLoad(AddressSpace space, T offset) {
|
||||
machine.checkLoad(space, offset);
|
||||
}
|
||||
|
||||
protected void checkStore(AddressSpace space, T offset) {
|
||||
machine.checkStore(space, offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,11 @@ import java.util.Collection;
|
|||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
||||
/**
|
||||
* A machine which execute p-code on state of an abstract type
|
||||
|
@ -29,6 +33,45 @@ import ghidra.program.model.address.Address;
|
|||
*/
|
||||
public interface PcodeMachine<T> {
|
||||
|
||||
/**
|
||||
* The kind of access breakpoint
|
||||
*/
|
||||
enum AccessKind {
|
||||
/** A read access breakpoint */
|
||||
R(true, false),
|
||||
/** A write access breakpoint */
|
||||
W(false, true),
|
||||
/** A read/write access breakpoint */
|
||||
RW(true, true);
|
||||
|
||||
private final boolean trapsRead;
|
||||
private final boolean trapsWrite;
|
||||
|
||||
private AccessKind(boolean trapsRead, boolean trapsWrite) {
|
||||
this.trapsRead = trapsRead;
|
||||
this.trapsWrite = trapsWrite;
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this kind of breakpoint should trap a read, i.e., {@link PcodeOp#LOAD}
|
||||
*
|
||||
* @return true to interrupt
|
||||
*/
|
||||
public boolean trapsRead() {
|
||||
return trapsRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this kind of breakpoint should trap a write, i.e., {@link PcodeOp#STORE}
|
||||
*
|
||||
* @return true to interrupt
|
||||
*/
|
||||
public boolean trapsWrite() {
|
||||
return trapsWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the machine's Sleigh language (processor model)
|
||||
*
|
||||
|
@ -112,6 +155,13 @@ public interface PcodeMachine<T> {
|
|||
*/
|
||||
PcodeExecutorState<T> getSharedState();
|
||||
|
||||
/**
|
||||
* Set the suspension state of the machine
|
||||
*
|
||||
* @see PcodeThread#setSuspended(boolean)
|
||||
*/
|
||||
void setSuspended(boolean suspended);
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh code for execution by a thread of this machine
|
||||
*
|
||||
|
@ -130,8 +180,8 @@ public interface PcodeMachine<T> {
|
|||
*
|
||||
* <p>
|
||||
* This will attempt to compile the given source against this machine's userop library and then
|
||||
* will inject it at the given address. The resulting p-code <em>replaces</em> that which would
|
||||
* be executed by decoding the instruction at the given address. The means the machine will not
|
||||
* inject it at the given address. The resulting p-code <em>replaces</em> that which would be
|
||||
* executed by decoding the instruction at the given address. The means the machine will not
|
||||
* decode, nor advance its counter, unless the Sleigh causes it. In most cases, the Sleigh will
|
||||
* call {@link PcodeEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and
|
||||
* execute the overridden instruction.
|
||||
|
@ -141,6 +191,11 @@ public interface PcodeMachine<T> {
|
|||
* replaced and the old inject completely forgotten. The injector does not support chaining or
|
||||
* double-wrapping, etc.
|
||||
*
|
||||
* <p>
|
||||
* No synchronization is provided on the internal injection storage. Clients should ensure the
|
||||
* machine is not executing when injecting p-code. Additionally, the client must ensure only one
|
||||
* thread is injecting p-code to the machine at a time.
|
||||
*
|
||||
* @param address the address to inject at
|
||||
* @param source the Sleigh source to compile and inject
|
||||
*/
|
||||
|
@ -155,19 +210,65 @@ public interface PcodeMachine<T> {
|
|||
|
||||
/**
|
||||
* Remove all injects from this machine
|
||||
*
|
||||
* <p>
|
||||
* This will clear execution breakpoints, but not access breakpoints. See
|
||||
* {@link #clearAccessBreakpoints()}.
|
||||
*/
|
||||
void clearAllInjects();
|
||||
|
||||
/**
|
||||
* Add a (conditional) breakpoint at the given address
|
||||
* Add a conditional execution breakpoint at the given address
|
||||
*
|
||||
* <p>
|
||||
* Breakpoints are implemented at the p-code level using an inject, without modification to the
|
||||
* emulated image. As such, it cannot coexist with another inject. A client needing to break
|
||||
* during an inject must use {@link PcodeEmulationLibrary#emu_swi()} in the injected Sleigh.
|
||||
*
|
||||
* <p>
|
||||
* No synchronization is provided on the internal breakpoint storage. Clients should ensure the
|
||||
* machine is not executing when adding breakpoints. Additionally, the client must ensure only
|
||||
* one thread is adding breakpoints to the machine at a time.
|
||||
*
|
||||
* @param address the address at which to break
|
||||
* @param sleighCondition a Sleigh expression which controls the breakpoint
|
||||
*/
|
||||
void addBreakpoint(Address address, String sleighCondition);
|
||||
|
||||
/**
|
||||
* Add an access breakpoint over the given range
|
||||
*
|
||||
* <p>
|
||||
* Access breakpoints are implemented out of band, without modification to the emulated image.
|
||||
* The breakpoints are only effective for p-code {@link PcodeOp#LOAD} and {@link PcodeOp#STORE}
|
||||
* operations with concrete offsets. Thus, an operation that refers directly to a memory
|
||||
* address, e.g., a memory-mapped register, will not be trapped. Similarly, access breakpoints
|
||||
* on registers or unique variables will not work. Access to an abstract offset that cannot be
|
||||
* made concrete, i.e., via {@link PcodeArithmetic#toConcrete(Object, Purpose)} cannot be
|
||||
* trapped. To interrupt on direct and/or abstract accesses, consider wrapping the relevant
|
||||
* state and/or overriding {@link PcodeExecutorStatePiece#getVar(Varnode, Reason)} and related.
|
||||
* For accesses to abstract offsets, consider overriding
|
||||
* {@link AbstractPcodeMachine#checkLoad(AddressSpace, Object)} and/or
|
||||
* {@link AbstractPcodeMachine#checkStore(AddressSpace, Object)} instead.
|
||||
*
|
||||
* <p>
|
||||
* A breakpoint's range cannot cross more than one page boundary. Pages are 4096 bytes each.
|
||||
* This allows implementations to optimize checking for breakpoints. If a breakpoint does not
|
||||
* follow this rule, the behavior is undefined. Breakpoints may overlap, but currently no
|
||||
* indication is given as to which breakpoint interrupted emulation.
|
||||
*
|
||||
* <p>
|
||||
* No synchronization is provided on the internal breakpoint storage. Clients should ensure the
|
||||
* machine is not executing when adding breakpoints. Additionally, the client must ensure only
|
||||
* one thread is adding breakpoints to the machine at a time.
|
||||
*
|
||||
* @param range the address range to trap
|
||||
* @param kind the kind of access to trap
|
||||
*/
|
||||
void addAccessBreakpoint(AddressRange range, AccessKind kind);
|
||||
|
||||
/**
|
||||
* Remove all access breakpoints from this machine
|
||||
*/
|
||||
void clearAccessBreakpoints();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/* ###
|
||||
* 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.pcode.emu;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public class SparseAddressRangeMap<V> {
|
||||
public static final long PAGE_BITS = 12;
|
||||
public static final long PAGE_MASK = -1L << PAGE_BITS;
|
||||
public static final long OFF_MASK = ~PAGE_MASK;
|
||||
|
||||
private static class Space<V> {
|
||||
private final Map<Long, Page<V>> pages = new HashMap<>();
|
||||
|
||||
private static long getPageIndex(Address addr) {
|
||||
return addr.getOffset() >> PAGE_BITS;
|
||||
}
|
||||
|
||||
Entry<AddressRange, V> put(Entry<AddressRange, V> entry) {
|
||||
AddressRange range = entry.getKey();
|
||||
long indexMin = getPageIndex(range.getMinAddress());
|
||||
Page<V> pageMin = pages.computeIfAbsent(indexMin, o -> new Page<>());
|
||||
pageMin.put(entry);
|
||||
long indexMax = getPageIndex(range.getMaxAddress());
|
||||
if (indexMax == indexMin) {
|
||||
return entry;
|
||||
}
|
||||
Page<V> pageMax = pages.computeIfAbsent(indexMax, o -> new Page<>());
|
||||
return pageMax.put(entry);
|
||||
}
|
||||
|
||||
boolean hasEntry(Address address, Predicate<V> predicate) {
|
||||
Page<V> page = pages.get(getPageIndex(address));
|
||||
if (page == null) {
|
||||
return false;
|
||||
}
|
||||
return page.hasEntry(address, predicate);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Page<V> {
|
||||
static final Comparator<Entry<AddressRange, ?>> ENTRY_COMPARATOR = Page::compareEntries;
|
||||
private final List<Entry<AddressRange, V>> entries = new ArrayList<>();
|
||||
|
||||
private static int compareEntries(Entry<AddressRange, ?> e1, Entry<AddressRange, ?> e2) {
|
||||
return e1.getKey().getMinAddress().compareTo(e2.getKey().getMinAddress());
|
||||
}
|
||||
|
||||
Entry<AddressRange, V> put(Entry<AddressRange, V> entry) {
|
||||
int index = Collections.binarySearch(entries, entry, ENTRY_COMPARATOR);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
}
|
||||
entries.add(index, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
boolean hasEntry(Address address, Predicate<V> predicate) {
|
||||
for (Entry<AddressRange, V> ent : entries) {
|
||||
AddressRange range = ent.getKey();
|
||||
if (range.contains(address)) {
|
||||
if (predicate.test(ent.getValue())) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (address.compareTo(range.getMinAddress()) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<AddressSpace, Space<V>> spaces = new HashMap<>();
|
||||
private boolean isEmpty = true;
|
||||
|
||||
public Entry<AddressRange, V> put(AddressRange range, V value) {
|
||||
Space<V> space = spaces.computeIfAbsent(range.getAddressSpace(), s -> new Space<>());
|
||||
Entry<AddressRange, V> entry = space.put(Map.entry(range, value));
|
||||
isEmpty = false;
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasEntry(Address address, Predicate<V> predicate) {
|
||||
Space<V> space = spaces.get(address.getAddressSpace());
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return space.hasEntry(address, predicate);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
spaces.clear();
|
||||
isEmpty = true;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return isEmpty;
|
||||
}
|
||||
}
|
|
@ -329,6 +329,15 @@ public class PcodeExecutor<T> {
|
|||
state.setVar(outVar, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point: logic preceding a load
|
||||
*
|
||||
* @param space the address space to be loaded from
|
||||
* @param offset the offset about to be loaded from
|
||||
*/
|
||||
protected void checkLoad(AddressSpace space, T offset) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a load
|
||||
*
|
||||
|
@ -339,6 +348,7 @@ public class PcodeExecutor<T> {
|
|||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
|
||||
Varnode inOffset = op.getInput(1);
|
||||
T offset = state.getVar(inOffset, reason);
|
||||
checkLoad(space, offset);
|
||||
Varnode outvar = op.getOutput();
|
||||
|
||||
T out = state.getVar(space, offset, outvar.getSize(), true, reason);
|
||||
|
@ -347,6 +357,15 @@ public class PcodeExecutor<T> {
|
|||
state.setVar(outvar, mod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point: logic preceding a store
|
||||
*
|
||||
* @param space the address space to be stored to
|
||||
* @param offset the offset about to be stored to
|
||||
*/
|
||||
protected void checkStore(AddressSpace space, T offset) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a store
|
||||
*
|
||||
|
@ -357,6 +376,7 @@ public class PcodeExecutor<T> {
|
|||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
|
||||
Varnode inOffset = op.getInput(1);
|
||||
T offset = state.getVar(inOffset, reason);
|
||||
checkStore(space, offset);
|
||||
Varnode valVar = op.getInput(2);
|
||||
|
||||
T val = state.getVar(valVar, reason);
|
||||
|
|
|
@ -130,6 +130,7 @@ public class PcodeFrame {
|
|||
private final List<PcodeOp> code;
|
||||
private final Map<Integer, String> useropNames;
|
||||
|
||||
private int count = 0;
|
||||
private int index = 0;
|
||||
private int branched = -1;
|
||||
|
||||
|
@ -157,6 +158,19 @@ public class PcodeFrame {
|
|||
return new MyFormatter().formatOps(language, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of p-code ops executed
|
||||
*
|
||||
* <p>
|
||||
* Contrast this to {@link #index()}, which marks the next op to be executed. This counts the
|
||||
* number of ops executed, which will differ from index when an internal branch is taken.
|
||||
*
|
||||
* @return the count
|
||||
*/
|
||||
public int count() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* The index of the <em>next</em> p-code op to be executed
|
||||
*
|
||||
|
@ -190,6 +204,7 @@ public class PcodeFrame {
|
|||
* @return the value of the index <em>before</em> it was advanced
|
||||
*/
|
||||
public int advance() {
|
||||
count++;
|
||||
return index++;
|
||||
}
|
||||
|
||||
|
@ -199,6 +214,7 @@ public class PcodeFrame {
|
|||
* @return the value of the index <em>before</em> it was stepped back
|
||||
*/
|
||||
public int stepBack() {
|
||||
count--;
|
||||
return index--;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* ###
|
||||
* 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.pcode.emu;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public class SparseAddressRangeMapTest {
|
||||
AddressSpace space = new GenericAddressSpace("test", 64, AddressSpace.TYPE_RAM, 0);
|
||||
|
||||
Address addr(long off) {
|
||||
return space.getAddress(off);
|
||||
}
|
||||
|
||||
AddressRange range(long min, long max) {
|
||||
return new AddressRangeImpl(addr(min), addr(max));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() {
|
||||
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
|
||||
assertTrue(map.isEmpty());
|
||||
|
||||
map.put(range(0x0, 0xff), "Hello!");
|
||||
assertFalse(map.isEmpty());
|
||||
|
||||
map.clear();
|
||||
assertTrue(map.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasEntry() {
|
||||
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
|
||||
assertFalse(map.hasEntry(addr(0x0f), "Hello!"::equals));
|
||||
|
||||
map.put(range(0x0, 0xff), "Hello!");
|
||||
assertTrue(map.hasEntry(addr(0x0f), "Hello!"::equals));
|
||||
assertFalse(map.hasEntry(addr(0x100), "Hello!"::equals));
|
||||
assertFalse(map.hasEntry(addr(0x0f), "Good bye!"::equals));
|
||||
|
||||
map.clear();
|
||||
assertFalse(map.hasEntry(addr(0x0f), "Hello!"::equals));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasEntrySpansPages() {
|
||||
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
|
||||
map.put(range(0x100, 0x1100), "Hello!");
|
||||
assertTrue(map.hasEntry(addr(0x0fff), "Hello!"::equals));
|
||||
assertTrue(map.hasEntry(addr(0x1000), "Hello!"::equals));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasEntryOverlapping() {
|
||||
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
|
||||
map.put(range(0x0, 0xff), "Hello!");
|
||||
map.put(range(0x10, 0x10f), "Good bye!");
|
||||
assertTrue(map.hasEntry(addr(0x0f), "Hello!"::equals));
|
||||
assertTrue(map.hasEntry(addr(0x20), "Hello!"::equals));
|
||||
assertTrue(map.hasEntry(addr(0x20), "Good bye!"::equals));
|
||||
assertTrue(map.hasEntry(addr(0x100), "Good bye!"::equals));
|
||||
}
|
||||
}
|