GP-4231: Fix emulator step from cache after interrupt

This commit is contained in:
Dan 2024-10-17 08:48:03 -04:00
parent f292bad0ed
commit 37e73f1885
12 changed files with 393 additions and 101 deletions

View File

@ -43,7 +43,8 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncLazyMap; import ghidra.async.AsyncLazyMap;
import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.emulation.*; import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory;
import ghidra.debug.api.emulation.DebuggerPcodeMachine;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
@ -781,7 +782,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
TraceThread eventThread = key.time.getEventThread(key.trace); TraceThread eventThread = key.time.getEventThread(key.trace);
be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP); be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP);
RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor); RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor);
if (result.schedule().hasSteps()) {
key = new CacheKey(key.platform, key.time.dropPSteps().advanced(result.schedule()));
}
else {
key = new CacheKey(key.platform, key.time.advanced(result.schedule())); key = new CacheKey(key.platform, key.time.advanced(result.schedule()));
}
Msg.info(this, "Stopped emulation at " + key.time); Msg.info(this, "Stopped emulation at " + key.time);
TraceSnapshot destSnap = writeToScratch(key, be.ce); TraceSnapshot destSnap = writeToScratch(key, be.ce);
cacheEmulator(key, be.ce); cacheEmulator(key, be.ce);

View File

@ -38,11 +38,11 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug
import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.emulation.DebuggerPcodeMachine;
import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.DecodePcodeExecutionException; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.InterruptPcodeExecutionException;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
@ -489,25 +489,180 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe
trace.getBreakpointManager() trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread), .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
trace.getBreakpointManager() TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread), .addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
// Force "partial instruction"
tb.setEmuSleigh("""
r1 = 0xbeef;
emu_swi();
emu_exec_decoded();
""");
trace.getBreakpointManager() trace.getBreakpointManager()
.addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread), .addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
} }
assertEquals(0, emulationPlugin.cache.size());
// This is already testing if the one set at the entry is ignored // This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread)); TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1), result1.schedule()); assertEquals(TraceSchedule.snap(0)
.steppedForward(thread, 1)
.steppedPcodeForward(thread, 2),
result1.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException); assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// Save this for comparison later
DebuggerPcodeMachine<?> emu = Unique.assertOne(emulationPlugin.cache.values()).emulator();
// This will test if the one just hit gets ignored // This will test if the one just hit gets ignored
EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
result1.schedule(), monitor, Scheduler.oneThread(thread)); result1.schedule(), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.snap(0).steppedForward(thread, 2), result2.schedule()); assertEquals(TraceSchedule.snap(0).steppedForward(thread, 2), result2.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException); assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// For efficiency, esp. after a long run, make sure we used the same emulator
assertSame(emu, Unique.assertOne(emulationPlugin.cache.values()).emulator());
}
@Test
public void testStepAfterExecutionBreakpoint() throws Exception {
createProgram();
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x00400000);
Address addrI1;
try (Transaction tx = program.openTransaction("Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
InstructionIterator ii = asm.assemble(addrText,
"mov r0, r0",
"mov r0, r1",
"mov r2, r0");
ii.next(); // addrText
addrI1 = ii.next().getMinAddress();
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
try (Transaction tx = trace.openTransaction("Add breakpoint")) {
trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
// Force "partial instruction"
tb.setEmuSleigh("""
r1 = 0xbeef;
emu_swi();
emu_exec_decoded();
""");
}
assertEquals(0, emulationPlugin.cache.size());
// This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.snap(0)
.steppedForward(thread, 1)
.steppedPcodeForward(thread, 2),
result1.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// Save this for comparison later
DebuggerPcodeMachine<?> emu = Unique.assertOne(emulationPlugin.cache.values()).emulator();
// Now, step it forward to complete the instruction
emulationPlugin.emulate(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0).steppedForward(thread, 2), monitor);
// For efficiency, esp. after a long run, make sure we used the same emulator
assertSame(emu, Unique.assertOne(emulationPlugin.cache.values()).emulator());
}
@Test
public void testStuckAtUserop() throws Exception {
createProgram();
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x00400000);
Address addrI1;
try (Transaction tx = program.openTransaction("Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
InstructionIterator ii = asm.assemble(addrText,
"mov r0, r0",
"mov r0, r1",
"mov r2, r0");
ii.next(); // addrText
addrI1 = ii.next().getMinAddress();
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
try (Transaction tx = trace.openTransaction("Add breakpoint")) {
TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
// Force "partial instruction"
tb.setEmuSleigh("""
r1 = 0xbeef;
pcodeop_one(r1);
emu_exec_decoded();
""");
}
TraceSchedule stuck = TraceSchedule.snap(0)
.steppedForward(thread, 1)
.steppedPcodeForward(thread, 2);
assertEquals(0, emulationPlugin.cache.size());
// This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(stuck, result1.schedule());
assertTrue(result1.error() instanceof PcodeExecutionException);
// Save this for comparison later
DebuggerPcodeMachine<?> emu = Unique.assertOne(emulationPlugin.cache.values()).emulator();
// We shouldn't get any further
EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
result1.schedule(), monitor, Scheduler.oneThread(thread));
assertEquals(stuck, result2.schedule());
assertTrue(result1.error() instanceof PcodeExecutionException);
// For efficiency, esp. after a long run, make sure we used the same emulator
assertSame(emu, Unique.assertOne(emulationPlugin.cache.values()).emulator());
} }
@Test @Test
@ -817,6 +972,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe
newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000)); newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000));
newTraceThread.setName("MyThread"); newTraceThread.setName("MyThread");
@SuppressWarnings("unused")
PcodeThread<byte[]> newEmuThread = emulator.newThread(newTraceThread.getPath()); PcodeThread<byte[]> newEmuThread = emulator.newThread(newTraceThread.getPath());
} }
} }

View File

@ -232,6 +232,11 @@ public class PatchStep implements Step {
return StepType.PATCH; return StepType.PATCH;
} }
@Override
public long getSkipCount() {
return 0;
}
@Override @Override
public boolean isNop() { public boolean isNop() {
// TODO: If parsing beforehand, base on number of ops // TODO: If parsing beforehand, base on number of ops
@ -307,7 +312,7 @@ public class PatchStep implements Step {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) public void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
emuThread.stepPatch(sleigh); emuThread.stepPatch(sleigh);
} }

View File

@ -81,14 +81,17 @@ public interface Scheduler {
/** /**
* The result of running a machine * The result of running a machine
*
* @param schedule the actual schedule executed
* @param error if applicable, the error that interrupted execution
*/ */
record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult { record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult {}
}
/** /**
* Get the next step to schedule * Get the next step to schedule
* *
* @return the (instruction-level) thread and tick count * @param trace the trace being emulated
* @return the thread and (instruction-level) tick count
*/ */
TickStep nextSlice(Trace trace); TickStep nextSlice(Trace trace);
@ -118,12 +121,17 @@ public interface Scheduler {
TickStep slice = nextSlice(trace); TickStep slice = nextSlice(trace);
eventThread = slice.getThread(tm, eventThread); eventThread = slice.getThread(tm, eventThread);
emuThread = machine.getThread(eventThread.getPath(), true); emuThread = machine.getThread(eventThread.getPath(), true);
if (emuThread.getFrame() != null) { long ticksLeft = slice.tickCount;
if (ticksLeft > 0 && emuThread.getFrame() != null) {
monitor.checkCancelled();
emuThread.finishInstruction(); emuThread.finishInstruction();
ticksLeft--;
completedTicks++;
} }
for (int i = 0; i < slice.tickCount; i++) { while (ticksLeft > 0) {
monitor.checkCancelled(); monitor.checkCancelled();
emuThread.stepInstruction(); emuThread.stepInstruction();
ticksLeft--;
completedTicks++; completedTicks++;
} }
completedSteps = completedSteps.steppedForward(eventThread, completedTicks); completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
@ -134,11 +142,11 @@ public interface Scheduler {
completedSteps = completedSteps.steppedForward(eventThread, completedTicks); completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
PcodeFrame frame = emuThread.getFrame(); PcodeFrame frame = emuThread.getFrame();
if (frame == null) { if (frame == null) {
return new RecordRunResult(completedSteps, e); return new RecordRunResult(completedSteps.assumeRecorded(), e);
} }
// Rewind one so stepping retries the op causing the error // Rewind one so stepping retries the op causing the error
frame.stepBack(); frame.stepBack();
int count = frame.count(); int count = frame.resetCount();
if (count == 0) { if (count == 0) {
// If we've decoded, but could not execute the first op, just drop the p-code steps // If we've decoded, but could not execute the first op, just drop the p-code steps
emuThread.dropInstruction(); emuThread.dropInstruction();
@ -146,11 +154,11 @@ public interface Scheduler {
} }
// The +1 accounts for the decode step // The +1 accounts for the decode step
return new RecordRunResult( return new RecordRunResult(
completedSteps.steppedPcodeForward(eventThread, count + 1), e); completedSteps.steppedPcodeForward(eventThread, count + 1).assumeRecorded(), e);
} }
catch (CancelledException e) { catch (CancelledException e) {
return new RecordRunResult( return new RecordRunResult(
completedSteps.steppedForward(eventThread, completedTicks), e); completedSteps.steppedForward(eventThread, completedTicks).assumeRecorded(), e);
} }
} }
} }

View File

@ -21,6 +21,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@ -240,7 +241,7 @@ public class Sequence implements Comparable<Sequence> {
} }
/** /**
* Richly compare to sequences * Richly compare two sequences
* *
* <p> * <p>
* The result indicates not only which is "less" or "greater" than the other, but also indicates * The result indicates not only which is "less" or "greater" than the other, but also indicates
@ -355,6 +356,14 @@ public class Sequence implements Comparable<Sequence> {
return count; return count;
} }
public long totalSkipCount() {
long count = 0;
for (Step step : steps) {
count += step.getSkipCount();
}
return count;
}
/** /**
* Compute to total number of patches specified * Compute to total number of patches specified
* *
@ -440,4 +449,25 @@ public class Sequence implements Comparable<Sequence> {
} }
return thread; return thread;
} }
/**
* Check if the first instruction step is actually to finish an incomplete instruction.
*
* @param thread the thread whose instruction to potentially finish
* @param machine a machine bound to the trace whose current state reflects the given position
* @return if a finish was performed, this sequence with one initial step removed, i.e., a
* sequence representing the steps remaining
*/
Sequence checkFinish(TraceThread thread, PcodeMachine<?> machine) {
PcodeThread<?> emuThread = machine.getThread(thread.getPath(), true);
if (emuThread.getFrame() == null) {
return this;
}
Sequence result = new Sequence(new ArrayList<>(steps));
emuThread.finishInstruction();
if (result.steps.get(0).rewind(1) == 0) {
result.steps.remove(0);
}
return result;
}
} }

View File

@ -48,6 +48,11 @@ public class SkipStep extends AbstractStep {
return StepType.SKIP; return StepType.SKIP;
} }
@Override
public long getSkipCount() {
return tickCount;
}
@Override @Override
protected String toStringStepPart() { protected String toStringStepPart() {
return String.format("s%d", tickCount); return String.format("s%d", tickCount);
@ -66,7 +71,7 @@ public class SkipStep extends AbstractStep {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) public void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
for (int i = 0; i < tickCount; i++) { for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1); monitor.incrementProgress(1);

View File

@ -116,6 +116,8 @@ public interface Step extends Comparable<Step> {
long getTickCount(); long getTickCount();
long getSkipCount();
long getPatchCount(); long getPatchCount();
/** /**
@ -147,7 +149,7 @@ public interface Step extends Comparable<Step> {
* method sets the count to 0 and returns the (positive) difference, indicating this step should * method sets the count to 0 and returns the (positive) difference, indicating this step should
* be removed from the sequence, and the remaining steps rewound from the preceding step. * be removed from the sequence, and the remaining steps rewound from the preceding step.
* *
* @param steps the count to rewind * @param count the count to rewind
* @return the number of steps remaining * @return the number of steps remaining
*/ */
long rewind(long count); long rewind(long count);
@ -155,7 +157,7 @@ public interface Step extends Comparable<Step> {
/** /**
* Richly compare this step to another * Richly compare this step to another
* *
* @param step the object of comparison (this being the subject) * @param that the object of comparison (this being the subject)
* @return a result describing the relationship from subject to object * @return a result describing the relationship from subject to object
*/ */
CompareResult compareStep(Step that); CompareResult compareStep(Step that);
@ -183,7 +185,7 @@ public interface Step extends Comparable<Step> {
return thread; return thread;
} }
<T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException; throws CancelledException;
long coalescePatches(Language language, List<Step> steps); long coalescePatches(Language language, List<Step> steps);

View File

@ -48,6 +48,11 @@ public class TickStep extends AbstractStep {
return StepType.TICK; return StepType.TICK;
} }
@Override
public long getSkipCount() {
return 0;
}
@Override @Override
protected String toStringStepPart() { protected String toStringStepPart() {
return Long.toString(tickCount); return Long.toString(tickCount);
@ -66,7 +71,7 @@ public class TickStep extends AbstractStep {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) public void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
for (int i = 0; i < tickCount; i++) { for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1); monitor.incrementProgress(1);

View File

@ -39,7 +39,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* @return the schedule * @return the schedule
*/ */
public static final TraceSchedule snap(long snap) { public static final TraceSchedule snap(long snap) {
return new TraceSchedule(snap, new Sequence(), new Sequence()); return new TraceSchedule(snap, new Sequence(), new Sequence(), Source.RECORD);
} }
private static final String PARSE_ERR_MSG = private static final String PARSE_ERR_MSG =
@ -55,9 +55,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* threads forward, and/or patching machine state. * threads forward, and/or patching machine state.
* *
* @param spec the string specification * @param spec the string specification
* @param source the presumed source of the schedule
* @return the parsed schedule * @return the parsed schedule
*/ */
public static TraceSchedule parse(String spec) { public static TraceSchedule parse(String spec, Source source) {
String[] parts = spec.split(":", 2); String[] parts = spec.split(":", 2);
if (parts.length > 2) { if (parts.length > 2) {
throw new AssertionError(); throw new AssertionError();
@ -98,24 +99,76 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
ticks = new Sequence(); ticks = new Sequence();
pTicks = new Sequence(); pTicks = new Sequence();
} }
return new TraceSchedule(snap, ticks, pTicks); return new TraceSchedule(snap, ticks, pTicks, source);
}
/**
* As in {@link #parse(String, Source)}, but assumed abnormal
*
* @param spec the string specification
* @return the parsed schedule
*/
public static TraceSchedule parse(String spec) {
return parse(spec, Source.INPUT);
}
public enum Source {
/**
* The schedule comes from the user or some source other than a recorded emulation schedule.
*/
INPUT {
@Override
Source adjust(long pTickCount, long pPatchCount, long pSkipCount) {
// The first tick is decode, so <= 1 tick is definitely not a full instruction
return pTickCount <= 1 && pPatchCount == 0 && pSkipCount == 0 ? RECORD : INPUT;
}
},
/**
* The schedule comes from recording actual emulation.
*
* <p>
* Specifically, the p-code steps must be known not to exceed one instruction.
*/
RECORD {
@Override
Source adjust(long pTickCount, long pPatchCount, long pSkipCount) {
return pPatchCount == 0 && pSkipCount == 0 ? RECORD : INPUT;
}
};
abstract Source adjust(long tickCount, long patchCount, long skipCount);
} }
private final long snap; private final long snap;
private final Sequence steps; private final Sequence steps;
private final Sequence pSteps; private final Sequence pSteps;
private final Source source;
/** /**
* Construct the given schedule * Construct the given schedule
* *
* @param snap the initial trace snapshot * @param snap the initial trace snapshot
* @param steps the step sequence * @param steps the step sequence
* @param pSteps the of p-code step sequence * @param pSteps the p-code step sequence
* @param source if the p-code steps are known not to exceed one instruction
*/ */
public TraceSchedule(long snap, Sequence steps, Sequence pSteps) { public TraceSchedule(long snap, Sequence steps, Sequence pSteps, Source source) {
this.snap = snap; this.snap = snap;
this.steps = steps; this.steps = steps;
this.pSteps = pSteps; this.pSteps = pSteps;
this.source = source.adjust(pSteps.totalTickCount(), pSteps.totalPatchCount(),
pSteps.totalSkipCount());
}
/**
* Construct the given schedule, but assumed abnormal
*
* @param snap the initial trace snapshot
* @param steps the step sequence
* @param pSteps the p-code step sequence
*/
public TraceSchedule(long snap, Sequence steps, Sequence pSteps) {
this(snap, steps, pSteps, Source.INPUT);
} }
@Override @Override
@ -152,33 +205,17 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
} }
result = this.steps.compareSeq(that.steps); result = this.steps.compareSeq(that.steps);
switch (result) {
case UNREL_LT:
case UNREL_GT:
return result;
case REL_LT:
if (this.pSteps.isNop()) {
return CompareResult.REL_LT;
}
else {
return CompareResult.UNREL_LT;
}
case REL_GT:
if (that.pSteps.isNop()) {
return CompareResult.REL_GT;
}
else {
return CompareResult.UNREL_GT;
}
default: // EQUALS, compare pSteps
}
result = this.pSteps.compareSeq(that.pSteps); return switch (result) {
if (result != CompareResult.EQUALS) { case UNREL_LT, UNREL_GT -> result;
return result; case REL_LT -> (this.pSteps.isNop() || this.source == Source.RECORD)
} ? CompareResult.REL_LT
: CompareResult.UNREL_LT;
return CompareResult.EQUALS; case REL_GT -> (that.pSteps.isNop() || that.source == Source.RECORD)
? CompareResult.REL_GT
: CompareResult.UNREL_GT;
default -> this.pSteps.compareSeq(that.pSteps);
};
} }
@Override @Override
@ -222,10 +259,19 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
return steps.isNop() && pSteps.isNop(); return steps.isNop() && pSteps.isNop();
} }
/**
* Check if this schedule has instruction steps
*
* @return true if this indicates at least one instruction step
*/
public boolean hasSteps() {
return !steps.isNop();
}
/** /**
* Get the source snapshot * Get the source snapshot
* *
* @return * @return the snapshot key
*/ */
public long getSnap() { public long getSnap() {
return snap; return snap;
@ -234,7 +280,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
/** /**
* Get the last thread key stepped by this schedule * Get the last thread key stepped by this schedule
* *
* @return * @return the thread key
*/ */
public long getLastThreadKey() { public long getLastThreadKey() {
long last = pSteps.getLastThreadKey(); long last = pSteps.getLastThreadKey();
@ -396,6 +442,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor); pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
} }
else { else {
remains = remains.checkFinish(lastThread, machine);
lastThread = lastThread =
remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor); 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);
@ -417,7 +464,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule steppedForward(TraceThread thread, long tickCount) { public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount)); steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount));
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence(), Source.RECORD);
} }
/** /**
@ -430,7 +477,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule skippedForward(TraceThread thread, long tickCount) { public TraceSchedule skippedForward(TraceThread thread, long tickCount) {
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.advance(new SkipStep(thread == null ? -1 : thread.getKey(), tickCount)); steps.advance(new SkipStep(thread == null ? -1 : thread.getKey(), tickCount));
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence(), Source.RECORD);
} }
protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) { protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) {
@ -454,7 +501,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
} }
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.rewind(tickCount); steps.rewind(tickCount);
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence(), Source.RECORD);
} }
/** /**
@ -486,7 +533,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) { public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount)); pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount));
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
/** /**
@ -499,7 +546,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule skippedPcodeForward(TraceThread thread, int pTickCount) { public TraceSchedule skippedPcodeForward(TraceThread thread, int pTickCount) {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new SkipStep(thread == null ? -1 : thread.getKey(), pTickCount)); pTicks.advance(new SkipStep(thread == null ? -1 : thread.getKey(), pTickCount));
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
/** /**
@ -519,7 +566,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
} }
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.rewind(pStepCount); pTicks.rewind(pStepCount);
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
private long keyOf(TraceThread thread) { private long keyOf(TraceThread thread) {
@ -530,6 +577,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* Returns the equivalent of executing this schedule then performing a given patch * Returns the equivalent of executing this schedule then performing a given patch
* *
* @param thread the thread context for the patch; cannot be null * @param thread the thread context for the patch; cannot be null
* @param language the sleigh language for the patch
* @param sleigh a single line of sleigh, excluding the terminating semicolon. * @param sleigh a single line of sleigh, excluding the terminating semicolon.
* @return the resulting schedule * @return the resulting schedule
*/ */
@ -538,19 +586,20 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new PatchStep(thread.getKey(), sleigh)); pTicks.advance(new PatchStep(thread.getKey(), sleigh));
pTicks.coalescePatches(language); pTicks.coalescePatches(language);
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
Sequence ticks = this.steps.clone(); Sequence ticks = this.steps.clone();
ticks.advance(new PatchStep(keyOf(thread), sleigh)); ticks.advance(new PatchStep(keyOf(thread), sleigh));
ticks.coalescePatches(language); ticks.coalescePatches(language);
return new TraceSchedule(snap, ticks, new Sequence()); return new TraceSchedule(snap, ticks, new Sequence(), Source.RECORD);
} }
/** /**
* Returns the equivalent of executing this schedule then performing the given patches * Returns the equivalent of executing this schedule then performing the given patches
* *
* @param thread the thread context for the patch; cannot be null * @param thread the thread context for the patch; cannot be null
* @param sleigh the lines of sleigh, excluding the terminating semicolons. * @param language the sleigh language for the patch
* @param sleigh the lines of sleigh, excluding the terminating semicolons
* @return the resulting schedule * @return the resulting schedule
*/ */
public TraceSchedule patched(TraceThread thread, Language language, List<String> sleigh) { public TraceSchedule patched(TraceThread thread, Language language, List<String> sleigh) {
@ -560,14 +609,14 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
pTicks.advance(new PatchStep(thread.getKey(), line)); pTicks.advance(new PatchStep(thread.getKey(), line));
} }
pTicks.coalescePatches(language); pTicks.coalescePatches(language);
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
Sequence ticks = this.steps.clone(); Sequence ticks = this.steps.clone();
for (String line : sleigh) { for (String line : sleigh) {
ticks.advance(new PatchStep(thread.getKey(), line)); ticks.advance(new PatchStep(thread.getKey(), line));
} }
ticks.coalescePatches(language); ticks.coalescePatches(language);
return new TraceSchedule(snap, ticks, new Sequence()); return new TraceSchedule(snap, ticks, new Sequence(), Source.RECORD);
} }
/** /**
@ -575,7 +624,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* *
* <p> * <p>
* This operation cannot be used to append instruction steps after p-code steps. Thus, if this * This operation cannot be used to append instruction steps after p-code steps. Thus, if this
* schedule contains any p-code steps and {@code} next has instruction steps, an error will be * schedule contains any p-code steps and {@code next} has instruction steps, an error will be
* *
* @param next the schedule to append. Its snap is ignored. * @param next the schedule to append. Its snap is ignored.
* @return the complete schedule * @return the complete schedule
@ -586,16 +635,25 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
if (this.pSteps.isNop()) { if (this.pSteps.isNop()) {
Sequence ticks = this.steps.clone(); Sequence ticks = this.steps.clone();
ticks.advance(next.steps); ticks.advance(next.steps);
return new TraceSchedule(this.snap, ticks, next.pSteps.clone()); return new TraceSchedule(this.snap, ticks, next.pSteps.clone(), next.source);
} }
else if (next.steps.isNop()) { else if (next.steps.isNop()) {
Sequence pTicks = this.steps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(next.pSteps); pTicks.advance(next.pSteps);
return new TraceSchedule(this.snap, this.steps.clone(), pTicks); return new TraceSchedule(this.snap, this.steps.clone(), pTicks, Source.INPUT);
} }
throw new IllegalArgumentException("Cannot have instructions steps following p-code steps"); throw new IllegalArgumentException("Cannot have instructions steps following p-code steps");
} }
/**
* Drop the p-code steps
*
* @return the schedule without ops
*/
public TraceSchedule dropPSteps() {
return new TraceSchedule(this.snap, this.steps, new Sequence());
}
/** /**
* Get the threads involved in the schedule * Get the threads involved in the schedule
* *
@ -611,4 +669,8 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
result.remove(null); result.remove(null);
return result; return result;
} }
public TraceSchedule assumeRecorded() {
return new TraceSchedule(snap, steps, pSteps, Source.RECORD);
}
} }

View File

@ -224,19 +224,30 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
expectU("0:10", "1:10"); expectU("0:10", "1:10");
expectU("0:t0-10", "0:t1-10"); expectU("0:t0-10", "0:t1-10");
// We don't know how many p-code steps complete an instruction step // We don't know how many p-code steps complete an instruction step
expectU("0:t0-10.1", "0:t0-11"); // But we need at least 2 to actually enter the instruction
expectU("0:t0-10.2", "0:t0-11");
expectU("0:t0-10;t1-5", "0:t0-11;t1-5"); expectU("0:t0-10;t1-5", "0:t0-11;t1-5");
expectR("0:t0-10", "0:t0-11"); expectR("0:t0-10", "0:t0-11");
expectR("0:t0-10", "0:t0-10;t1-5"); expectR("0:t0-10", "0:t0-10;t1-5");
expectR("0:t0-10", "0:t0-11;t1-5"); expectR("0:t0-10", "0:t0-11;t1-5");
expectR("0:t0-10", "0:t0-10.1"); expectR("0:t0-10", "0:t0-10.2");
expectR("0:t0-10", "0:t0-11.1"); expectR("0:t0-10", "0:t0-11.2");
expectR("0:t0-10", "0:t0-10;t1-5.1"); expectR("0:t0-10", "0:t0-10;t1-5.2");
expectR("0:t0-10", "0:t0-11;t1-5.1"); expectR("0:t0-10", "0:t0-11;t1-5.2");
expectE("0:t0-10", "0:t0-10"); expectE("0:t0-10", "0:t0-10");
expectE("0:t0-10.1", "0:t0-10.1"); expectE("0:t0-10.2", "0:t0-10.2");
}
@Test
public void testCompare2() {
TraceSchedule timeL = TraceSchedule.parse("0:t0-2.5");
TraceSchedule timeR = TraceSchedule.parse("0:t0-3");
assertEquals(CompareResult.UNREL_LT, timeL.compareSchedule(timeR));
assertEquals(CompareResult.REL_LT, timeL.assumeRecorded().compareSchedule(timeR));
assertEquals(CompareResult.UNREL_LT, timeL.compareSchedule(timeR.assumeRecorded()));
} }
public String strRelativize(String fromSpec, String toSpec) { public String strRelativize(String fromSpec, String toSpec) {

View File

@ -400,6 +400,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void stepInstruction() { public void stepInstruction() {
assertCompletedInstruction();
PcodeProgram inj = getInject(counter); PcodeProgram inj = getInject(counter);
if (inj != null) { if (inj != null) {
instruction = null; instruction = null;
@ -576,7 +577,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void executeInstruction() { public void executeInstruction() {
assertCompletedInstruction();
instruction = decoder.decodeInstruction(counter, context); instruction = decoder.decodeInstruction(counter, context);
PcodeProgram insProg = PcodeProgram.fromInstruction(instruction); PcodeProgram insProg = PcodeProgram.fromInstruction(instruction);
preExecuteInstruction(); preExecuteInstruction();

View File

@ -159,7 +159,7 @@ public class PcodeFrame {
} }
/** /**
* The number of p-code ops executed * Get and reset the number of p-code ops executed
* *
* <p> * <p>
* Contrast this to {@link #index()}, which marks the next op to be executed. This counts the * Contrast this to {@link #index()}, which marks the next op to be executed. This counts the
@ -167,7 +167,9 @@ public class PcodeFrame {
* *
* @return the count * @return the count
*/ */
public int count() { public int resetCount() {
int count = this.count;
this.count = 0;
return count; return count;
} }