Merge remote-tracking branch 'origin/Ghidra_10.1'

Conflicts:
	Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png
	Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java
This commit is contained in:
Ryan Kurtz
2021-11-26 11:17:19 -05:00
102 changed files with 3613 additions and 2091 deletions
@@ -22,10 +22,10 @@
</TBODY>
</TABLE>
<P>P-code is the "microcode" of Ghidra's processor specifications, compiled from its SLEIGH
<P>P-code is the "microcode" of Ghidra's processor specifications, compiled from its Sleigh
specification. Originally designed to facilitate static analysis, it is easily applied to
emulation as well. Stepping each p-code operation is an effective means of debugging the
SLEIGH. The plugin provides two panes: 1) The p-code listing, and 2) Temporary ("Unique")
Sleigh. The plugin provides two panes: 1) The p-code listing, and 2) Temporary ("Unique")
variables. The listing works similarly to the dynamic listing. It displays each p-code
operation, highlighting the current "counter", which is the next operation to be executed.
There is also a cursor, allowing selection of an operation. The variables view operates
@@ -62,12 +62,12 @@
interact with the target, stepping at the p-code level implies you are no longer "at the
present."</P>
<H3><A name="step_trace_pcode_backward"></A>Step Trace p-code Backward</H3>
<H3><A name="emu_trace_pcode_backward"></A>Emulate Trace p-code Backward</H3>
<P>This action is available when the current coordinates have some positive number of p-code
ticks. It steps the trace backward to the previous p-code tick.</P>
<H3><A name="step_trace_pcode_forward"></A>Step Trace p-code Forward</H3>
<H3><A name="emu_trace_pcode_forward"></A>Emulate Trace p-code Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward to
the next p-code tick, using emulation. Note that emulation does not affect the target.
@@ -100,5 +100,14 @@
<P>This action is available when the dynamic listing's cursor is at a valid location. It
selects the region containing that cursor. If the dynamic listing has a selection, it selects
all regions intersecting that selection.</P>
<H3><A name="force_full_view"></A>Force Full View</H3>
<P>This action is available when a trace is active. It forces all physical address spaces into
the view. Ordinarily, only those addresses contained in a region at the active snap are
presented in the listing and memory windows. When this toggle is on, regions are ignored.
Instead, all physical addresses are presented. (Here "physical" includes all memory spaces
except <CODE>OTHER</CODE>.) This toggle applies only to the current trace for the duration it
is open.</P>
</BODY>
</HTML>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

@@ -45,9 +45,10 @@
<LI>Value - the value of the register as recorded in the trace. When the value refers to a
valid memory offset, right-clicking the row allows the user to navigate to that offset in a
selected memory space. This field is user modifiable when the target is alive, the trace is
"at the present," and the <B>Enable Edits</B> toggle is on. Changes to the register's value
are sent to the target. Values changed by the last event are displayed in <FONT color=
selected memory space. This field is user modifiable when the <B>Enable Edits</B> toggle is
on, and the register is not part of <CODE>contextreg</CODE>. Changes are sent to the target
if the trace is live and "at the present." Otherwise, the change is materialized via
emulation. Values changed by the last event are displayed in <FONT color=
"red">red</FONT>.</LI>
<LI>Type - the type of the register as marked up in the trace. There is generally no default
@@ -91,10 +92,15 @@
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for live registers. To modify live register values, this
toggle must be enabled, and the trace must be live and "at the present." Note that editing
recorded historical values is not permitted, regardless of this toggle, but can be accomplished
via watches or scripts.</P>
<P>This toggle is a write protector for target machine state. To modify register values, this
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
the value to be modified on the target. Editing emulated values is permitted, but ity has no
effect on the target. Editing historical values is not permitted. All edits to non-live trace
values are performed in emulation. Specifically, it appends a patch command to the current
emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
be annotated and recalled later, since they are stored in the trace's scratch space. Note that
only the raw "Value" column can be edited directory. The "Repr" column cannot be edited,
yet.</P>
<H3><A name="snapshot_window"></A>Snapshot Window</H3>
@@ -109,14 +109,14 @@
trace forward to the next snapshot, causing most windows to display the recorded data from the
new point in time.</P>
<H3><A name="step_trace_tick_backward"></A><IMG alt="" src="images/stepback.png">Step Trace
<H3><A name="emu_trace_tick_backward"></A><IMG alt="" src="images/stepback.png">Emulate Trace
Tick Backward</H3>
<P>This action is available when the current point in time includes emulated steps. It steps
the trace backward to the previous tick.</P>
<H3><A name="step_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Step Trace Tick
Forward</H3>
<H3><A name="emu_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Emulate Trace
Tick Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward to
the next tick, using emulation. Note that emulation does not affect the target. Furthermore,
@@ -132,6 +132,37 @@
not cause any windows to update. Toggling it off then on is a quick way to return to the
present after browsing the past.</P>
<H3><A name="goto_time"></A>Go To Time</H3>
<P>This action is available when a trace is active. It prompts for a <B>Time Schedule</B>
expression. This is the same form as the expression in the title bar of the threads window. In
many cases, it is simply the snapshot number, e.g., <CODE>3</CODE>, which will go to the
snapshot with key 3. It may optionally include an emulation schedule, for example,
<CODE>3:10</CODE> will use snapshot 3 for an emulator's initial state and step 10 machine
instructions on snapshot 3's event thread. If the snapshot does not give an event thread, then
the thread must be specified in the expression, e.g., <CODE>3:t1-10</CODE>. That expression
will start at snapshot 3, get the thread with key 1, and step it 10 machine instructions. The
stepping commands can be repeated any number of times, separated by semicolons, to step threads
in a specified sequence, e.g., <CODE>3:t1-10;t2-5</CODE> will do the same as before, then get
thread 2 and step it 5 times.</P>
<P>The emulator's state can also be modified by the schedule. Instead of specifying a number of
steps, write a <B>Sleigh</B> statement, e.g., <CODE>3:t1-{r0=0x1234};10</CODE>. This will start
at snapshot 3, patch thread 1's r0 to 0x1234, then step 10 instructions. Like stepping
commands, the thread may be omitted for Sleigh commands. Each command without a thread
specified implicitly uses the one from the previous command, or in the case of the first
command, the event thread. Only one Sleigh statement is permitted per command.</P>
<P>A second command sequence may be appended, following a dot, to command the emulator at the
level of p-code operations as well. This is particularly useful when debugging a processor
specification. See also the <A href=
"help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html">P-code Stepper</A>
window. For example, <CODE>3:2.10</CODE> will start at snapshot 3 and step the event thread 2
machine instructions, followed by 10 p-code operations. The same thread-by-thread sequencing
and state patching commands are allowed in the p-code command sequence. The <EM>entire</EM>
instruction sequence precedes the entire p-code sequence, i.e., only a single dot is allowed.
Once the expression enters p-code mode, it cannot re-enter instruction mode.</P>
<H2>Other Actions</H2>
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

@@ -62,9 +62,9 @@
<LI>Value - the raw bytes of the watched buffer. If the expression is a register, then this
is its hexadecimal value. This field is user modifiable when the <B>Enable Edits</B> toggle
is on. Changes are sent to the target if the trace is live and "at the present." If the value
has changed since the last navigation event, this cell is rendered in <FONT color=
"red">red</FONT>.</LI>
is on. Changes are sent to the target if the trace is live and "at the present." Otherwise,
the change is materialized via emulation. If the value has changed since the last navigation
event, this cell is rendered in <FONT color="red">red</FONT>.</LI>
<LI>Type - the user-modifiable type of the watch. Note the type is not marked up in the
trace. Clicking the Apply Data Type action will apply it to the current trace, if
@@ -79,7 +79,7 @@
not necessarily cause alarm. An expression devised for one context may not have meaning under
another, even if it evaluates without error. E.g., <CODE>RIP</CODE> will disappear when
switching to a 32-bit trace, or <CODE>*:8 (*:8 (RSP+8))</CODE> may cause an invalid
dereference if an x86 <CODE>PUSH</CODE> causes <CODE>*:8 (RSP+8)</CODE> to become 0.</LI>
dereference when the stack pointer moves.</LI>
</UL>
<H2>Actions</H2>
@@ -123,11 +123,15 @@
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for recorded and/or live values. To modify a watch's value,
this toggle must be enabled. Editing a value when the trace is live and "at the present" will
cause the value to be modified on the target. Editing historical and/or emulated values is
permitted, but it has no effect on the target. Note that only the raw "Value" column can be
edited directly. The "Repr" column cannot be edited, yet.</P>
<P>This toggle is a write protector for target machine state. To modify a watch's value, this
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
the value to be modified on the target. Editing emulated values is permitted, but it has no
effect on the target. Editing historical values is not permitted. All edits to non-live trace
values are performed in emulation. Specifically, it appends a patch command to the current
emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
be annotated and recalled later, since they are stored in the trace's scratch space. Note that
only the raw "Value" column can be edited directly. The "Repr" column cannot be edited,
yet.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>
@@ -31,8 +31,8 @@ import ghidra.trace.database.DBTraceContentHandler;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.DefaultTraceTimeViewport;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.Msg;
@@ -259,8 +259,10 @@ public class DebuggerCoordinates {
Collection<? extends TraceSnapshot> snapshots =
trace.getTimeManager().getSnapshotsWithSchedule(time);
if (snapshots.isEmpty()) {
Msg.warn(this, "Seems the emulation service did not create the requested snapshot");
return viewSnap = time.getSnap();
Msg.warn(this,
"Seems the emulation service did not create the requested snapshot, yet");
// NB. Don't cache viewSnap. Maybe next time, we'll get it.
return time.getSnap();
}
return viewSnap = snapshots.iterator().next().getKey();
}
@@ -1520,24 +1520,24 @@ public interface DebuggerResources {
}
}
abstract class AbstractStepTickForwardAction extends DockingAction {
public static final String NAME = "Step Trace Tick Forward";
abstract class AbstractEmulateTickForwardAction extends DockingAction {
public static final String NAME = "Emulate Trace Tick Forward";
public static final Icon ICON = ICON_STEP_INTO;
public static final String HELP_ANCHOR = "step_trace_tick_forward";
public static final String HELP_ANCHOR = "emu_trace_tick_forward";
public AbstractStepTickForwardAction(Plugin owner) {
public AbstractEmulateTickForwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Navigate the recording forward one tick");
setDescription("Emulate the recording forward one tick");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
interface StepPcodeForwardAction {
String NAME = "Step Trace p-code Forward";
interface EmulatePcodeForwardAction {
String NAME = "Emulate Trace p-code Forward";
String DESCRIPTION = "Navigate the recording forward one p-code tick";
Icon ICON = ICON_STEP_INTO;
String GROUP = GROUP_CONTROL;
String HELP_ANCHOR = "step_trace_pcode_forward";
String HELP_ANCHOR = "emu_trace_pcode_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
@@ -1549,14 +1549,14 @@ public interface DebuggerResources {
}
}
abstract class AbstractStepTickBackwardAction extends DockingAction {
public static final String NAME = "Step Trace Tick Backward";
abstract class AbstractEmulateTickBackwardAction extends DockingAction {
public static final String NAME = "Emulate Trace Tick Backward";
public static final Icon ICON = ICON_STEP_BACK;
public static final String HELP_ANCHOR = "step_trace_tick_backward";
public static final String HELP_ANCHOR = "emu_trace_tick_backward";
public AbstractStepTickBackwardAction(Plugin owner) {
public AbstractEmulateTickBackwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Navigate the recording backward one tick");
setDescription("Emulate the recording backward one tick");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
@@ -1573,12 +1573,12 @@ public interface DebuggerResources {
}
}
interface StepPcodeBackwardAction {
String NAME = "Step Trace p-code Backward";
interface EmulatePcodeBackwardAction {
String NAME = "Emulate Trace p-code Backward";
String DESCRIPTION = "Navigate the recording backward one p-code tick";
Icon ICON = ICON_STEP_BACK;
String GROUP = GROUP_CONTROL;
String HELP_ANCHOR = "step_trace_pcode_backward";
String HELP_ANCHOR = "emu_trace_pcode_backward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
@@ -1605,15 +1605,30 @@ public interface DebuggerResources {
interface SynchronizeFocusAction {
String NAME = "Synchronize Focus";
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
String GROUP = "zz";
Icon ICON = ICON_SYNC;
String HELP_ANCHOR = "sync_focus";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.menuPath(NAME)
.menuIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface GoToTimeAction {
String NAME = "Go To Time";
String DESCRIPTION = "Go to a specific time, optionally using emulation";
Icon ICON = ICON_TIME;
String HELP_ANCHOR = "goto_time";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
.menuPath(NAME)
.menuIcon(ICON)
.keyBinding("CTRL SHIFT T")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
@@ -1808,6 +1823,22 @@ public interface DebuggerResources {
}
}
interface ForceFullViewAction {
String NAME = "Force Full View";
String DESCRIPTION = "Ignore regions and fiew full address spaces";
String GROUP = GROUP_GENERAL;
String HELP_ANCHOR = "force_full_view";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuGroup(GROUP_GENERAL)
.menuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
@Override
public String getName() {
@@ -46,8 +46,14 @@ public class DebuggerMemoryByteViewerComponent extends ByteViewerComponent
List<ColoredFieldSelection> selections) {
Color selectionColor = paintContext.getSelectionColor();
Color highlightColor = paintContext.getHighlightColor();
selections.add(new ColoredFieldSelection(getSelection(), selectionColor));
selections.add(new ColoredFieldSelection(getHighlight(), highlightColor));
FieldSelection selection = getSelection();
if (!selection.isEmpty()) {
selections.add(new ColoredFieldSelection(selection, selectionColor));
}
FieldSelection highlight = getHighlight();
if (!highlight.isEmpty()) {
selections.add(new ColoredFieldSelection(highlight, highlightColor));
}
}
}
@@ -253,6 +253,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
SelectAddressesAction actionSelectAddresses;
DockingAction actionSelectRows;
ToggleDockingAction actionForceFullView;
public DebuggerRegionsProvider(DebuggerRegionsPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGIONS, plugin.getName(),
@@ -382,7 +383,10 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
.enabledWhen(ctx -> currentTrace != null)
.onAction(this::activatedSelectCurrent)
.buildAndInstallLocal(this);
actionForceFullView = ForceFullViewAction.builder(plugin)
.enabledWhen(ctx -> currentTrace != null)
.onAction(this::activatedForceFullView)
.buildAndInstallLocal(this);
contextChanged();
}
@@ -501,6 +505,15 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
}
private void activatedForceFullView(ActionContext ignored) {
if (currentTrace == null) {
return;
}
currentTrace.getProgramView()
.getMemory()
.setForceFullView(actionForceFullView.isSelected());
}
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable,
regionTableModel, regionFilterPanel);
@@ -554,6 +567,16 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
contextChanged();
}
@Override
public void contextChanged() {
super.contextChanged();
if (currentTrace != null) {
actionForceFullView.setSelected(currentTrace.getProgramView()
.getMemory()
.isForceFullView());
}
}
private void removeOldListeners() {
if (currentTrace == null) {
return;
@@ -55,7 +55,7 @@ import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.ColorUtils;
import ghidra.util.HTMLUtilities;
import ghidra.util.database.UndoableTransaction;
@@ -510,11 +510,11 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
}
protected void createActions() {
actionStepBackward = DebuggerResources.StepPcodeBackwardAction.builder(plugin)
actionStepBackward = DebuggerResources.EmulatePcodeBackwardAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null && current.getTime().pTickCount() != 0)
.onAction(c -> stepBackwardActivated())
.buildAndInstallLocal(this);
actionStepForward = DebuggerResources.StepPcodeForwardAction.builder(plugin)
actionStepForward = DebuggerResources.EmulatePcodeForwardAction.builder(plugin)
.enabledWhen(
c -> current.getThread() != null)
.onAction(c -> stepForwardActivated())
@@ -72,6 +72,7 @@ import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
@@ -755,8 +756,21 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return true;
}
boolean canWriteRegister(Register register) {
if (!isEditsEnabled()) {
return false;
}
if (register.isProcessorContext()) {
return false; // TODO: Limitation from using Sleigh for patching
}
return true;
}
boolean canWriteTargetRegister(Register register) {
if (!computeEditsEnabled()) {
if (!isEditsEnabled()) {
return false;
}
if (!canWriteTarget()) {
return false;
}
return current.getRecorder().isRegisterOnTarget(current.getThread(), register);
@@ -775,23 +789,32 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
void writeRegisterValue(RegisterValue rv) {
rv = combineWithTraceBaseRegisterValue(rv);
CompletableFuture<Void> future = current.getRecorder()
.writeThreadRegisters(current.getThread(), current.getFrame(),
Map.of(rv.getRegister(), rv));
future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) {
Msg.error(this, "Could not write target register", ex);
plugin.getTool()
.setStatusInfo("Could not write target register: " + ex.getMessage());
}
else {
Msg.showError(this, getComponent(), "Edit Register",
"Could not write target register", ex);
}
return null;
});
if (canWriteTargetRegister(rv.getRegister())) {
rv = combineWithTraceBaseRegisterValue(rv);
CompletableFuture<Void> future = current.getRecorder()
.writeThreadRegisters(current.getThread(), current.getFrame(),
Map.of(rv.getRegister(), rv));
future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) {
Msg.error(this, "Could not write target register", ex);
plugin.getTool()
.setStatusInfo("Could not write target register: " + ex.getMessage());
}
else {
Msg.showError(this, getComponent(), "Edit Register",
"Could not write target register", ex);
}
return null;
});
return;
}
TraceSchedule time = current.getTime().patched(current.getThread(), generateSleigh(rv));
traceManager.activateTime(time);
}
protected String generateSleigh(RegisterValue rv) {
return String.format("%s=0x%s", rv.getRegister(), rv.getUnsignedValue().toString(16));
}
private RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv) {
@@ -886,11 +909,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return !Objects.equals(curRegVal, prevRegVal);
}
private boolean computeEditsEnabled() {
if (!actionEnableEdits.isSelected()) {
return false;
}
return canWriteTarget();
private boolean isEditsEnabled() {
return actionEnableEdits.isSelected();
}
/**
@@ -62,7 +62,7 @@ public class RegisterRow {
}
public boolean isValueEditable() {
return provider.canWriteTargetRegister(register);
return provider.canWriteRegister(register);
}
public void setValue(BigInteger value) {
@@ -33,6 +33,7 @@ import docking.WindowPosition;
import docking.action.*;
import docking.widgets.HorizontalTabPanel;
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.table.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
@@ -53,8 +54,9 @@ import ghidra.trace.model.Trace.TraceThreadChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.database.ObjectKey;
import ghidra.util.datastruct.CollectionChangeListener;
@@ -120,10 +122,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
}
protected class StepTickBackwardAction extends AbstractStepTickBackwardAction {
protected class EmulateTickBackwardAction extends AbstractEmulateTickBackwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public StepTickBackwardAction() {
public EmulateTickBackwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "2"));
addLocalAction(this);
@@ -157,10 +159,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
}
protected class StepTickForwardAction extends AbstractStepTickForwardAction {
protected class EmulateTickForwardAction extends AbstractEmulateTickForwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public StepTickForwardAction() {
public EmulateTickForwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "3"));
addLocalAction(this);
@@ -353,11 +355,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
DockingAction actionSaveTrace;
StepSnapBackwardAction actionStepSnapBackward;
StepTickBackwardAction actionStepTickBackward;
StepTickForwardAction actionStepTickForward;
EmulateTickBackwardAction actionEmulateTickBackward;
EmulateTickForwardAction actionEmulateTickForward;
StepSnapForwardAction actionStepSnapForward;
SeekTracePresentAction actionSeekTracePresent;
ToggleDockingAction actionSyncFocus;
DockingAction actionGoToTime;
Set<Object> strongRefs = new HashSet<>(); // Eww
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
@@ -669,8 +672,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
protected void createActions() {
// TODO: Make other actions use builder?
actionStepSnapBackward = new StepSnapBackwardAction();
actionStepTickBackward = new StepTickBackwardAction();
actionStepTickForward = new StepTickForwardAction();
actionEmulateTickBackward = new EmulateTickBackwardAction();
actionEmulateTickForward = new EmulateTickForwardAction();
actionStepSnapForward = new StepSnapForwardAction();
actionSeekTracePresent = new SeekTracePresentAction();
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
@@ -678,6 +681,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
.enabledWhen(c -> traceManager != null)
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
.buildAndInstallLocal(this);
actionGoToTime = GoToTimeAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null)
.onAction(c -> activatedGoToTime())
.buildAndInstallLocal(this);
traceManager.addSynchronizeFocusChangeListener(
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
}
@@ -689,6 +696,22 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
traceManager.setSynchronizeFocus(enabled);
}
private void activatedGoToTime() {
InputDialog dialog =
new InputDialog("Go To Time", "Schedule:", current.getTime().toString());
tool.showDialog(dialog);
if (dialog.isCanceled()) {
return;
}
try {
TraceSchedule time = TraceSchedule.parse(dialog.getValue());
traceManager.activateTime(time);
}
catch (IllegalArgumentException e) {
Msg.showError(this, getComponent(), "Go To Time", "Could not parse schedule");
}
}
private void traceTabSelected(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
@@ -24,19 +24,18 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Lists recorded snapshots in a trace", //
description = "Provides the component which lists snapshots and allows navigation", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = { //
TraceActivatedPluginEvent.class //
}, //
servicesRequired = { //
DebuggerTraceManagerService.class //
} //
)
@PluginInfo(
shortDescription = "Lists recorded snapshots in a trace",
description = "Provides the component which lists snapshots and allows navigation",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class
},
servicesRequired = {
DebuggerTraceManagerService.class
})
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
protected DebuggerTimeProvider provider;
@@ -38,6 +38,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
@@ -60,6 +61,7 @@ import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Msg;
import ghidra.util.Swing;
@@ -237,8 +239,10 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
private Trace currentTrace; // Copy for transition
@AutoServiceConsumed
private DebuggerListingService listingService; // TODO: For goto and selection
private DebuggerListingService listingService; // For goto and selection
// TODO: Allow address marking
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@@ -639,4 +643,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public boolean isEditsEnabled() {
return actionEnableEdits.isSelected();
}
public void goToTime(TraceSchedule time) {
traceManager.activateTime(time);
}
}
@@ -16,7 +16,6 @@
package ghidra.app.plugin.core.debug.gui.watch;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
@@ -33,14 +32,15 @@ import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
public class WatchRow {
public static final int TRUNCATE_BYTES_LENGTH = 64;
@@ -415,7 +415,7 @@ public class WatchRow {
return;
}
try (UndoableTransaction tid =
/*try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
final TraceMemorySpace space;
if (address.isRegisterAddress()) {
@@ -427,7 +427,27 @@ public class WatchRow {
space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
}
space.putBytes(coordinates.getViewSnap(), address, ByteBuffer.wrap(bytes));
}*/
TraceSchedule time =
coordinates.getTime().patched(coordinates.getThread(), generateSleigh(bytes));
provider.goToTime(time);
}
protected String generateSleigh(byte[] bytes) {
BigInteger value = Utils.bytesToBigInteger(bytes, bytes.length,
trace.getBaseLanguage().isBigEndian(), false);
if (address.isMemoryAddress()) {
AddressSpace space = address.getAddressSpace();
return String.format("*[%s]:%d 0x%s:%d=0x%s",
space.getName(), bytes.length,
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
value.toString(16));
}
Register register = trace.getBaseLanguage().getRegister(address, bytes.length);
if (register == null) {
throw new AssertionError("Can only modify memory or register");
}
return String.format("%s=0x%s", register, value.toString(16));
}
public int getValueLength() {
@@ -57,7 +57,7 @@ public abstract class AbstractReadsTargetPcodeExecutorState
public byte[] read(long offset, int size) {
if (source != null) {
AddressSet uninitialized = new AddressSet();
for (Range<UnsignedLong> rng : cache.getUninitialized(offset, offset + size)
for (Range<UnsignedLong> rng : cache.getUninitialized(offset, offset + size - 1)
.asRanges()) {
uninitialized.add(space.getAddress(lower(rng)),
space.getAddress(upper(rng)));
@@ -45,9 +45,9 @@ import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSchedule.CompareResult;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.CompareResult;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
@@ -53,8 +53,8 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.exception.*;
@@ -693,7 +693,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
varView.setSnap(emuSnap);
fireLocationEvent(coordinates);
}));
})).exceptionally(ex -> {
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
ex);
return null;
});
}
}
@@ -21,7 +21,7 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePl
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -25,7 +25,7 @@ import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.TriConsumer;
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
@@ -29,7 +29,7 @@ import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
import help.screenshot.GhidraScreenShotGenerator;
@@ -22,8 +22,8 @@ import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
import help.screenshot.GhidraScreenShotGenerator;
@@ -38,7 +38,7 @@ import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
@@ -59,6 +59,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
protected Register r0;
protected Register pc;
protected Register sp;
protected Register contextreg;
protected Register r0h;
protected Register r0l;
@@ -79,6 +80,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
r0 = tb.language.getRegister("r0");
pc = tb.language.getProgramCounter();
sp = tb.language.getDefaultCompilerSpec().getStackPointer();
contextreg = tb.language.getContextBaseRegister();
pch = tb.language.getRegister("pch");
pcl = tb.language.getRegister("pcl");
@@ -381,6 +383,38 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
assertR0RowTypePopulated();
}
// TODO: Test that contextreg cannot be modified
// TODO: Make contextreg modifiable by Registers window
@Test
public void testDeadModifyValueEmulates() throws Exception {
traceManager.openTrace(tb.trace);
TraceThread thread = addThread();
traceManager.activateThread(thread);
waitForSwing();
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
addRegisterValues(thread);
waitForDomainObject(tb.trace);
TraceMemoryRegisterSpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
RegisterRow row = findRegisterRow(r0);
setRowText(row, "1234");
waitForSwing();
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(0x1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals(BigInteger.valueOf(0x1234), row.getValue());
});
}
@Test
public void testLiveModifyValueAffectsTarget() throws Exception {
TraceRecorder recorder = recordAndWaitSync();
@@ -26,8 +26,8 @@ import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.trace.database.time.DBTraceTimeManager;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
@@ -50,7 +50,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
first.setRealTime(c.getTimeInMillis());
TraceSnapshot second = timeManager.getSnapshot(10, true);
second.setDescription("Snap 10");
second.setSchedule(TraceSchedule.parse("0:5,t1-5"));
second.setSchedule(TraceSchedule.parse("0:5;t1-5"));
}
}
@@ -81,7 +81,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
SnapshotRow secondRow = snapsDisplayed.get(1);
assertEquals(10, secondRow.getSnap());
assertEquals("Snap 10", secondRow.getDescription());
assertEquals("0:5,t1-5", secondRow.getSchedule());
assertEquals("0:5;t1-5", secondRow.getSchedule());
// Timestamp is left unchecked, since default is current time
}
@@ -292,39 +292,49 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testDeadEditRegister() {
WatchRow row = prepareTestDeadEdit("r0");
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryRegisterSpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(BigInteger.valueOf(0x1234), regVals.getValue(0, r0).getUnsignedValue());
row.setRawValueString("1234");
waitForSwing();
row.setRawValueString("0x1234");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(0x1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals("0x1234", row.getRawValueString());
});
assertEquals(BigInteger.valueOf(1234), regVals.getValue(0, r0).getUnsignedValue());
row.setRawValueString("1234"); // Decimal this time
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals("0x4d2", row.getRawValueString());
});
}
@Test
public void testDeadEditMemory() {
WatchRow row = prepareTestDeadEdit("*:8 r0");
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(8);
mem.getBytes(0, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x1234, buf.getLong());
row.setRawValueString("0x1234");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
buf.clear();
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x1234, buf.getLong());
});
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
waitForSwing();
buf.clear();
mem.getBytes(0, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x123456789abcdef0L, buf.getLong());
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
buf.clear();
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x123456789abcdef0L, buf.getLong());
});
}
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
@@ -379,17 +389,18 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
public void testLiveEditNonMappableRegister() throws Throwable {
WatchRow row = prepareTestLiveEdit("r1");
TraceThread thread = recorder.getTraceThread(mb.testThread1);
// Sanity check
assertFalse(recorder.isRegisterOnTarget(thread, r1));
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryRegisterSpace regs =
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(BigInteger.valueOf(0x1234),
regs.getValue(recorder.getSnap(), r1).getUnsignedValue());
waitForPass(() -> {
TraceMemoryRegisterSpace regs =
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(0x1234),
regs.getValue(viewSnap, r1).getUnsignedValue());
});
assertFalse(bank.regVals.containsKey("r1"));
}
@@ -35,7 +35,7 @@ import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;
@@ -58,11 +58,29 @@ public class CachedMemory implements MemoryReader, MemoryWriter {
private final MemoryWriter writer;
protected static class PendingRead {
protected static Range<UnsignedLong> normalize(Range<UnsignedLong> range) {
if (range.lowerBoundType() == BoundType.CLOSED) {
if (range.upperBoundType() == BoundType.OPEN ||
range.upperEndpoint().longValue() == -1) {
return range;
}
return Range.closedOpen(range.lowerEndpoint(),
range.upperEndpoint().plus(UnsignedLong.ONE));
}
assert range.lowerEndpoint().longValue() != -1;
UnsignedLong lower = range.lowerEndpoint().plus(UnsignedLong.ONE);
if (range.upperBoundType() == BoundType.OPEN ||
range.upperEndpoint().longValue() == -1) {
return Range.closed(lower, range.upperEndpoint());
}
return Range.closedOpen(lower, range.upperEndpoint().plus(UnsignedLong.ONE));
}
final Range<UnsignedLong> range;
final CompletableFuture<Void> future;
protected PendingRead(Range<UnsignedLong> range, CompletableFuture<Void> future) {
this.range = range;
this.range = normalize(range);
this.future = future;
}
}
@@ -90,7 +108,7 @@ public class CachedMemory implements MemoryReader, MemoryWriter {
}
protected synchronized CompletableFuture<Void> waitForReads(long addr, int len) {
RangeSet<UnsignedLong> undefined = memory.getUninitialized(addr, addr + len);
RangeSet<UnsignedLong> undefined = memory.getUninitialized(addr, addr + len - 1);
// Do the reads in parallel
AsyncFence fence = new AsyncFence();
for (Range<UnsignedLong> rng : undefined.asRanges()) {
@@ -115,7 +133,8 @@ public class CachedMemory implements MemoryReader, MemoryWriter {
}
}
NavigableMap<UnsignedLong, PendingRead> applicablePending =
pendingByLoc.subMap(rng.lowerEndpoint(), true, rng.upperEndpoint(), false);
pendingByLoc.subMap(rng.lowerEndpoint(), true, rng.upperEndpoint(),
rng.upperBoundType() == BoundType.CLOSED);
for (Map.Entry<UnsignedLong, PendingRead> ent : applicablePending.entrySet()) {
PendingRead pending = ent.getValue();
if (pending.future.isCompletedExceptionally()) {
@@ -128,7 +147,10 @@ public class CachedMemory implements MemoryReader, MemoryWriter {
// Now we're left with a set of needed ranges. Make a request for each
for (Range<UnsignedLong> needed : needRequests.asRanges()) {
final UnsignedLong lower = needed.lowerEndpoint();
final UnsignedLong upper = needed.upperEndpoint();
// NB. upper is only used in size computation, so overflow to 0 is no big deal
final UnsignedLong upper = needed.upperBoundType() == BoundType.CLOSED
? needed.upperEndpoint().plus(UnsignedLong.ONE)
: needed.upperEndpoint();
/*Msg.debug(this,
"Need to read: [" + lower.toString(16) + ":" + upper.toString(16) + ")");*/
CompletableFuture<byte[]> futureRead =
@@ -17,57 +17,82 @@ package ghidra.dbg.memory;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.*;
import org.junit.Test;
import mockit.Expectations;
import mockit.Mocked;
public class CachedMemoryTest {
class ReadRecord extends CompletableFuture<byte[]> {
static class RequestRecord<T> {
final CompletableFuture<T> future = new CompletableFuture<>();
final long address;
public RequestRecord(long address) {
this.address = address;
}
}
static class ReadRequestRecord extends RequestRecord<byte[]> {
final int length;
public ReadRecord(long address, int length) {
this.address = address;
public ReadRequestRecord(long address, int length) {
super(address);
this.length = length;
}
}
class WriteRecord extends CompletableFuture<Void> {
final long address;
static class WriteRequestRecord extends RequestRecord<Void> {
final byte[] data;
public WriteRecord(long address, byte[] data) {
this.address = address;
public WriteRequestRecord(long address, byte[] data) {
super(address);
this.data = data;
}
}
class DummyMemory implements MemoryReader, MemoryWriter {
final List<CompletableFuture<?>> record = new ArrayList<>();
@Override
public CompletableFuture<Void> writeMemory(long address, byte[] data) {
return null;
}
static class TestMemoryReaderWriter implements MemoryReader, MemoryWriter {
Deque<RequestRecord<?>> earlies = new LinkedList<>();
Deque<RequestRecord<?>> requests = new LinkedList<>();
@Override
public CompletableFuture<byte[]> readMemory(long address, int length) {
return new ReadRecord(address, length);
RequestRecord<?> early = earlies.poll();
if (early != null) {
ReadRequestRecord req = (ReadRequestRecord) early;
assertEquals(req.address, address);
assertEquals(req.length, length);
return req.future;
}
ReadRequestRecord req = new ReadRequestRecord(address, length);
requests.add(req);
return req.future;
}
@Override
public CompletableFuture<Void> writeMemory(long address, byte[] data) {
WriteRequestRecord req = new WriteRequestRecord(address, data);
requests.add(req);
return req.future;
}
public void expectEarlyRead(long address, byte[] data) {
ReadRequestRecord req = new ReadRequestRecord(address, data.length);
req.future.complete(data);
earlies.add(req);
}
public ReadRequestRecord assertPollRead() {
return (ReadRequestRecord) requests.remove();
}
public WriteRequestRecord assertPollWrite() {
return (WriteRequestRecord) requests.remove();
}
}
interface MemoryReaderWriter extends MemoryReader, MemoryWriter {
// Nothing new, just combined interfaces
}
@Mocked
protected MemoryReaderWriter memory;
protected TestMemoryReaderWriter memory = new TestMemoryReaderWriter();
byte[] inc(int len) {
byte[] result = new byte[len];
@@ -83,30 +108,37 @@ public class CachedMemoryTest {
@Test
public void testSingleRead() throws Exception {
final CompletableFuture<byte[]> raw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(1234, 90);
result = raw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> future = cache.readMemory(1234, 90);
raw.complete(inc(90));
ReadRequestRecord rec = memory.assertPollRead();
assertEquals(1234, rec.address);
assertEquals(90, rec.length);
rec.future.complete(inc(90));
byte[] arr = future.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(90), arr);
}
@Test
public void testSingleReadIncludesMax() throws Exception {
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> future = cache.readMemory(-4, 4);
ReadRequestRecord rec = memory.assertPollRead();
assertEquals(-4, rec.address);
assertEquals(4, rec.length);
rec.future.complete(new byte[] { 1, 2, 3, 4 });
byte[] arr = future.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, arr);
}
@Test
public void testSingleReadCompletedEarly() throws Exception {
new Expectations() {
{
memory.readMemory(1234, 90);
result = CompletableFuture.completedFuture(inc(90));
}
};
memory.expectEarlyRead(1234, inc(90));
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> future = cache.readMemory(1234, 90);
@@ -117,25 +149,20 @@ public class CachedMemoryTest {
@Test
public void testOverlappingSequentialReads() throws Exception {
final CompletableFuture<byte[]> firstRaw = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(1234, 100);
result = firstRaw;
memory.readMemory(1334, 50);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(1234, 100);
firstRaw.complete(inc(100));
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(1234, req1.address);
assertEquals(100, req1.length);
req1.future.complete(inc(100));
byte[] firstArr = first.get(1000, TimeUnit.MILLISECONDS);
CompletableFuture<byte[]> second = cache.readMemory(1284, 100);
secondRaw.complete(inc(50));
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(1334, req2.address);
assertEquals(50, req2.length);
req2.future.complete(inc(50));
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(100), firstArr);
@@ -148,23 +175,20 @@ public class CachedMemoryTest {
@Test
public void testOverlappingParallelReads() throws Exception {
final CompletableFuture<byte[]> firstRaw = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(1234, 100);
result = firstRaw;
memory.readMemory(1334, 50);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(1234, 100);
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(1234, req1.address);
assertEquals(100, req1.length);
CompletableFuture<byte[]> second = cache.readMemory(1284, 100);
firstRaw.complete(inc(100));
secondRaw.complete(inc(50));
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(1334, req2.address);
assertEquals(50, req2.length);
req1.future.complete(inc(100));
req2.future.complete(inc(50));
byte[] firstArr = first.get(1000, TimeUnit.MILLISECONDS);
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
@@ -178,28 +202,24 @@ public class CachedMemoryTest {
@Test
public void testSameStartsGrowingParallelReads() throws Exception {
final CompletableFuture<byte[]> firstRaw = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(1234, 50);
result = firstRaw;
memory.readMemory(1284, 50);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(1234, 50);
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(1234, req1.address);
assertEquals(50, req1.length);
CompletableFuture<byte[]> second = cache.readMemory(1234, 100);
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(1284, req2.address);
assertEquals(50, req2.length);
assertFalse(first.isDone());
firstRaw.complete(inc(50));
req1.future.complete(inc(50));
byte[] firstArr = first.get(1000, TimeUnit.MILLISECONDS);
assertFalse(second.isDone());
secondRaw.complete(inc(50));
req2.future.complete(inc(50));
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(50), firstArr);
@@ -212,23 +232,20 @@ public class CachedMemoryTest {
@Test
public void testLargeOffsetsParallelReads() throws Exception {
final CompletableFuture<byte[]> firstRaw = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(0x8000000000000000L, 100);
result = firstRaw;
memory.readMemory(0x8000000000000000L + 100, 50);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(0x8000000000000000L, 100);
CompletableFuture<byte[]> second = cache.readMemory(0x8000000000000000L + 50, 100);
firstRaw.complete(inc(100));
secondRaw.complete(inc(50));
CompletableFuture<byte[]> first = cache.readMemory(0x8000_0000_0000_0000L, 100);
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(0x8000_0000_0000_0000L, req1.address);
assertEquals(100, req1.length);
CompletableFuture<byte[]> second = cache.readMemory(0x8000_0000_0000_0000L + 50, 100);
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(0x8000_0000_0000_0000L + 100, req2.address);
assertEquals(50, req2.length);
req1.future.complete(inc(100));
req2.future.complete(inc(50));
byte[] firstArr = first.get(1000, TimeUnit.MILLISECONDS);
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
@@ -242,22 +259,15 @@ public class CachedMemoryTest {
@Test
public void testErroneousRead() throws Exception {
final CompletableFuture<byte[]> firstErr = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(0, 100);
result = firstErr;
memory.readMemory(50, 100);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(0, 100);
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(0, req1.address);
assertEquals(100, req1.length);
Throwable sentinel = new AssertionError("Sentinel");
firstErr.completeExceptionally(sentinel);
req1.future.completeExceptionally(sentinel);
try {
first.get(1000, TimeUnit.MILLISECONDS);
fail();
@@ -267,7 +277,10 @@ public class CachedMemoryTest {
}
CompletableFuture<byte[]> second = cache.readMemory(50, 100);
secondRaw.complete(inc(100));
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(50, req2.address);
assertEquals(100, req2.length);
req2.future.complete(inc(100));
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(100), secondArr);
@@ -275,26 +288,23 @@ public class CachedMemoryTest {
@Test
public void testPartialResult() throws Exception {
final CompletableFuture<byte[]> firstPartial = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(0, 100);
result = firstPartial;
memory.readMemory(50, 50);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(0, 100);
firstPartial.complete(inc(50)); // request was for 100!
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(0, req1.address);
assertEquals(100, req1.length);
req1.future.complete(inc(50)); // request was for 100!
byte[] firstArr = first.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(50), firstArr);
CompletableFuture<byte[]> second = cache.readMemory(25, 75);
secondRaw.complete(inc(50));
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(50, req2.address);
assertEquals(50, req2.length);
req2.future.complete(inc(50));
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
byte[] dinc = new byte[75];
@@ -305,24 +315,20 @@ public class CachedMemoryTest {
@Test
public void testDisjointParallellFirstErrs() throws Exception {
final CompletableFuture<byte[]> firstErr = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(0, 25);
result = firstErr;
memory.readMemory(50, 25);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(0, 25);
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(0, req1.address);
assertEquals(25, req1.length);
CompletableFuture<byte[]> second = cache.readMemory(50, 25);
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(50, req2.address);
assertEquals(25, req2.length);
Throwable sentinel = new AssertionError("Sentinel");
firstErr.completeExceptionally(sentinel);
req1.future.completeExceptionally(sentinel);
try {
first.get(0, TimeUnit.MILLISECONDS);
fail();
@@ -331,31 +337,27 @@ public class CachedMemoryTest {
assertEquals(sentinel, e.getCause());
}
secondRaw.complete(inc(25));
req2.future.complete(inc(25));
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(25), secondArr);
}
@Test
public void testPartialFromErr() throws Exception {
final CompletableFuture<byte[]> firstErr = new CompletableFuture<>();
final CompletableFuture<byte[]> secondRaw = new CompletableFuture<>();
new Expectations() {
{
memory.readMemory(50, 50);
result = firstErr;
memory.readMemory(0, 50);
result = secondRaw;
}
};
CachedMemory cache = new CachedMemory(memory, memory);
CompletableFuture<byte[]> first = cache.readMemory(50, 50);
ReadRequestRecord req1 = memory.assertPollRead();
assertEquals(50, req1.address);
assertEquals(50, req1.length);
CompletableFuture<byte[]> second = cache.readMemory(0, 100);
ReadRequestRecord req2 = memory.assertPollRead();
assertEquals(0, req2.address);
assertEquals(50, req2.length);
Throwable sentinel = new AssertionError("Sentinel");
firstErr.completeExceptionally(sentinel);
req1.future.completeExceptionally(sentinel);
try {
first.get(0, TimeUnit.MILLISECONDS);
fail();
@@ -364,7 +366,7 @@ public class CachedMemoryTest {
assertEquals(sentinel, e.getCause());
}
// First should still succeed partially
secondRaw.complete(inc(50));
req2.future.complete(inc(50));
byte[] secondArr = second.get(1000, TimeUnit.MILLISECONDS);
assertArrayEquals(inc(50), secondArr);
}
@@ -48,7 +48,8 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
@Override
public byte[] read(long offset, int size) {
RangeSet<UnsignedLong> uninitialized = cache.getUninitialized(offset, offset + size);
RangeSet<UnsignedLong> uninitialized =
cache.getUninitialized(offset, offset + size - 1);
if (!uninitialized.isEmpty()) {
size = checkUninitialized(source, space.getAddress(offset), size,
@@ -155,7 +155,7 @@ public class TraceCachedWriteBytesPcodeExecutorState
// TODO: Warn or bail when reading UNKNOWN bytes
// NOTE: Read without regard to gaps
// NOTE: Cannot write those gaps, though!!!
readUninitializedFromSource(cache.getUninitialized(offset, offset + size));
readUninitializedFromSource(cache.getUninitialized(offset, offset + size - 1));
}
return readCached(offset, size);
}
@@ -716,30 +716,38 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
}
}
public void updateViewsAddBlock(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryAddBlock(region));
public void updateViewsAddRegionBlock(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryAddRegionBlock(region));
}
public void updateViewsChangeBlockName(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryChangeBlockName(region));
public void updateViewsChangeRegionBlockName(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryChangeRegionBlockName(region));
}
public void updateViewsChangeBlockFlags(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryChangeBlockFlags(region));
public void updateViewsChangeRegionBlockFlags(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryChangeRegionBlockFlags(region));
}
public void updateViewsChangeBlockRange(DBTraceMemoryRegion region,
public void updateViewsChangeRegionBlockRange(DBTraceMemoryRegion region,
AddressRange oldRange, AddressRange newRange) {
allViews(v -> v.updateMemoryChangeBlockRange(region, oldRange, newRange));
allViews(v -> v.updateMemoryChangeRegionBlockRange(region, oldRange, newRange));
}
public void updateViewsChangeBlockLifespan(DBTraceMemoryRegion region,
public void updateViewsChangeRegionBlockLifespan(DBTraceMemoryRegion region,
Range<Long> oldLifespan, Range<Long> newLifespan) {
allViews(v -> v.updateMemoryChangeBlockLifespan(region, oldLifespan, newLifespan));
allViews(v -> v.updateMemoryChangeRegionBlockLifespan(region, oldLifespan, newLifespan));
}
public void updateViewsDeleteBlock(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryDeleteBlock(region));
public void updateViewsDeleteRegionBlock(DBTraceMemoryRegion region) {
allViews(v -> v.updateMemoryDeleteRegionBlock(region));
}
public void updateViewsAddSpaceBlock(AddressSpace space) {
allViews(v -> v.updateMemoryAddSpaceBlock(space));
}
public void updateViewsDeleteSpaceBlock(AddressSpace space) {
allViews(v -> v.updateMemoryDeleteSpaceBlock(space));
}
public void updateViewsRefreshBlocks() {
@@ -255,6 +255,7 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
// Only if it succeeds do we store the record
DBTraceOverlaySpaceEntry ent = overlayStore.create();
ent.set(space.getName(), base.getName());
trace.updateViewsAddSpaceBlock(space);
return space;
}
}
@@ -268,7 +269,10 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
}
overlayStore.delete(exists);
TraceAddressFactory factory = trace.getInternalAddressFactory();
AddressSpace space = factory.getAddressSpace(name);
assert space != null;
factory.removeOverlaySpace(name);
trace.updateViewsDeleteSpaceBlock(space);
}
}
}
@@ -154,7 +154,7 @@ public class DBTraceMemoryRegion
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
this.name = name;
update(NAME_COLUMN);
space.trace.updateViewsChangeBlockName(this);
space.trace.updateViewsChangeRegionBlockName(this);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this));
@@ -175,7 +175,7 @@ public class DBTraceMemoryRegion
checkPathConflicts(newLifespan, path);
Range<Long> oldLifespan = getLifespan();
doSetLifespan(newLifespan);
space.trace.updateViewsChangeBlockLifespan(this, oldLifespan, newLifespan);
space.trace.updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
space.trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.LIFESPAN_CHANGED,
space, this, oldLifespan, newLifespan));
@@ -218,7 +218,7 @@ public class DBTraceMemoryRegion
oldRange = range;
checkOverlapConflicts(lifespan, newRange);
doSetRange(newRange);
space.trace.updateViewsChangeBlockRange(this, oldRange, newRange);
space.trace.updateViewsChangeRegionBlockRange(this, oldRange, newRange);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this));
@@ -278,7 +278,7 @@ public class DBTraceMemoryRegion
this.flags.add(f);
}
update(FLAGS_COLUMN);
space.trace.updateViewsChangeBlockFlags(this);
space.trace.updateViewsChangeRegionBlockFlags(this);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this));
@@ -293,7 +293,7 @@ public class DBTraceMemoryRegion
this.flags.add(f);
}
update(FLAGS_COLUMN);
space.trace.updateViewsChangeBlockFlags(this);
space.trace.updateViewsChangeRegionBlockFlags(this);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this));
@@ -308,7 +308,7 @@ public class DBTraceMemoryRegion
this.flags.remove(f);
}
update(FLAGS_COLUMN);
space.trace.updateViewsChangeBlockFlags(this);
space.trace.updateViewsChangeRegionBlockFlags(this);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this));
@@ -161,7 +161,7 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
DBTraceMemoryRegion region =
regionMapSpace.put(new ImmutableTraceAddressSnapRange(range, lifespan), null);
region.set(path, path, flags);
trace.updateViewsAddBlock(region);
trace.updateViewsAddRegionBlock(region);
trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.ADDED, this, region));
return region;
@@ -244,7 +244,7 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
try (LockHold hold = LockHold.lock(lock.writeLock())) {
regionMapSpace.deleteData(region);
regionCache.remove(region);
trace.updateViewsDeleteBlock(region);
trace.updateViewsDeleteRegionBlock(region);
trace.setChanged(
new TraceChangeRecord<>(TraceMemoryRegionChangeType.DELETED, this, region));
}
@@ -69,6 +69,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
DBTraceMemorySpace mem = trace.getMemoryManager().get(this, false);
if (mem == null) {
// TODO: 0-fill instead? Will need to check memory space bounds.
return 0;
}
return mem.getViewBytes(program.snap, address.add(addressOffset), buffer);
}
@@ -19,6 +19,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Consumer;
import com.google.common.cache.RemovalNotification;
@@ -42,6 +43,7 @@ public abstract class AbstractDBTraceProgramViewMemory
protected final DBTraceMemoryManager memoryManager;
protected AddressSetView addressSet;
protected boolean forceFullView = false;
protected long snap;
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
@@ -50,16 +52,57 @@ public abstract class AbstractDBTraceProgramViewMemory
setSnap(program.snap);
}
protected void blockRemoved(
RemovalNotification<DBTraceMemoryRegion, DBTraceProgramViewMemoryBlock> rn) {
protected void regionBlockRemoved(
RemovalNotification<DBTraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> rn) {
// Nothing
}
protected void spaceBlockRemoved(
RemovalNotification<AddressSpace, DBTraceProgramViewMemorySpaceBlock> rn) {
// Nothing
}
protected abstract void recomputeAddressSet();
protected void forPhysicalSpaces(Consumer<AddressSpace> consumer) {
for (AddressSpace space : program.getAddressFactory().getAddressSpaces()) {
// NB. Overlay's isMemory depends on its base space
// TODO: Allow other?
// For some reason "other" is omitted from factory.getAddressSet
if (space.isMemorySpace() && space.getType() != AddressSpace.TYPE_OTHER) {
consumer.accept(space);
}
}
}
protected void computeFullAdddressSet() {
AddressSet temp = new AddressSet();
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
addressSet = temp;
}
@Override
public void setForceFullView(boolean forceFullView) {
this.forceFullView = forceFullView;
if (forceFullView) {
computeFullAdddressSet();
}
else {
recomputeAddressSet();
}
program.fireObjectRestored();
}
@Override
public boolean isForceFullView() {
return forceFullView;
}
void setSnap(long snap) {
this.snap = snap;
recomputeAddressSet();
if (!forceFullView) {
recomputeAddressSet();
}
}
@Override
@@ -463,17 +506,23 @@ public abstract class AbstractDBTraceProgramViewMemory
}
protected synchronized void addRange(AddressRange range) {
addressSet = addressSet.union(new AddressSet(range));
if (!forceFullView) {
addressSet = addressSet.union(new AddressSet(range));
}
}
protected synchronized void removeRange(AddressRange range) {
addressSet = addressSet.subtract(new AddressSet(range));
if (!forceFullView) {
addressSet = addressSet.subtract(new AddressSet(range));
}
}
protected synchronized void changeRange(AddressRange remove, AddressRange add) {
AddressSet temp = new AddressSet(addressSet);
temp.delete(remove);
temp.add(add);
addressSet = temp;
if (!forceFullView) {
AddressSet temp = new AddressSet(addressSet);
temp.delete(remove);
temp.add(add);
addressSet = temp;
}
}
}
@@ -16,42 +16,39 @@
package ghidra.trace.database.program;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import ghidra.framework.store.LockException;
import ghidra.program.database.mem.ByteMappingScheme;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.*;
import ghidra.trace.database.memory.DBTraceMemoryRegion;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemorySpaceInputStream;
import ghidra.util.MathUtilities;
// TODO: Proper locking all over here
public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlock {
private class DBTraceProgramViewMemoryBlockSourceInfo implements MemoryBlockSourceInfo {
private class MyMemoryBlockSourceInfo implements MemoryBlockSourceInfo {
@Override
public long getLength() {
return region.getLength();
return getMemoryBlock().getSize();
}
@Override
public Address getMinAddress() {
return region.getMinAddress();
return getMemoryBlock().getStart();
}
@Override
public Address getMaxAddress() {
return region.getMaxAddress();
return getMemoryBlock().getEnd();
}
@Override
public String getDescription() {
return "Trace region: " + region;
return getInfoDescription();
}
@Override
@@ -81,12 +78,12 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
@Override
public MemoryBlock getMemoryBlock() {
return DBTraceProgramViewMemoryBlock.this;
return AbstractDBTraceProgramViewMemoryBlock.this;
}
@Override
public boolean contains(Address address) {
return region.getRange().contains(address);
return getMemoryBlock().contains(address);
}
@Override
@@ -95,15 +92,26 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
}
}
private final DBTraceProgramView program;
private final DBTraceMemoryRegion region;
protected final DBTraceProgramView program;
private final List<MemoryBlockSourceInfo> info =
Collections.singletonList(new DBTraceProgramViewMemoryBlockSourceInfo());
Collections.singletonList(new MyMemoryBlockSourceInfo());
public DBTraceProgramViewMemoryBlock(DBTraceProgramView program, DBTraceMemoryRegion region) {
protected AbstractDBTraceProgramViewMemoryBlock(DBTraceProgramView program) {
this.program = program;
this.region = region;
}
protected abstract String getInfoDescription();
protected AddressSpace getAddressSpace() {
return getStart().getAddressSpace();
}
protected DBTraceMemorySpace getMemorySpace() {
return program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false);
}
protected AddressRange getAddressRange() {
return new AddressRangeImpl(getStart(), getEnd());
}
@Override
@@ -111,112 +119,40 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
return this.getStart().compareTo(that.getStart());
}
@Override
public void setPermissions(boolean read, boolean write, boolean execute) {
region.setRead(read);
region.setWrite(write);
region.setExecute(execute);
}
@Override
public int getPermissions() {
int bits = 0;
for (TraceMemoryFlag flag : region.getFlags()) {
bits |= flag.getBits();
}
return bits;
}
@Override
public InputStream getData() {
AddressRange range = region.getRange();
DBTraceMemorySpace space =
program.trace.getMemoryManager().getMemorySpace(range.getAddressSpace(), false);
if (space == null) {
return null;
}
return new TraceMemorySpaceInputStream(program, space, range);
}
@Override
public boolean contains(Address addr) {
return region.getRange().contains(addr);
}
@Override
public Address getStart() {
return region.getRange().getMinAddress();
}
@Override
public Address getEnd() {
return region.getRange().getMaxAddress();
return getAddressRange().contains(addr);
}
@Override
public long getSize() {
return region.getRange().getLength();
return getEnd().subtract(getStart()) + 1;
}
@Override
public String getName() {
return region.getName();
}
@Override
public void setName(String name) throws LockException {
region.setName(name);
public BigInteger getSizeAsBigInteger() {
return getEnd().getOffsetAsBigInteger()
.subtract(getStart().getOffsetAsBigInteger())
.add(BigInteger.ONE);
}
@Override
public String getComment() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setComment(String comment) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();
}
@Override
public boolean isRead() {
return region.isRead();
}
@Override
public void setRead(boolean r) {
region.setRead(r);
}
@Override
public boolean isWrite() {
return region.isWrite();
}
@Override
public void setWrite(boolean w) {
region.setWrite(w);
}
@Override
public boolean isExecute() {
return region.isExecute();
}
@Override
public void setExecute(boolean e) {
region.setExecute(e);
}
@Override
public boolean isVolatile() {
return region.isVolatile();
}
@Override
public void setVolatile(boolean v) {
region.setVolatile(v);
public InputStream getData() {
DBTraceMemorySpace ms = getMemorySpace();
if (ms == null) {
return null;
}
return new TraceMemorySpaceInputStream(program, ms, getAddressRange());
}
@Override
@@ -231,7 +167,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
@Override
public byte getByte(Address addr) throws MemoryAccessException {
AddressRange range = region.getRange();
AddressRange range = getAddressRange();
if (!range.contains(addr)) {
throw new MemoryAccessException();
}
@@ -254,7 +190,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
@Override
public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
AddressRange range = region.getRange();
AddressRange range = getAddressRange();
if (!range.contains(addr)) {
throw new MemoryAccessException();
}
@@ -263,7 +199,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
if (space == null) {
throw new MemoryAccessException("Space does not exist");
}
len = (int) Math.min(len, range.getMaxAddress().subtract(addr) + 1);
len = MathUtilities.unsignedMin(len, range.getMaxAddress().subtract(addr) + 1);
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
}
@@ -281,7 +217,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
@Override
public int putBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
AddressRange range = region.getRange();
AddressRange range = getAddressRange();
if (!range.contains(addr)) {
throw new MemoryAccessException();
}
@@ -308,7 +244,8 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
@Override
public boolean isOverlay() {
return false;
// TODO: What effect does this have? Does it makes sense for trace "overlays"?
return getAddressSpace().isOverlaySpace();
}
@Override
@@ -160,6 +160,15 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
new AddressRangeImpl(fromAddr, fromAddr));
}
@Override
public void removeAllReferencesTo(Address toAddr) {
if (refs(false) == null) {
return;
}
refs.clearReferencesTo(Range.closed(program.snap, program.snap),
new AddressRangeImpl(toAddr, toAddr));
}
@Override
public Reference[] getReferencesTo(Variable var) {
return TODO();
@@ -943,6 +943,13 @@ public class DBTraceProgramView implements TraceProgramView {
}
}
/**
* Fires object-restored event on this view and all associated register views.
*/
protected void fireObjectRestored() {
fireEventAllViews(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
}
@Override
public String toString() {
return String.format("<%s on %s at snap=%d>", getClass().getSimpleName(), trace, snap);
@@ -1555,52 +1562,63 @@ public class DBTraceProgramView implements TraceProgramView {
trace.removeTransactionListener(listener);
}
public void updateMemoryAddBlock(DBTraceMemoryRegion region) {
public void updateMemoryAddRegionBlock(DBTraceMemoryRegion region) {
if (!isRegionVisible(region)) {
return;
}
memory.updateAddBlock(region);
memory.updateAddRegionBlock(region);
}
public void updateMemoryChangeBlockName(DBTraceMemoryRegion region) {
public void updateMemoryChangeRegionBlockName(DBTraceMemoryRegion region) {
if (!isRegionVisible(region)) {
return;
}
memory.updateChangeBlockName(region);
memory.updateChangeRegionBlockName(region);
}
public void updateMemoryChangeBlockFlags(DBTraceMemoryRegion region) {
public void updateMemoryChangeRegionBlockFlags(DBTraceMemoryRegion region) {
if (!isRegionVisible(region)) {
return;
}
memory.updateChangeBlockFlags(region);
memory.updateChangeRegionBlockFlags(region);
}
public void updateMemoryChangeBlockRange(DBTraceMemoryRegion region, AddressRange oldRange,
public void updateMemoryChangeRegionBlockRange(DBTraceMemoryRegion region,
AddressRange oldRange,
AddressRange newRange) {
if (!isRegionVisible(region)) {
return;
}
memory.updateChangeBlockRange(region, oldRange, newRange);
memory.updateChangeRegionBlockRange(region, oldRange, newRange);
}
public void updateMemoryChangeBlockLifespan(DBTraceMemoryRegion region,
public void updateMemoryChangeRegionBlockLifespan(DBTraceMemoryRegion region,
Range<Long> oldLifespan, Range<Long> newLifespan) {
boolean inOld = isRegionVisible(region, oldLifespan);
boolean inNew = isRegionVisible(region, newLifespan);
if (inOld && !inNew) {
memory.updateDeleteBlock(region);
memory.updateDeleteRegionBlock(region);
}
if (!inOld && inNew) {
memory.updateAddBlock(region);
memory.updateAddRegionBlock(region);
}
}
public void updateMemoryDeleteBlock(DBTraceMemoryRegion region) {
public void updateMemoryDeleteRegionBlock(DBTraceMemoryRegion region) {
if (!isRegionVisible(region)) {
return;
}
memory.updateAddBlock(region);
memory.updateAddRegionBlock(region);
}
public void updateMemoryAddSpaceBlock(AddressSpace space) {
// Spaces not not time-bound. No visibility check.
memory.updateAddSpaceBlock(space);
}
public void updateMemoryDeleteSpaceBlock(AddressSpace space) {
// Spaces not not time-bound. No visibility check.
memory.updateDeleteSpaceBlock(space);
}
public void updateMemoryRefreshBlocks() {
@@ -27,8 +27,19 @@ import ghidra.trace.database.memory.DBTraceMemoryRegion;
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
private final Map<DBTraceMemoryRegion, DBTraceProgramViewMemoryBlock> blocks =
CacheBuilder.newBuilder().removalListener(this::blockRemoved).weakValues().build().asMap();
// NB. Keep both per-region and force-full (per-space) block sets ready
private final Map<DBTraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> regionBlocks =
CacheBuilder.newBuilder()
.removalListener(this::regionBlockRemoved)
.weakValues()
.build()
.asMap();
private final Map<AddressSpace, DBTraceProgramViewMemorySpaceBlock> spaceBlocks =
CacheBuilder.newBuilder()
.removalListener(this::spaceBlockRemoved)
.weakValues()
.build()
.asMap();
public DBTraceProgramViewMemory(DBTraceProgramView program) {
super(program);
@@ -64,58 +75,84 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
addressSet = temp;
}
protected MemoryBlock getBlock(DBTraceMemoryRegion region) {
return blocks.computeIfAbsent(region,
r -> new DBTraceProgramViewMemoryBlock(program, region));
protected MemoryBlock getRegionBlock(DBTraceMemoryRegion region) {
return regionBlocks.computeIfAbsent(region,
r -> new DBTraceProgramViewMemoryRegionBlock(program, region));
}
protected MemoryBlock getSpaceBlock(AddressSpace space) {
return spaceBlocks.computeIfAbsent(space,
s -> new DBTraceProgramViewMemorySpaceBlock(program, space));
}
@Override
public MemoryBlock getBlock(Address addr) {
if (forceFullView) {
return getSpaceBlock(addr.getAddressSpace());
}
DBTraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
return region == null ? null : getBlock(region);
return region == null ? null : getRegionBlock(region);
}
@Override
public MemoryBlock getBlock(String blockName) {
if (forceFullView) {
AddressSpace space = program.getAddressFactory().getAddressSpace(blockName);
return space == null ? null : getSpaceBlock(space);
}
DBTraceMemoryRegion region =
getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
return region == null ? null : getBlock(region);
return region == null ? null : getRegionBlock(region);
}
@Override
public MemoryBlock[] getBlocks() {
List<MemoryBlock> result = new ArrayList<>();
forVisibleRegions(reg -> result.add(getBlock(reg)));
if (forceFullView) {
forPhysicalSpaces(space -> result.add(getSpaceBlock(space)));
}
else {
forVisibleRegions(reg -> result.add(getRegionBlock(reg)));
}
Collections.sort(result, Comparator.comparing(b -> b.getStart()));
return result.toArray(new MemoryBlock[result.size()]);
}
public void updateAddBlock(DBTraceMemoryRegion region) {
public void updateAddRegionBlock(DBTraceMemoryRegion region) {
// TODO: add block to cache?
addRange(region.getRange());
}
public void updateChangeBlockName(DBTraceMemoryRegion region) {
public void updateChangeRegionBlockName(DBTraceMemoryRegion region) {
// Nothing. Block name is taken from region, uncached
}
public void updateChangeBlockFlags(DBTraceMemoryRegion region) {
public void updateChangeRegionBlockFlags(DBTraceMemoryRegion region) {
// Nothing. Block flags are taken from region, uncached
}
public void updateChangeBlockRange(DBTraceMemoryRegion region, AddressRange oldRange,
public void updateChangeRegionBlockRange(DBTraceMemoryRegion region, AddressRange oldRange,
AddressRange newRange) {
// TODO: update cached block? Nothing to update.
changeRange(oldRange, newRange);
}
public void updateDeleteBlock(DBTraceMemoryRegion region) {
blocks.remove(region);
public void updateDeleteRegionBlock(DBTraceMemoryRegion region) {
regionBlocks.remove(region);
removeRange(region.getRange());
}
public void updateAddSpaceBlock(AddressSpace space) {
// Nothing. Cache will construct it upon request, lazily
}
public void updateDeleteSpaceBlock(AddressSpace space) {
spaceBlocks.remove(space);
}
public void updateRefreshBlocks() {
blocks.clear();
regionBlocks.clear();
spaceBlocks.clear();
recomputeAddressSet();
}
}
@@ -0,0 +1,150 @@
/* ###
* 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.database.program;
import java.io.InputStream;
import java.math.BigInteger;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.*;
import ghidra.trace.database.memory.DBTraceMemoryRegion;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemorySpaceInputStream;
// TODO: Proper locking all over here
public class DBTraceProgramViewMemoryRegionBlock extends AbstractDBTraceProgramViewMemoryBlock {
private final DBTraceMemoryRegion region;
public DBTraceProgramViewMemoryRegionBlock(DBTraceProgramView program,
DBTraceMemoryRegion region) {
super(program);
this.region = region;
}
@Override
protected String getInfoDescription() {
return "Trace region: " + region;
}
@Override
protected AddressSpace getAddressSpace() {
return region.getRange().getAddressSpace();
}
@Override
protected AddressRange getAddressRange() {
return region.getRange();
}
@Override
public void setPermissions(boolean read, boolean write, boolean execute) {
region.setRead(read);
region.setWrite(write);
region.setExecute(execute);
}
@Override
public int getPermissions() {
int bits = 0;
for (TraceMemoryFlag flag : region.getFlags()) {
bits |= flag.getBits();
}
return bits;
}
@Override
public InputStream getData() {
AddressRange range = region.getRange();
DBTraceMemorySpace space =
program.trace.getMemoryManager().getMemorySpace(range.getAddressSpace(), false);
if (space == null) {
return null;
}
return new TraceMemorySpaceInputStream(program, space, range);
}
@Override
public Address getStart() {
return region.getRange().getMinAddress();
}
@Override
public Address getEnd() {
return region.getRange().getMaxAddress();
}
@Override
public long getSize() {
return region.getRange().getLength();
}
@Override
public BigInteger getSizeAsBigInteger() {
return region.getRange().getBigLength();
}
@Override
public String getName() {
return region.getName();
}
@Override
public void setName(String name) throws LockException {
region.setName(name);
}
@Override
public boolean isRead() {
return region.isRead();
}
@Override
public void setRead(boolean r) {
region.setRead(r);
}
@Override
public boolean isWrite() {
return region.isWrite();
}
@Override
public void setWrite(boolean w) {
region.setWrite(w);
}
@Override
public boolean isExecute() {
return region.isExecute();
}
@Override
public void setExecute(boolean e) {
region.setExecute(e);
}
@Override
public boolean isVolatile() {
return region.isVolatile();
}
@Override
public void setVolatile(boolean v) {
region.setVolatile(v);
}
}
@@ -0,0 +1,137 @@
/* ###
* 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.database.program;
import java.io.InputStream;
import java.util.*;
import ghidra.framework.store.LockException;
import ghidra.program.database.mem.ByteMappingScheme;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.*;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.memory.TraceMemorySpaceInputStream;
public class DBTraceProgramViewMemorySpaceBlock extends AbstractDBTraceProgramViewMemoryBlock {
private final AddressSpace space;
public DBTraceProgramViewMemorySpaceBlock(DBTraceProgramView program, AddressSpace space) {
super(program);
this.space = space;
}
@Override
protected String getInfoDescription() {
return "Trace space: " + space;
}
@Override
protected AddressSpace getAddressSpace() {
return space;
}
@Override
public Address getStart() {
return space.getMinAddress();
}
@Override
public Address getEnd() {
return space.getMaxAddress();
}
@Override
public int getPermissions() {
return MemoryBlock.READ | MemoryBlock.WRITE | MemoryBlock.EXECUTE;
}
@Override
public String getName() {
return space.getName();
}
@Override
public void setName(String name) throws IllegalArgumentException, LockException {
throw new UnsupportedOperationException();
}
@Override
public String getComment() {
return null;
}
@Override
public void setComment(String comment) {
throw new UnsupportedOperationException();
}
@Override
public boolean isRead() {
return true;
}
@Override
public void setRead(boolean r) {
throw new UnsupportedOperationException();
}
@Override
public boolean isWrite() {
return true;
}
@Override
public void setWrite(boolean w) {
throw new UnsupportedOperationException();
}
@Override
public boolean isExecute() {
return true;
}
@Override
public void setExecute(boolean e) {
throw new UnsupportedOperationException();
}
@Override
public void setPermissions(boolean read, boolean write, boolean execute) {
throw new UnsupportedOperationException();
}
@Override
public boolean isVolatile() {
return false;
}
@Override
public void setVolatile(boolean v) {
throw new UnsupportedOperationException();
}
@Override
public String getSourceName() {
return "Trace"; // TODO: What does this method actually do?
}
@Override
public void setSourceName(String sourceName) {
throw new UnsupportedOperationException();
}
}
@@ -33,6 +33,11 @@ public class DBTraceProgramViewRegisterMemory extends AbstractDBTraceProgramView
space.getAddressSpace().getMaxAddress()));
}
@Override
public void setForceFullView(boolean forceFullView) {
throw new UnsupportedOperationException();
}
@Override
protected void recomputeAddressSet() {
// AddressSet is always full space
@@ -16,6 +16,7 @@
package ghidra.trace.database.program;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
@@ -144,6 +145,11 @@ public class DBTraceProgramViewRegisterMemoryBlock implements MemoryBlock {
return range.getLength();
}
@Override
public BigInteger getSizeAsBigInteger() {
return range.getBigLength();
}
@Override
public String getName() {
return REGS_BLOCK_NAME;
@@ -28,7 +28,6 @@ import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.reloc.RelocationTable;
import ghidra.program.model.symbol.*;
@@ -40,6 +39,7 @@ import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.Trace;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.exception.CancelledException;
@@ -113,7 +113,7 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
}
@Override
public Memory getMemory() {
public TraceProgramViewMemory getMemory() {
return memory;
}
@@ -37,13 +37,6 @@ public class DBTraceVariableSnapProgramView extends DBTraceProgramView
super(trace, snap, compilerSpec);
}
/**
* Fires object-restored event on this view and all associated register views.
*/
protected void fireObjectRestored() {
fireEventAllViews(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
}
@Override
public void setSnap(long newSnap) {
if (this.snap == newSnap) {

Some files were not shown because too many files have changed in this diff Show More