GP-2676: Breakpoints can now be placed in emulator with Sleigh injections or custom conditions.

This commit is contained in:
Dan 2023-01-18 12:22:26 -05:00
parent df0274a4d7
commit 888c8c911d
116 changed files with 5676 additions and 1993 deletions

View file

@ -59,7 +59,7 @@ public enum TraceSleighUtils {
new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame);
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
throw new IllegalArgumentException("TracePlatform must use a Sleigh language");
}
return new PcodeExecutor<>((SleighLanguage) language,
BytesPcodeArithmetic.forLanguage(language), state, Reason.INSPECT);
@ -99,7 +99,7 @@ public enum TraceSleighUtils {
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
throw new IllegalArgumentException("TracePlatform must use a Sleigh language");
}
return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE),

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.util.*;
import db.DBRecord;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
@ -38,19 +39,21 @@ import ghidra.util.database.DBObjectColumn;
import ghidra.util.database.annot.*;
import ghidra.util.exception.DuplicateNameException;
@DBAnnotatedObjectInfo(version = 0)
@DBAnnotatedObjectInfo(version = 1)
public class DBTraceBreakpoint
extends AbstractDBTraceAddressSnapRangePropertyMapData<DBTraceBreakpoint>
implements TraceBreakpoint {
protected static final String TABLE_NAME = "Breakpoints";
private static final byte ENABLED_MASK = (byte) (1 << 7);
private static final byte EMU_ENABLED_MASK = (byte) (1 << 6);
static final String PATH_COLUMN_NAME = "Path";
static final String NAME_COLUMN_NAME = "Name";
static final String THREADS_COLUMN_NAME = "Threads";
static final String FLAGS_COLUMN_NAME = "Flags";
static final String COMMENT_COLUMN_NAME = "Comment";
static final String SLEIGH_COLUMN_NAME = "Sleigh";
@DBAnnotatedColumn(PATH_COLUMN_NAME)
static DBObjectColumn PATH_COLUMN;
@ -62,6 +65,8 @@ public class DBTraceBreakpoint
static DBObjectColumn FLAGS_COLUMN;
@DBAnnotatedColumn(COMMENT_COLUMN_NAME)
static DBObjectColumn COMMENT_COLUMN;
@DBAnnotatedColumn(SLEIGH_COLUMN_NAME)
static DBObjectColumn SLEIGH_COLUMN;
protected static String tableName(AddressSpace space, long threadKey) {
return DBTraceUtils.tableName(TABLE_NAME, space, threadKey, 0);
@ -77,10 +82,13 @@ public class DBTraceBreakpoint
private byte flagsByte;
@DBAnnotatedField(column = COMMENT_COLUMN_NAME)
private String comment;
@DBAnnotatedField(column = SLEIGH_COLUMN_NAME)
private String emuSleigh;
private final Set<TraceBreakpointKind> kinds = EnumSet.noneOf(TraceBreakpointKind.class);
private final Set<TraceBreakpointKind> kindsView = Collections.unmodifiableSet(kinds);
private boolean enabled;
private boolean emuEnabled;
protected final DBTraceBreakpointSpace space;
@ -108,7 +116,7 @@ public class DBTraceBreakpoint
}
}
enabled = (flagsByte & ENABLED_MASK) != 0;
// Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because doFresh");
emuEnabled = (flagsByte & EMU_ENABLED_MASK) != 0;
}
@Override
@ -127,7 +135,8 @@ public class DBTraceBreakpoint
}
public void set(String path, String name, Collection<TraceThread> threads,
Collection<TraceBreakpointKind> kinds, boolean enabled, String comment) {
Collection<TraceBreakpointKind> kinds, boolean enabled, boolean emuEnabled,
String comment) {
// TODO: Check that the threads exist and that each's lifespan covers the breakpoint's
// TODO: This would require additional validation any time those are updated
// TODO: For efficiency, would also require index of breakpoints by thread
@ -150,9 +159,13 @@ public class DBTraceBreakpoint
if (enabled) {
this.flagsByte |= ENABLED_MASK;
}
if (emuEnabled) {
this.flagsByte |= EMU_ENABLED_MASK;
}
this.comment = comment;
update(PATH_COLUMN, NAME_COLUMN, THREADS_COLUMN, FLAGS_COLUMN, COMMENT_COLUMN);
this.enabled = enabled;
this.emuEnabled = emuEnabled;
// Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because set");
}
@ -360,6 +373,17 @@ public class DBTraceBreakpoint
update(FLAGS_COLUMN);
}
protected void doSetEmuEnabled(boolean emuEnabled) {
this.emuEnabled = emuEnabled;
if (emuEnabled) {
flagsByte |= EMU_ENABLED_MASK;
}
else {
flagsByte &= ~EMU_ENABLED_MASK;
}
update(FLAGS_COLUMN);
}
protected void doSetKinds(Collection<TraceBreakpointKind> kinds) {
for (TraceBreakpointKind k : TraceBreakpointKind.values()) {
if (kinds.contains(k)) {
@ -391,6 +415,23 @@ public class DBTraceBreakpoint
}
}
@Override
public void setEmuEnabled(boolean enabled) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
doSetEmuEnabled(enabled);
}
space.trace.setChanged(new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED,
space, this));
}
@Override
public boolean isEmuEnabled(long snap) {
// NB. Only object mode support per-snap emu-enablement
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return emuEnabled;
}
}
@Override
public void setKinds(Collection<TraceBreakpointKind> kinds) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
@ -424,6 +465,29 @@ public class DBTraceBreakpoint
}
}
@Override
public void setEmuSleigh(String emuSleigh) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
if (emuSleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(emuSleigh)) {
this.emuSleigh = null;
}
else {
this.emuSleigh = emuSleigh.trim();
}
update(SLEIGH_COLUMN);
}
space.trace.setChanged(new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED,
space, this));
}
@Override
public String getEmuSleigh() {
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return emuSleigh == null || emuSleigh.isBlank() ? SleighUtils.UNCONDITIONAL_BREAK
: emuSleigh;
}
}
@Override
public void delete() {
space.deleteBreakpoint(this);

View file

@ -100,7 +100,7 @@ public class DBTraceBreakpointSpace implements DBTraceSpaceBased {
}
DBTraceBreakpoint breakpoint =
breakpointMapSpace.put(new ImmutableTraceAddressSnapRange(range, lifespan), null);
breakpoint.set(path, path, threads, kinds, enabled, comment);
breakpoint.set(path, path, threads, kinds, enabled, true, comment);
trace.setChanged(
new TraceChangeRecord<>(TraceBreakpointChangeType.ADDED, this, breakpoint));
return breakpoint;

View file

@ -15,13 +15,13 @@
*/
package ghidra.trace.database.breakpoint;
import java.util.Collection;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathMatcher;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.database.target.*;
@ -208,6 +208,11 @@ public class DBTraceObjectBreakpointLocation
object.setValue(Lifespan.span(snap, getClearedSnap()),
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
}
Set<TraceBreakpointKind> asSet =
kinds instanceof Set<TraceBreakpointKind> yes ? yes : Set.copyOf(kinds);
if (!Objects.equals(asSet, getKinds())) {
this.setKinds(Lifespan.span(snap, getClearedSnap()), asSet);
}
return this;
}
}
@ -297,8 +302,55 @@ public class DBTraceObjectBreakpointLocation
@Override
public String getComment() {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT,
String.class, "");
try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT,
String.class, "");
}
}
@Override
public void setEmuEnabled(Lifespan lifespan, boolean emuEnabled) {
object.setValue(lifespan, KEY_EMU_ENABLED, emuEnabled ? null : false);
}
@Override
public void setEmuEnabled(boolean emuEnabled) {
try (LockHold hold = object.getTrace().lockWrite()) {
setEmuEnabled(getLifespan(), emuEnabled);
}
}
@Override
public boolean isEmuEnabled(long snap) {
try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_EMU_ENABLED,
Boolean.class, true);
}
}
@Override
public void setEmuSleigh(Lifespan lifespan, String sleigh) {
if (sleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh)) {
object.setValue(lifespan, KEY_EMU_SLEIGH, null);
}
else {
object.setValue(lifespan, KEY_EMU_SLEIGH, sleigh.trim());
}
}
@Override
public void setEmuSleigh(String sleigh) {
try (LockHold hold = object.getTrace().lockWrite()) {
setEmuSleigh(getLifespan(), sleigh);
}
}
@Override
public String getEmuSleigh() {
try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_EMU_SLEIGH,
String.class, SleighUtils.UNCONDITIONAL_BREAK);
}
}
@Override

View file

@ -131,7 +131,8 @@ public class DBTraceObjectBreakpointSpec
@Override
public void setEnabled(boolean enabled) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME,
enabled ? true : null);
}
}
@ -190,6 +191,26 @@ public class DBTraceObjectBreakpointSpec
throw new UnsupportedOperationException("Ask a location instead");
}
@Override
public void setEmuEnabled(boolean enabled) {
throw new UnsupportedOperationException("Set on a location instead");
}
@Override
public boolean isEmuEnabled(long snap) {
throw new UnsupportedOperationException("Ask a location instead");
}
@Override
public void setEmuSleigh(String sleigh) {
throw new UnsupportedOperationException("Set on a location instead");
}
@Override
public String getEmuSleigh() {
throw new UnsupportedOperationException("Ask a location instead");
}
@Override
public void delete() {
try (LockHold hold = object.getTrace().lockWrite()) {

View file

@ -53,6 +53,16 @@ import resources.ResourceManager;
public interface Trace extends DataTypeManagerDomainObject {
ImageIcon TRACE_ICON = ResourceManager.loadImage("images/video-x-generic16.png");
/**
* TEMPORARY: An a/b switch while both table- (legacy) and object-mode traces are supported
*
* @param trace the trace, or null
* @return true if the trace is non-null and has no root schema
*/
public static boolean isLegacy(Trace trace) {
return trace != null && trace.getObjectManager().getRootSchema() == null;
}
public static final class TraceObjectChangeType<T, U>
extends DefaultTraceChangeType<T, U> {
/**

View file

@ -28,7 +28,6 @@ import ghidra.util.exception.DuplicateNameException;
* A breakpoint in a trace
*/
public interface TraceBreakpoint extends TraceUniqueObject {
/**
* Get the trace containing this breakpoint
*
@ -161,10 +160,30 @@ public interface TraceBreakpoint extends TraceUniqueObject {
/**
* Check whether this breakpoint is enabled or disabled at the given snap
*
* @param snap the snap
* @return true if enabled, false if disabled
*/
boolean isEnabled(long snap);
/**
* Set whether this breakpoint is enabled or disabled for emulation
*
* <p>
* This change applies to the entire lifespan of the record. It's not intended to record a
* history, but to toggle the breakpoint in the integrated emulator.
*
* @param enabled true if enabled, false if disabled
*/
void setEmuEnabled(boolean enabled);
/**
* Check whether this breakpoint is enabled or disabled for emulation at the given snap
*
* @param snap the snap
* @return true if enabled, false if disabled
*/
boolean isEmuEnabled(long snap);
/**
* Set the kinds included in this breakpoint
*
@ -213,6 +232,36 @@ public interface TraceBreakpoint extends TraceUniqueObject {
*/
String getComment();
/**
* Set Sleigh source to replace the breakpointed instruction in emulation
*
* <p>
* The default is simply "<code>{@link PcodeEmulationLibrary#emu_swi() emu_swi()};
* {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};</code>", effectively a
* non-conditional breakpoint followed by execution of the actual instruction. Modifying this
* allows clients to create conditional breakpoints or simply override or inject additional
* logic into an emulated target.
*
* <p>
* <b>NOTE:</b> This current has no effect on access breakpoints, but only execution
* breakpoints.
*
* <p>
* If the specified source fails to compile during emulator set-up, this will fall back to
* {@link PcodeEmulationLibrary#emu_err
*
* @see #DEFAULT_SLEIGH
* @param sleigh the Sleigh source
*/
void setEmuSleigh(String sleigh);
/**
* Get the Sleigh source that replaces the breakpointed instruction in emulation
*
* @return the Sleigh source
*/
String getEmuSleigh();
/**
* Delete this breakpoint from the trace
*/

View file

@ -37,6 +37,8 @@ public enum TraceBreakpointKind {
HW_EXECUTE(1 << 2),
SW_EXECUTE(1 << 3);
public static final int COUNT = values().length;
public static class TraceBreakpointKindSet extends AbstractSetDecorator<TraceBreakpointKind> {
public static final TraceBreakpointKindSet SW_EXECUTE = of(TraceBreakpointKind.SW_EXECUTE);
public static final TraceBreakpointKindSet HW_EXECUTE = of(TraceBreakpointKind.HW_EXECUTE);

View file

@ -32,9 +32,12 @@ import ghidra.util.exception.DuplicateNameException;
TargetObject.DISPLAY_ATTRIBUTE_NAME,
TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME,
TraceObjectBreakpointLocation.KEY_COMMENT,
TraceObjectBreakpointLocation.KEY_EMU_ENABLED,
})
public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObjectInterface {
String KEY_COMMENT = "_comment";
String KEY_EMU_ENABLED = "_emu_enabled";
String KEY_EMU_SLEIGH = "_emu_sleigh";
TraceObjectBreakpointSpec getSpecification();
@ -48,5 +51,9 @@ public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObj
void setEnabled(Lifespan lifespan, boolean enabled);
void setEmuEnabled(Lifespan lifespan, boolean emuEnabled);
void setEmuSleigh(Lifespan lifespan, String sleigh);
void setComment(Lifespan lifespan, String comment);
}

View file

@ -132,14 +132,16 @@ public interface Scheduler {
}
catch (PcodeExecutionException e) {
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
if (emuThread.getInstruction() == null) {
PcodeFrame frame = emuThread.getFrame();
if (frame == 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
frame.stepBack();
int count = frame.count();
if (count == 0) {
// If we've decoded, but could not execute the first op, just drop the p-code steps
emuThread.dropInstruction();
return new RecordRunResult(completedSteps, e);
}
// The +1 accounts for the decode step

View file

@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule;
import java.util.*;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeMachine.SwiMode;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
@ -25,9 +26,18 @@ import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A sequence of emulator stepping commands, essentially comprising a "point in time."
*/
public class TraceSchedule implements Comparable<TraceSchedule> {
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
/**
* Create a schedule that consists solely of a snapshot
*
* @param snap the snapshot key
* @return the schedule
*/
public static final TraceSchedule snap(long snap) {
return new TraceSchedule(snap, new Sequence(), new Sequence());
}
@ -337,11 +347,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
*/
public void execute(Trace trace, PcodeMachine<?> machine, TaskMonitor monitor)
throws CancelledException {
machine.setSoftwareInterruptMode(SwiMode.IGNORE_ALL);
TraceThread lastThread = getEventThread(trace);
lastThread =
steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread =
pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
lastThread = steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
}
/**
@ -380,6 +389,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
TaskMonitor monitor) throws CancelledException {
TraceThread lastThread = position.getLastThread(trace);
Sequence remains = steps.relativize(position.steps);
machine.setSoftwareInterruptMode(SwiMode.IGNORE_ALL);
if (remains.isNop()) {
Sequence pRemains = this.pSteps.relativize(position.pSteps);
lastThread =
@ -388,8 +398,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
else {
lastThread =
remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread =
pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
}
}

View file

@ -46,8 +46,8 @@ public class AbstractTracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegr
* <p>
* This creates a relatively bare-bones trace with initial state for testing trace
* emulation/interpolation. It adds ".text" and "stack" regions, creates a thread, assembles
* given instructions, and then executes the given SLEIGH source (in the context of the new
* thread) to finish initializing the trace. Note, though given first, the SLEIGH is executed
* given instructions, and then executes the given Sleigh source (in the context of the new
* thread) to finish initializing the trace. Note, though given first, the Sleigh is executed
* after assembly. Thus, it can be used to modify the resulting machine code by modifying the
* memory where it was assembled.
*

View file

@ -312,7 +312,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
* This may not reflect the semantics of an actual processor in these situations, since they may
* have instruction caching. Emulating such semantics is TODO, if at all. NB. This also tests
* that PC-relative addressing works, since internally the emulator advances the counter after
* execution of each instruction. Addressing is computed by the SLEIGH instruction parser and
* execution of each instruction. Addressing is computed by the Sleigh instruction parser and
* encoded as a constant deref in the p-code.
*/
@Test