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"),
|
RETURN("return"),
|
||||||
STEP("step"),
|
STEP("step"),
|
||||||
STEPI("stepi", "step-instruction"),
|
STEPI("stepi", "step-instruction"),
|
||||||
UNTIL("until"),
|
UNTIL("until");
|
||||||
/** User-defined */
|
|
||||||
EXTENDED("echo extended-step?", "???"),;
|
|
||||||
|
|
||||||
public final String mi2;
|
public final String mi2;
|
||||||
public final String cli;
|
public final String cli;
|
||||||
|
|
|
@ -19,9 +19,8 @@ import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.gdb.manager.*;
|
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.GdbConsoleExecCommand.CompletesWithRunning;
|
||||||
|
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
|
||||||
import agent.gdb.manager.reason.*;
|
import agent.gdb.manager.reason.*;
|
||||||
import ghidra.async.AsyncFence;
|
import ghidra.async.AsyncFence;
|
||||||
import ghidra.dbg.agent.DefaultTargetObject;
|
import ghidra.dbg.agent.DefaultTargetObject;
|
||||||
|
@ -43,7 +42,7 @@ public class GdbModelTargetInferior
|
||||||
extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer>
|
extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer>
|
||||||
implements TargetProcess, TargetAggregate, TargetExecutionStateful, TargetAttacher,
|
implements TargetProcess, TargetAggregate, TargetExecutionStateful, TargetAttacher,
|
||||||
TargetDeletable, TargetDetachable, TargetKillable, TargetLauncher, TargetResumable,
|
TargetDeletable, TargetDetachable, TargetKillable, TargetLauncher, TargetResumable,
|
||||||
TargetSteppable, GdbModelSelectableObject {
|
TargetSteppable, TargetInterruptible, GdbModelSelectableObject {
|
||||||
|
|
||||||
public static final String EXIT_CODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "exit_code";
|
public static final String EXIT_CODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "exit_code";
|
||||||
|
|
||||||
|
@ -187,39 +186,18 @@ public class GdbModelTargetInferior
|
||||||
return impl.gateFuture(inferior.cont());
|
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
|
@Override
|
||||||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case SKIP:
|
case SKIP:
|
||||||
|
case EXTENDED:
|
||||||
throw new UnsupportedOperationException(kind.name());
|
throw new UnsupportedOperationException(kind.name());
|
||||||
case ADVANCE: // Why no exec-advance in GDB/MI?
|
case ADVANCE: // Why no exec-advance in GDB/MI?
|
||||||
// TODO: This doesn't work, since advance requires a parameter
|
// TODO: This doesn't work, since advance requires a parameter
|
||||||
return model.gateFuture(inferior.console("advance", CompletesWithRunning.MUST));
|
return model.gateFuture(inferior.console("advance", CompletesWithRunning.MUST));
|
||||||
default:
|
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());
|
return model.gateFuture(inferior.kill());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> interrupt() {
|
||||||
|
return impl.session.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> attach(TargetAttachable attachable) {
|
public CompletableFuture<Void> attach(TargetAttachable attachable) {
|
||||||
GdbModelTargetAttachable mine = impl.assertMine(GdbModelTargetAttachable.class, 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) {
|
switch (kind) {
|
||||||
case FINISH:
|
case FINISH:
|
||||||
return StepCmd.FINISH;
|
return StepCmd.FINISH;
|
||||||
|
@ -203,8 +203,6 @@ public class GdbModelTargetThread
|
||||||
return StepCmd.RETURN;
|
return StepCmd.RETURN;
|
||||||
case UNTIL:
|
case UNTIL:
|
||||||
return StepCmd.UNTIL;
|
return StepCmd.UNTIL;
|
||||||
case EXTENDED:
|
|
||||||
return StepCmd.EXTENDED;
|
|
||||||
default:
|
default:
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
@ -214,6 +212,7 @@ public class GdbModelTargetThread
|
||||||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case SKIP:
|
case SKIP:
|
||||||
|
case EXTENDED:
|
||||||
throw new UnsupportedOperationException(kind.name());
|
throw new UnsupportedOperationException(kind.name());
|
||||||
case ADVANCE: // Why no exec-advance in GDB/MI?
|
case ADVANCE: // Why no exec-advance in GDB/MI?
|
||||||
// TODO: This doesn't work, since advance requires a parameter
|
// 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/DebuggerBreakpointsPlugin/images/breakpoints-make-effective.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html||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/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/DebuggerCopyActionsPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerDisassemblerPlugin/DebuggerDisassemblerPlugin.html||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/DebuggerRegistersPlugin/images/select-registers.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||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/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/DebuggerStaticMappingPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END|
|
||||||
|
|
|
@ -153,9 +153,9 @@
|
||||||
sortgroup="n"
|
sortgroup="n"
|
||||||
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
|
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerStateEditingPlugin" text="Editing Machine State"
|
<tocdef id="DebuggerControlPlugin" text="Control and Machine State"
|
||||||
sortgroup="n1"
|
sortgroup="n1"
|
||||||
target="help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html" />
|
target="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"
|
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"
|
||||||
sortgroup="o"
|
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,
|
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>
|
up-to-date contents are displayed with the default background color.</P>
|
||||||
|
|
||||||
<P>The dynamic listing supports editing memory via the <A href=
|
<P>The dynamic listing supports editing memory. See <A href=
|
||||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State</A>.
|
||||||
Plugin and Service</A>. Such edits are performed as usual: Via the <A href=
|
Such edits are performed as usual: Via the <A href=
|
||||||
"help/topics/AssemblerPlugin/Assembler.htm">Patch</A> actions, or by pasting byte strings.
|
"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>
|
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,
|
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>
|
up-to-date contents are displayed with the default background color.</P>
|
||||||
|
|
||||||
<P>The dynamic listing supports editing memory via the <A href=
|
<P>The dynamic listing supports editing memory. See<A href=
|
||||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State</A>.
|
||||||
Plugin and Service</A>. Such edits are performed as usual: Toggling edits and typing into the
|
Such edits are performed as usual: Toggling edits and typing into the editor, or by pasting
|
||||||
editor, or by pasting byte strings. These edits may be directed toward a live target, the
|
byte strings. These edits may be directed toward a live target, the trace, or the emulator.
|
||||||
trace, or the emulator. <B>NOTE:</B> Please be wary of hand-typing large edits into the
|
<B>NOTE:</B> Please be wary of hand-typing large edits into the emulator, since every keystroke
|
||||||
emulator, since every keystroke may produce a unique scratch snapshot. It is better to paste
|
may produce a unique scratch snapshot. It is better to paste such edits instead.</P>
|
||||||
such edits instead.</P>
|
|
||||||
|
|
||||||
<H2>Actions</H2>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@
|
||||||
|
|
||||||
<P>Interrupt the current target's execution.</P>
|
<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>
|
<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>
|
<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>
|
<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
|
<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
|
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
|
selected memory space. This field is user modifiable when the <B>Enable Edits</B> toggle is
|
||||||
on, and the <A href=
|
on, and the register is modifiable. Edits may be directed toward a live target, the trace, or
|
||||||
"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. Values changed by the last event are displayed in <FONT color=
|
the emulator. Values changed by the last event are displayed in <FONT color=
|
||||||
"red">red</FONT>.</LI>
|
"red">red</FONT>.</LI>
|
||||||
|
|
||||||
|
@ -102,9 +100,9 @@
|
||||||
|
|
||||||
<P>This toggle is a write protector for machine state. To modify register values, this toggle
|
<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=
|
must be enabled. Edits are directed according the to <A href=
|
||||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State
|
||||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
Plugin</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr" column
|
||||||
column cannot be edited, yet.</P>
|
cannot be edited, yet.</P>
|
||||||
|
|
||||||
<H3><A name="clone_window"></A>Clone Window</H3>
|
<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
|
<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 its hexadecimal value. This field is user modifiable when the <B>Enable Edits</B> toggle
|
||||||
is on, and the <A href=
|
is on, and the variable is modifiable. Edits may be directed toward a live target, the trace,
|
||||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
or the emulator. If the value has changed since the last navigation event, this cell is
|
||||||
reports the register as modifiable. Edits may be directed toward a live target, the trace, or
|
rendered in <FONT color="red">red</FONT>.</LI>
|
||||||
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
|
<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
|
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
|
<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=
|
must be enabled. Edits are directed according the to <A href=
|
||||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State
|
||||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
Plugin</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr" column
|
||||||
column cannot be edited, yet.</P>
|
cannot be edited, yet.</P>
|
||||||
|
|
||||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||||
|
|
||||||
|
|
|
@ -709,7 +709,7 @@ public class DebuggerCoordinates {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlive() {
|
public boolean isAlive() {
|
||||||
return recorder != null;
|
return recorder != null && recorder.isRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPresent() {
|
public boolean isPresent() {
|
||||||
|
|
|
@ -22,15 +22,12 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
import docking.action.builder.*;
|
import docking.action.builder.*;
|
||||||
import docking.menu.ActionState;
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.time.DebuggerTimePlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
|
||||||
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
@ -183,10 +179,10 @@ public interface DebuggerResources {
|
||||||
ImageIcon ICON_EDIT_MODE_WRITE_EMULATOR =
|
ImageIcon ICON_EDIT_MODE_WRITE_EMULATOR =
|
||||||
ResourceManager.loadImage("images/write-emulator.png");
|
ResourceManager.loadImage("images/write-emulator.png");
|
||||||
|
|
||||||
String NAME_EDIT_MODE_READ_ONLY = "Read Only";
|
String NAME_EDIT_MODE_READ_ONLY = "Control Target w/ Edits Disabled";
|
||||||
String NAME_EDIT_MODE_WRITE_TARGET = "Write Target";
|
String NAME_EDIT_MODE_WRITE_TARGET = "Control Target";
|
||||||
String NAME_EDIT_MODE_WRITE_TRACE = "Write Trace";
|
String NAME_EDIT_MODE_WRITE_TRACE = "Control Trace";
|
||||||
String NAME_EDIT_MODE_WRITE_EMULATOR = "Write Emulator";
|
String NAME_EDIT_MODE_WRITE_EMULATOR = "Control Emulator";
|
||||||
|
|
||||||
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
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 {
|
interface LimitToCurrentSnapAction {
|
||||||
String NAME = "Limit to Current Snap";
|
String NAME = "Limit to Current Snap";
|
||||||
String DESCRIPTION = "Choose whether displayed objects must be alive at the 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.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
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.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.breakpoint.*;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.model.time.schedule.CompareResult;
|
import ghidra.trace.model.time.schedule.*;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.Scheduler.RunResult;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -139,27 +142,19 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class CachedEmulator {
|
protected abstract class AbstractEmulateTask<T> extends Task {
|
||||||
final DebuggerPcodeMachine<?> emulator;
|
protected final CompletableFuture<T> future = new CompletableFuture<>();
|
||||||
|
|
||||||
public CachedEmulator(DebuggerPcodeMachine<?> emulator) {
|
public AbstractEmulateTask(String title, boolean hasProgress) {
|
||||||
this.emulator = emulator;
|
super(title, true, hasProgress, false, false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class EmulateTask extends Task {
|
protected abstract T compute(TaskMonitor monitor) throws CancelledException;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
try {
|
try {
|
||||||
future.complete(doEmulate(key, monitor));
|
future.complete(compute(monitor));
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
future.completeExceptionally(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 =
|
protected DebuggerPcodeEmulatorFactory emulatorFactory =
|
||||||
new BytesDebuggerPcodeEmulatorFactory();
|
new BytesDebuggerPcodeEmulatorFactory();
|
||||||
|
|
||||||
|
@ -181,6 +206,53 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||||
new AsyncLazyMap<>(new HashMap<>(), this::doBackgroundEmulate)
|
new AsyncLazyMap<>(new HashMap<>(), this::doBackgroundEmulate)
|
||||||
.forgetErrors((key, t) -> true)
|
.forgetErrors((key, t) -> true)
|
||||||
.forgetValues((key, l) -> 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
|
@AutoServiceConsumed
|
||||||
private DebuggerTraceManagerService traceManager;
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
@ -429,17 +501,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Long> backgroundEmulate(TracePlatform platform, TraceSchedule time) {
|
public CompletableFuture<Long> backgroundEmulate(TracePlatform platform, TraceSchedule time) {
|
||||||
Trace trace = platform.getTrace();
|
requireOpen(platform.getTrace());
|
||||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Cannot emulate a trace unless it's opened in the tool.");
|
|
||||||
}
|
|
||||||
if (time.isSnapOnly()) {
|
if (time.isSnapOnly()) {
|
||||||
return CompletableFuture.completedFuture(time.getSnap());
|
return CompletableFuture.completedFuture(time.getSnap());
|
||||||
}
|
}
|
||||||
return requests.get(new CacheKey(platform, time));
|
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) {
|
protected TraceSnapshot findScratch(Trace trace, TraceSchedule time) {
|
||||||
Collection<? extends TraceSnapshot> exist =
|
Collection<? extends TraceSnapshot> exist =
|
||||||
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
||||||
|
@ -461,13 +539,43 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||||
return snapshot;
|
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;
|
Trace trace = key.trace;
|
||||||
TracePlatform platform = key.platform;
|
TracePlatform platform = key.platform;
|
||||||
TraceSchedule time = key.time;
|
TraceSchedule time = key.time;
|
||||||
|
|
||||||
CachedEmulator ce;
|
|
||||||
DebuggerPcodeMachine<?> emu;
|
|
||||||
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
|
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
|
||||||
if (ancestor != null) {
|
if (ancestor != null) {
|
||||||
CacheKey prevKey = ancestor.getKey();
|
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: Handle errors, and add to proper place in cache?
|
||||||
// TODO: Finish partially-executed instructions?
|
// TODO: Finish partially-executed instructions?
|
||||||
ce = ancestor.getValue();
|
try (BusyEmu be = new BusyEmu(ancestor.getValue())) {
|
||||||
emu = ce.emulator;
|
DebuggerPcodeMachine<?> emu = be.ce.emulator();
|
||||||
|
|
||||||
|
emu.clearAllInjects();
|
||||||
|
emu.clearAccessBreakpoints();
|
||||||
|
emu.setSuspended(false);
|
||||||
|
|
||||||
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
|
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
|
||||||
createRegisterSpaces(trace, time, monitor);
|
createRegisterSpaces(trace, time, monitor);
|
||||||
monitor.setMessage("Emulating");
|
monitor.setMessage("Emulating");
|
||||||
time.finish(trace, prevKey.time, emu, monitor);
|
time.finish(trace, prevKey.time, emu, monitor);
|
||||||
|
return be.dup();
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
emu = emulatorFactory.create(tool, platform, time.getSnap(),
|
DebuggerPcodeMachine<?> emu = emulatorFactory.create(tool, platform, time.getSnap(),
|
||||||
modelService == null ? null : modelService.getRecorder(trace));
|
modelService == null ? null : modelService.getRecorder(trace));
|
||||||
ce = new CachedEmulator(emu);
|
try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu))) {
|
||||||
monitor.initialize(time.totalTickCount());
|
monitor.initialize(time.totalTickCount());
|
||||||
createRegisterSpaces(trace, time, monitor);
|
createRegisterSpaces(trace, time, monitor);
|
||||||
monitor.setMessage("Emulating");
|
monitor.setMessage("Emulating");
|
||||||
time.execute(trace, emu, monitor);
|
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) {
|
synchronized (cache) {
|
||||||
cache.put(key, ce);
|
cache.put(key, ce);
|
||||||
eldest.add(key);
|
eldest.add(key);
|
||||||
|
@ -511,9 +623,36 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||||
cache.remove(expired);
|
cache.remove(expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
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) {
|
protected void createRegisterSpaces(Trace trace, TraceSchedule time, TaskMonitor monitor) {
|
||||||
if (trace.getObjectManager().getRootObject() == null) {
|
if (trace.getObjectManager().getRootObject() == null) {
|
||||||
|
@ -529,25 +668,53 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void requireOpen(Trace trace) {
|
||||||
public long emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor)
|
|
||||||
throws CancelledException {
|
|
||||||
Trace trace = platform.getTrace();
|
|
||||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Cannot emulate a trace unless it's opened in the tool.");
|
"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()) {
|
if (time.isSnapOnly()) {
|
||||||
return time.getSnap();
|
return time.getSnap();
|
||||||
}
|
}
|
||||||
return doEmulate(new CacheKey(platform, time), monitor);
|
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
|
@Override
|
||||||
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
|
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
|
||||||
CachedEmulator ce =
|
CachedEmulator ce =
|
||||||
cache.get(new CacheKey(trace.getPlatformManager().getHostPlatform(), time));
|
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
|
@AutoServiceConsumed
|
||||||
|
|
|
@ -523,7 +523,6 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String traceName = nameTrace(target);
|
String traceName = nameTrace(target);
|
||||||
Trace trace = new DBTrace(traceName, mapper.getTraceCompilerSpec(), this);
|
Trace trace = new DBTrace(traceName, mapper.getTraceCompilerSpec(), this);
|
||||||
//DefaultTraceRecorder recorder = new DefaultTraceRecorder(this, trace, target, mapper);
|
|
||||||
TraceRecorder recorder = mapper.startRecording(this, trace);
|
TraceRecorder recorder = mapper.startRecording(this, trace);
|
||||||
trace.release(this); // The recorder now owns it (on behalf of the service)
|
trace.release(this); // The recorder now owns it (on behalf of the service)
|
||||||
return recorder;
|
return recorder;
|
||||||
|
|
|
@ -47,6 +47,7 @@ import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
import ghidra.trace.model.modules.TraceModule;
|
import ghidra.trace.model.modules.TraceModule;
|
||||||
import ghidra.trace.model.modules.TraceSection;
|
import ghidra.trace.model.modules.TraceSection;
|
||||||
import ghidra.trace.model.stack.TraceStackFrame;
|
import ghidra.trace.model.stack.TraceStackFrame;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -107,6 +108,16 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
||||||
|
|
||||||
/*---------------- OBJECT MANAGER METHODS -------------------*/
|
/*---------------- OBJECT MANAGER METHODS -------------------*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TargetObject getTargetObject(TraceObject obj) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TraceObject getTraceObject(TargetObject obj) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
|
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
|
||||||
return objectManager.getTargetBreakpoint(bpt);
|
return objectManager.getTargetBreakpoint(bpt);
|
||||||
|
|
|
@ -351,6 +351,16 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TargetObject getTargetObject(TraceObject obj) {
|
||||||
|
return objectRecorder.toTarget(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TraceObject getTraceObject(TargetObject obj) {
|
||||||
|
return objectRecorder.toTrace(obj);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
|
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
|
||||||
return objectRecorder.getTargetInterface(bpt, TraceObjectBreakpointLocation.class,
|
return objectRecorder.getTargetInterface(bpt, TraceObjectBreakpointLocation.class,
|
||||||
|
|
|
@ -19,8 +19,7 @@ import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
|
@ -38,6 +37,7 @@ import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.framework.client.ClientUtil;
|
import ghidra.framework.client.ClientUtil;
|
||||||
import ghidra.framework.client.NotConnectedException;
|
import ghidra.framework.client.NotConnectedException;
|
||||||
|
import ghidra.framework.data.DomainObjectAdapterDB;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.DataTreeDialog;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.SaveState;
|
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.
|
// TODO: This is a bit out of this manager's bounds, but acceptable for now.
|
||||||
class ForRecordersListener implements CollectionChangeListener<TraceRecorder> {
|
class ForRecordersListener implements CollectionChangeListener<TraceRecorder> {
|
||||||
@Override
|
@Override
|
||||||
|
@ -138,9 +174,25 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
Swing.runLater(() -> updateCurrentRecorder());
|
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
|
@Override
|
||||||
public void elementRemoved(TraceRecorder recorder) {
|
public void elementRemoved(TraceRecorder recorder) {
|
||||||
Swing.runLater(() -> {
|
boolean save = isSaveTracesByDefault();
|
||||||
|
CompletableFuture<Void> flush = save
|
||||||
|
? waitUnlockedDebounced(recorder)
|
||||||
|
: AsyncUtils.NIL;
|
||||||
|
flush.thenRunAsync(() -> {
|
||||||
updateCurrentRecorder();
|
updateCurrentRecorder();
|
||||||
if (!isAutoCloseOnTerminate()) {
|
if (!isAutoCloseOnTerminate()) {
|
||||||
return;
|
return;
|
||||||
|
@ -151,13 +203,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isSaveTracesByDefault()) {
|
if (save) {
|
||||||
closeTrace(trace);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Errors already handled by saveTrace
|
// Errors already handled by saveTrace
|
||||||
tryHarder(() -> saveTrace(trace), 3, 100).thenRun(() -> closeTrace(trace));
|
saveTrace(trace);
|
||||||
});
|
}
|
||||||
|
closeTrace(trace);
|
||||||
|
}, AsyncUtils.SWING_EXECUTOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,22 +259,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return t;
|
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
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
|
@ -22,6 +22,8 @@ import ghidra.app.plugin.core.debug.service.emulation.*;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
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.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -37,6 +39,71 @@ import ghidra.util.task.TaskMonitor;
|
||||||
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
|
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
|
||||||
public interface DebuggerEmulationService {
|
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
|
* 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.
|
* the tool, but the config options for each factory to the program/trace.
|
||||||
*
|
*
|
||||||
* <p>
|
* <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
|
* 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
|
* (built-in) {@link BytesDebuggerPcodeEmulatorFactory}, and we won't have configuration
|
||||||
* options.
|
* options.
|
||||||
|
@ -97,35 +164,74 @@ public interface DebuggerEmulationService {
|
||||||
* Emulate using the trace's "host" platform
|
* Emulate using the trace's "host" platform
|
||||||
*
|
*
|
||||||
* @see #emulate(TracePlatform, TraceSchedule, TaskMonitor)
|
* @see #emulate(TracePlatform, TraceSchedule, TaskMonitor)
|
||||||
* @param trace
|
* @param trace the trace containing the initial state
|
||||||
* @param time
|
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||||
* @param monitor
|
* @param monitor a monitor for cancellation and progress reporting
|
||||||
* @return
|
* @return the snap in the trace's scratch space where the realize state is stored
|
||||||
* @throws CancelledException
|
* @throws CancelledException if the emulation is cancelled
|
||||||
*/
|
*/
|
||||||
default long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor)
|
default long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
return emulate(trace.getPlatformManager().getHostPlatform(), time, monitor);
|
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
|
* Invoke {@link #emulate(Trace, TraceSchedule, TaskMonitor)} in the background
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This is the preferred means of performing emulation. Because the underlying emulator may
|
* This is the preferred means of performing definite emulation. Because the underlying emulator
|
||||||
* request a <em>blocking</em> read from a target, it is important that
|
* 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
|
* {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor) emulate} is <em>never</em> called
|
||||||
* Swing thread.
|
* by the Swing thread.
|
||||||
*
|
*
|
||||||
* @param platform the trace platform containing the initial state
|
* @param platform the trace platform containing the initial state
|
||||||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||||
* @return a future which completes with the result of
|
* @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);
|
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>
|
* <p>
|
||||||
* To guarantee the emulator is present, call {@link #backgroundEmulate(Trace, TraceSchedule)}
|
* To guarantee the emulator is present, call {@link #backgroundEmulate(Trace, TraceSchedule)}
|
||||||
|
@ -142,4 +248,25 @@ public interface DebuggerEmulationService {
|
||||||
* @return the copied p-code frame
|
* @return the copied p-code frame
|
||||||
*/
|
*/
|
||||||
DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time);
|
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.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
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.Trace;
|
||||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
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.TraceModule;
|
||||||
import ghidra.trace.model.modules.TraceSection;
|
import ghidra.trace.model.modules.TraceSection;
|
||||||
import ghidra.trace.model.stack.TraceStackFrame;
|
import ghidra.trace.model.stack.TraceStackFrame;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.model.time.TraceTimeManager;
|
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
|
* 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
|
* 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
|
* 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>
|
* <p>
|
||||||
* The recorder copies information in one direction; thus, if a trace UI component needs to affect
|
* 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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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();
|
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);
|
DebuggerRegisterMapper getRegisterMapper(TraceThread thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the memory mapper for the target
|
||||||
|
*
|
||||||
|
* @return the mapper, or null
|
||||||
|
*/
|
||||||
DebuggerMemoryMapper getMemoryMapper();
|
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);
|
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);
|
boolean isRegisterBankAccessible(TraceThread thread, int frameLevel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<PACKAGE NAME="Ghidra Core">
|
<PACKAGE NAME="Ghidra Core">
|
||||||
<INCLUDE CLASS="ghidra.app.plugin.core.editor.TextEditorManagerPlugin" />
|
<INCLUDE CLASS="ghidra.app.plugin.core.editor.TextEditorManagerPlugin" />
|
||||||
<INCLUDE CLASS="ghidra.app.plugin.core.interpreter.InterpreterPanelPlugin" />
|
<INCLUDE CLASS="ghidra.app.plugin.core.interpreter.InterpreterPanelPlugin" />
|
||||||
|
<EXCLUDE CLASS="ghidra.plugins.fsbrowser.FileSystemBrowserPlugin" />
|
||||||
</PACKAGE>
|
</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">
|
<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">
|
<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.math.BigInteger;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -455,7 +454,9 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
protected static void performEnabledAction(ActionContextProvider provider,
|
protected static void performEnabledAction(ActionContextProvider provider,
|
||||||
DockingActionIf action, boolean wait) {
|
DockingActionIf action, boolean wait) {
|
||||||
ActionContext context = waitForValue(() -> {
|
ActionContext context = waitForValue(() -> {
|
||||||
ActionContext ctx = provider.getActionContext(null);
|
ActionContext ctx = provider == null
|
||||||
|
? new ActionContext()
|
||||||
|
: provider.getActionContext(null);
|
||||||
if (!action.isEnabledForContext(ctx)) {
|
if (!action.isEnabledForContext(ctx)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -529,8 +530,18 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
if (recorder == null) {
|
if (recorder == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
waitOn(recorder.getTarget().getModel().flushEvents());
|
waitOn(recorder.getTarget().getModel().flushEvents());
|
||||||
|
}
|
||||||
|
catch (RejectedExecutionException e) {
|
||||||
|
// Whatever
|
||||||
|
}
|
||||||
|
try {
|
||||||
waitOn(recorder.flushTransactions());
|
waitOn(recorder.flushTransactions());
|
||||||
|
}
|
||||||
|
catch (RejectedExecutionException e) {
|
||||||
|
// Whatever
|
||||||
|
}
|
||||||
waitForDomainObject(recorder.getTrace());
|
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.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import org.junit.Before;
|
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.DebuggerPlatformMapper;
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion;
|
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion;
|
||||||
import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin;
|
import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin;
|
||||||
|
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
import ghidra.app.services.DebuggerStaticMappingService;
|
||||||
|
import ghidra.pcode.exec.InterruptPcodeExecutionException;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.program.model.lang.*;
|
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.Memory;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.DefaultTraceLocation;
|
import ghidra.trace.model.DefaultTraceLocation;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.memory.TraceMemoryManager;
|
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.time.schedule.Scheduler;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -335,4 +342,130 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
|
||||||
.getUnsignedValue()
|
.getUnsignedValue()
|
||||||
.toString(16));
|
.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
|
* This may apply, e.g., to a GDB "Inferior," which has no yet been used to launch or attach
|
||||||
* to a process.
|
* to a process.
|
||||||
*/
|
*/
|
||||||
INACTIVE {
|
INACTIVE(false, false, false),
|
||||||
@Override
|
|
||||||
public boolean isAlive() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRunning() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStopped() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object is alive, but its execution state is unspecified
|
* 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
|
* when <em>all</em> of its threads are stopped. For the clients' sakes, all models should
|
||||||
* implement these conventions internally.
|
* implement these conventions internally.
|
||||||
*/
|
*/
|
||||||
ALIVE {
|
ALIVE(true, false, false),
|
||||||
@Override
|
|
||||||
public boolean isAlive() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRunning() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStopped() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object is alive, but not executing
|
* The object is alive, but not executing
|
||||||
*/
|
*/
|
||||||
STOPPED {
|
STOPPED(true, false, true),
|
||||||
@Override
|
|
||||||
public boolean isAlive() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRunning() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStopped() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object is alive and executing
|
* 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
|
* 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.
|
* not necessarily mean it is executing on a CPU at this exact moment.
|
||||||
*/
|
*/
|
||||||
RUNNING {
|
RUNNING(true, true, false),
|
||||||
@Override
|
|
||||||
public boolean isAlive() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRunning() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStopped() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object is no longer alive
|
* 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
|
* 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.
|
* e.g., a GDB "Inferior," which could be re-used to launch or attach to another process.
|
||||||
*/
|
*/
|
||||||
TERMINATED {
|
TERMINATED(false, false, false);
|
||||||
@Override
|
|
||||||
public boolean isAlive() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private final boolean alive;
|
||||||
public boolean isRunning() {
|
private final boolean running;
|
||||||
return false;
|
private final boolean stopped;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private TargetExecutionState(boolean alive, boolean running, boolean stopped) {
|
||||||
public boolean isStopped() {
|
this.alive = alive;
|
||||||
return false;
|
this.running = running;
|
||||||
|
this.stopped = stopped;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this state implies the object is alive
|
* Check if this state implies the object is alive
|
||||||
*
|
*
|
||||||
* @return true if alive
|
* @return true if alive
|
||||||
*/
|
*/
|
||||||
public abstract boolean isAlive();
|
public boolean isAlive() {
|
||||||
|
return alive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this state implies the object is running
|
* Check if this state implies the object is running
|
||||||
*
|
*
|
||||||
* @return true if running
|
* @return true if running
|
||||||
*/
|
*/
|
||||||
public abstract boolean isRunning();
|
public boolean isRunning() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this state implies the object is stopped
|
* Check if this state implies the object is stopped
|
||||||
*
|
*
|
||||||
* @return true if 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);
|
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
|
* 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.Range;
|
||||||
import com.google.common.collect.RangeSet;
|
import com.google.common.collect.RangeSet;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetMethod;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.PathPattern;
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathPredicates;
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
@ -570,4 +570,29 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
}
|
}
|
||||||
return null;
|
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
|
@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 {
|
throws CancelledException {
|
||||||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", sleigh + ";");
|
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", sleigh + ";");
|
||||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
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
|
* @return the last trace thread stepped during execution
|
||||||
* @throws CancelledException if execution is cancelled
|
* @throws CancelledException if execution is cancelled
|
||||||
*/
|
*/
|
||||||
public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine,
|
public TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<?> machine,
|
||||||
Stepper<T> stepper, TaskMonitor monitor) throws CancelledException {
|
Stepper stepper, TaskMonitor monitor) throws CancelledException {
|
||||||
TraceThreadManager tm = trace.getThreadManager();
|
TraceThreadManager tm = trace.getThreadManager();
|
||||||
TraceThread thread = eventThread;
|
TraceThread thread = eventThread;
|
||||||
for (Step step : steps) {
|
for (Step step : steps) {
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class SkipStep extends AbstractStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 {
|
throws CancelledException {
|
||||||
for (int i = 0; i < tickCount; i++) {
|
for (int i = 0; i < tickCount; i++) {
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
|
|
|
@ -170,20 +170,20 @@ public interface Step extends Comparable<Step> {
|
||||||
return compareStep(that).compareTo;
|
return compareStep(that).compareTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
|
default TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
|
||||||
PcodeMachine<T> machine, Stepper<T> stepper, TaskMonitor monitor)
|
PcodeMachine<?> machine, Stepper stepper, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
TraceThread thread = getThread(tm, eventThread);
|
TraceThread thread = getThread(tm, eventThread);
|
||||||
if (machine == null) {
|
if (machine == null) {
|
||||||
// Just performing validation (specifically thread parts)
|
// Just performing validation (specifically thread parts)
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
PcodeThread<?> emuThread = machine.getThread(thread.getPath(), true);
|
||||||
execute(emuThread, stepper, monitor);
|
execute(emuThread, stepper, monitor);
|
||||||
return thread;
|
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;
|
throws CancelledException;
|
||||||
|
|
||||||
long coalescePatches(Language language, List<Step> steps);
|
long coalescePatches(Language language, List<Step> steps);
|
||||||
|
|
|
@ -17,44 +17,41 @@ package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
|
||||||
public interface Stepper<T> {
|
public interface Stepper {
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
enum Enum implements Stepper {
|
enum Enum implements Stepper {
|
||||||
INSTRUCTION {
|
INSTRUCTION {
|
||||||
@Override
|
@Override
|
||||||
public void tick(PcodeThread thread) {
|
public void tick(PcodeThread<?> thread) {
|
||||||
thread.stepInstruction();
|
thread.stepInstruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void skip(PcodeThread thread) {
|
public void skip(PcodeThread<?> thread) {
|
||||||
thread.skipInstruction();
|
thread.skipInstruction();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PCODE {
|
PCODE {
|
||||||
@Override
|
@Override
|
||||||
public void tick(PcodeThread thread) {
|
public void tick(PcodeThread<?> thread) {
|
||||||
thread.stepPcodeOp();
|
thread.stepPcodeOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void skip(PcodeThread thread) {
|
public void skip(PcodeThread<?> thread) {
|
||||||
thread.skipPcodeOp();
|
thread.skipPcodeOp();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
static Stepper instruction() {
|
||||||
static <T> Stepper<T> instruction() {
|
|
||||||
return Enum.INSTRUCTION;
|
return Enum.INSTRUCTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
static Stepper pcode() {
|
||||||
static <T> Stepper<T> pcode() {
|
|
||||||
return Enum.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
|
@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 {
|
throws CancelledException {
|
||||||
for (int i = 0; i < tickCount; i++) {
|
for (int i = 0; i < tickCount; i++) {
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
|
|
|
@ -561,6 +561,32 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
return new TraceSchedule(snap, ticks, new Sequence());
|
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
|
* Get the threads involved in the schedule
|
||||||
*
|
*
|
||||||
|
|
|
@ -20,7 +20,7 @@ import java.util.*;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
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.program.model.lang.Language;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
|
|
||||||
|
@ -70,7 +70,10 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
|
||||||
protected final Collection<PcodeThread<T>> threadsView =
|
protected final Collection<PcodeThread<T>> threadsView =
|
||||||
Collections.unmodifiableCollection(threads.values());
|
Collections.unmodifiableCollection(threads.values());
|
||||||
|
|
||||||
|
protected volatile boolean suspended = false;
|
||||||
protected final Map<Address, PcodeProgram> injects = new HashMap<>();
|
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
|
* 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;
|
return sharedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSuspended(boolean suspended) {
|
||||||
|
this.suspended = suspended;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for a p-code injection (override) at the given address
|
* Check for a p-code injection (override) at the given address
|
||||||
*
|
*
|
||||||
|
@ -300,4 +308,44 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
|
||||||
""", sleighCondition));
|
""", sleighCondition));
|
||||||
injects.put(address, pcode);
|
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.PcodeArithmetic.Purpose;
|
||||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.program.model.listing.Instruction;
|
import ghidra.program.model.listing.Instruction;
|
||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
|
@ -157,12 +158,22 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
|
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
|
||||||
if (suspended) {
|
if (suspended || thread.machine.suspended) {
|
||||||
throw new SuspendedPcodeExecutionException(frame, null);
|
throw new SuspendedPcodeExecutionException(frame, null);
|
||||||
}
|
}
|
||||||
super.stepOp(op, frame, library);
|
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
|
@Override
|
||||||
protected void branchToAddress(Address target) {
|
protected void branchToAddress(Address target) {
|
||||||
thread.overrideCounter(target);
|
thread.overrideCounter(target);
|
||||||
|
@ -606,4 +617,12 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
||||||
public void clearAllInjects() {
|
public void clearAllInjects() {
|
||||||
injects.clear();
|
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.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
|
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
|
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||||
import ghidra.program.model.address.Address;
|
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
|
* 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> {
|
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)
|
* Get the machine's Sleigh language (processor model)
|
||||||
*
|
*
|
||||||
|
@ -112,6 +155,13 @@ public interface PcodeMachine<T> {
|
||||||
*/
|
*/
|
||||||
PcodeExecutorState<T> getSharedState();
|
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
|
* Compile the given Sleigh code for execution by a thread of this machine
|
||||||
*
|
*
|
||||||
|
@ -130,8 +180,8 @@ public interface PcodeMachine<T> {
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This will attempt to compile the given source against this machine's userop library and then
|
* 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
|
* inject it at the given address. The resulting p-code <em>replaces</em> that which would be
|
||||||
* be executed by decoding the instruction at the given address. The means the machine will not
|
* 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
|
* 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
|
* call {@link PcodeEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and
|
||||||
* execute the overridden instruction.
|
* 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
|
* replaced and the old inject completely forgotten. The injector does not support chaining or
|
||||||
* double-wrapping, etc.
|
* 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 address the address to inject at
|
||||||
* @param source the Sleigh source to compile and inject
|
* @param source the Sleigh source to compile and inject
|
||||||
*/
|
*/
|
||||||
|
@ -155,19 +210,65 @@ public interface PcodeMachine<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all injects from this machine
|
* Remove all injects from this machine
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This will clear execution breakpoints, but not access breakpoints. See
|
||||||
|
* {@link #clearAccessBreakpoints()}.
|
||||||
*/
|
*/
|
||||||
void clearAllInjects();
|
void clearAllInjects();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a (conditional) breakpoint at the given address
|
* Add a conditional execution breakpoint at the given address
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Breakpoints are implemented at the p-code level using an inject, without modification to the
|
* 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
|
* 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.
|
* 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 address the address at which to break
|
||||||
* @param sleighCondition a Sleigh expression which controls the breakpoint
|
* @param sleighCondition a Sleigh expression which controls the breakpoint
|
||||||
*/
|
*/
|
||||||
void addBreakpoint(Address address, String sleighCondition);
|
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);
|
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
|
* Execute a load
|
||||||
*
|
*
|
||||||
|
@ -339,6 +348,7 @@ public class PcodeExecutor<T> {
|
||||||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
|
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
|
||||||
Varnode inOffset = op.getInput(1);
|
Varnode inOffset = op.getInput(1);
|
||||||
T offset = state.getVar(inOffset, reason);
|
T offset = state.getVar(inOffset, reason);
|
||||||
|
checkLoad(space, offset);
|
||||||
Varnode outvar = op.getOutput();
|
Varnode outvar = op.getOutput();
|
||||||
|
|
||||||
T out = state.getVar(space, offset, outvar.getSize(), true, reason);
|
T out = state.getVar(space, offset, outvar.getSize(), true, reason);
|
||||||
|
@ -347,6 +357,15 @@ public class PcodeExecutor<T> {
|
||||||
state.setVar(outvar, mod);
|
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
|
* Execute a store
|
||||||
*
|
*
|
||||||
|
@ -357,6 +376,7 @@ public class PcodeExecutor<T> {
|
||||||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
|
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
|
||||||
Varnode inOffset = op.getInput(1);
|
Varnode inOffset = op.getInput(1);
|
||||||
T offset = state.getVar(inOffset, reason);
|
T offset = state.getVar(inOffset, reason);
|
||||||
|
checkStore(space, offset);
|
||||||
Varnode valVar = op.getInput(2);
|
Varnode valVar = op.getInput(2);
|
||||||
|
|
||||||
T val = state.getVar(valVar, reason);
|
T val = state.getVar(valVar, reason);
|
||||||
|
|
|
@ -130,6 +130,7 @@ public class PcodeFrame {
|
||||||
private final List<PcodeOp> code;
|
private final List<PcodeOp> code;
|
||||||
private final Map<Integer, String> useropNames;
|
private final Map<Integer, String> useropNames;
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
private int index = 0;
|
private int index = 0;
|
||||||
private int branched = -1;
|
private int branched = -1;
|
||||||
|
|
||||||
|
@ -157,6 +158,19 @@ public class PcodeFrame {
|
||||||
return new MyFormatter().formatOps(language, code);
|
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
|
* 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
|
* @return the value of the index <em>before</em> it was advanced
|
||||||
*/
|
*/
|
||||||
public int advance() {
|
public int advance() {
|
||||||
|
count++;
|
||||||
return index++;
|
return index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +214,7 @@ public class PcodeFrame {
|
||||||
* @return the value of the index <em>before</em> it was stepped back
|
* @return the value of the index <em>before</em> it was stepped back
|
||||||
*/
|
*/
|
||||||
public int stepBack() {
|
public int stepBack() {
|
||||||
|
count--;
|
||||||
return index--;
|
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));
|
||||||
|
}
|
||||||
|
}
|