From 37e73f18854aeb2043e0918bc8a5b26bbe139d5d Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:48:03 -0400 Subject: [PATCH] GP-4231: Fix emulator step from cache after interrupt --- .../DebuggerEmulationServicePlugin.java | 14 +- .../DebuggerEmulationServiceTest.java | 164 +++++++++++++++++- .../trace/model/time/schedule/PatchStep.java | 11 +- .../trace/model/time/schedule/Scheduler.java | 30 ++-- .../trace/model/time/schedule/Sequence.java | 36 +++- .../trace/model/time/schedule/SkipStep.java | 11 +- .../trace/model/time/schedule/Step.java | 12 +- .../trace/model/time/schedule/TickStep.java | 11 +- .../model/time/schedule/TraceSchedule.java | 162 +++++++++++------ .../time/schedule/TraceScheduleTest.java | 27 ++- .../ghidra/pcode/emu/DefaultPcodeThread.java | 6 +- .../java/ghidra/pcode/exec/PcodeFrame.java | 10 +- 12 files changed, 393 insertions(+), 101 deletions(-) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index a97b476824..e23e8a20be 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -4,9 +4,9 @@ * 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. @@ -43,7 +43,8 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.*; import ghidra.async.AsyncLazyMap; 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.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; @@ -781,7 +782,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm TraceThread eventThread = key.time.getEventThread(key.trace); be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP); RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor); - key = new CacheKey(key.platform, key.time.advanced(result.schedule())); + 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())); + } Msg.info(this, "Stopped emulation at " + key.time); TraceSnapshot destSnap = writeToScratch(key, be.ce); cacheEmulator(key, be.ce); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index 0898a65a92..dd45893063 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -38,11 +38,11 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; +import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.DecodePcodeExecutionException; -import ghidra.pcode.exec.InterruptPcodeExecutionException; +import ghidra.pcode.exec.*; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; @@ -489,25 +489,180 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe trace.getBreakpointManager() .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread), Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); - trace.getBreakpointManager() + 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(); + """); trace.getBreakpointManager() .addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread), 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 EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), 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); + // Save this for comparison later + DebuggerPcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); + // This will test if the one just hit gets ignored EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), result1.schedule(), monitor, Scheduler.oneThread(thread)); assertEquals(TraceSchedule.snap(0).steppedForward(thread, 2), result2.schedule()); 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 @@ -817,6 +972,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000)); newTraceThread.setName("MyThread"); + @SuppressWarnings("unused") PcodeThread newEmuThread = emulator.newThread(newTraceThread.getPath()); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java index 24a6a75de9..aafbc22ac6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java @@ -4,9 +4,9 @@ * 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. @@ -232,6 +232,11 @@ public class PatchStep implements Step { return StepType.PATCH; } + @Override + public long getSkipCount() { + return 0; + } + @Override public boolean isNop() { // TODO: If parsing beforehand, base on number of ops @@ -307,7 +312,7 @@ public class PatchStep implements Step { } @Override - public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) throws CancelledException { emuThread.stepPatch(sleigh); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java index c06f53b58c..68cce8217c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java @@ -4,9 +4,9 @@ * 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. @@ -81,14 +81,17 @@ public interface Scheduler { /** * 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 * - * @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); @@ -118,12 +121,17 @@ public interface Scheduler { TickStep slice = nextSlice(trace); eventThread = slice.getThread(tm, eventThread); emuThread = machine.getThread(eventThread.getPath(), true); - if (emuThread.getFrame() != null) { + long ticksLeft = slice.tickCount; + if (ticksLeft > 0 && emuThread.getFrame() != null) { + monitor.checkCancelled(); emuThread.finishInstruction(); + ticksLeft--; + completedTicks++; } - for (int i = 0; i < slice.tickCount; i++) { + while (ticksLeft > 0) { monitor.checkCancelled(); emuThread.stepInstruction(); + ticksLeft--; completedTicks++; } completedSteps = completedSteps.steppedForward(eventThread, completedTicks); @@ -134,11 +142,11 @@ public interface Scheduler { completedSteps = completedSteps.steppedForward(eventThread, completedTicks); PcodeFrame frame = emuThread.getFrame(); if (frame == null) { - return new RecordRunResult(completedSteps, e); + return new RecordRunResult(completedSteps.assumeRecorded(), e); } // Rewind one so stepping retries the op causing the error frame.stepBack(); - int count = frame.count(); + int count = frame.resetCount(); if (count == 0) { // If we've decoded, but could not execute the first op, just drop the p-code steps emuThread.dropInstruction(); @@ -146,11 +154,11 @@ public interface Scheduler { } // The +1 accounts for the decode step return new RecordRunResult( - completedSteps.steppedPcodeForward(eventThread, count + 1), e); + completedSteps.steppedPcodeForward(eventThread, count + 1).assumeRecorded(), e); } catch (CancelledException e) { return new RecordRunResult( - completedSteps.steppedForward(eventThread, completedTicks), e); + completedSteps.steppedForward(eventThread, completedTicks).assumeRecorded(), e); } } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java index d35b7ad5c0..c532ad33e5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java @@ -4,9 +4,9 @@ * 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. @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.PcodeThread; import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; @@ -240,7 +241,7 @@ public class Sequence implements Comparable { } /** - * Richly compare to sequences + * Richly compare two sequences * *

* 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 { return count; } + public long totalSkipCount() { + long count = 0; + for (Step step : steps) { + count += step.getSkipCount(); + } + return count; + } + /** * Compute to total number of patches specified * @@ -440,4 +449,25 @@ public class Sequence implements Comparable { } 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; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java index 207015d501..8702dc3ce9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java @@ -4,9 +4,9 @@ * 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. @@ -48,6 +48,11 @@ public class SkipStep extends AbstractStep { return StepType.SKIP; } + @Override + public long getSkipCount() { + return tickCount; + } + @Override protected String toStringStepPart() { return String.format("s%d", tickCount); @@ -66,7 +71,7 @@ public class SkipStep extends AbstractStep { } @Override - public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) throws CancelledException { for (int i = 0; i < tickCount; i++) { monitor.incrementProgress(1); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java index af90d704cd..e4dd0abe03 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java @@ -4,9 +4,9 @@ * 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. @@ -116,6 +116,8 @@ public interface Step extends Comparable { long getTickCount(); + long getSkipCount(); + long getPatchCount(); /** @@ -147,7 +149,7 @@ public interface Step extends Comparable { * 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. * - * @param steps the count to rewind + * @param count the count to rewind * @return the number of steps remaining */ long rewind(long count); @@ -155,7 +157,7 @@ public interface Step extends Comparable { /** * 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 */ CompareResult compareStep(Step that); @@ -183,7 +185,7 @@ public interface Step extends Comparable { return thread; } - void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) throws CancelledException; long coalescePatches(Language language, List steps); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java index 274c1e98f4..f21437535c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java @@ -4,9 +4,9 @@ * 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. @@ -48,6 +48,11 @@ public class TickStep extends AbstractStep { return StepType.TICK; } + @Override + public long getSkipCount() { + return 0; + } + @Override protected String toStringStepPart() { return Long.toString(tickCount); @@ -66,7 +71,7 @@ public class TickStep extends AbstractStep { } @Override - public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) throws CancelledException { for (int i = 0; i < tickCount; i++) { monitor.incrementProgress(1); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index 12b71d2fdd..ea703ad8ea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -4,9 +4,9 @@ * 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. @@ -39,7 +39,7 @@ public class TraceSchedule implements Comparable { * @return the schedule */ 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 = @@ -55,9 +55,10 @@ public class TraceSchedule implements Comparable { * threads forward, and/or patching machine state. * * @param spec the string specification + * @param source the presumed source of the schedule * @return the parsed schedule */ - public static TraceSchedule parse(String spec) { + public static TraceSchedule parse(String spec, Source source) { String[] parts = spec.split(":", 2); if (parts.length > 2) { throw new AssertionError(); @@ -98,24 +99,76 @@ public class TraceSchedule implements Comparable { ticks = 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. + * + *

+ * 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 Sequence steps; private final Sequence pSteps; + private final Source source; /** * Construct the given schedule * * @param snap the initial trace snapshot * @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.steps = steps; 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 @@ -152,33 +205,17 @@ public class TraceSchedule implements Comparable { } 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); - if (result != CompareResult.EQUALS) { - return result; - } - - return CompareResult.EQUALS; + return switch (result) { + case UNREL_LT, UNREL_GT -> result; + case REL_LT -> (this.pSteps.isNop() || this.source == Source.RECORD) + ? CompareResult.REL_LT + : CompareResult.UNREL_LT; + case REL_GT -> (that.pSteps.isNop() || that.source == Source.RECORD) + ? CompareResult.REL_GT + : CompareResult.UNREL_GT; + default -> this.pSteps.compareSeq(that.pSteps); + }; } @Override @@ -222,10 +259,19 @@ public class TraceSchedule implements Comparable { 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 * - * @return + * @return the snapshot key */ public long getSnap() { return snap; @@ -234,7 +280,7 @@ public class TraceSchedule implements Comparable { /** * Get the last thread key stepped by this schedule * - * @return + * @return the thread key */ public long getLastThreadKey() { long last = pSteps.getLastThreadKey(); @@ -396,6 +442,7 @@ public class TraceSchedule implements Comparable { pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor); } else { + remains = remains.checkFinish(lastThread, machine); lastThread = remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor); lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor); @@ -417,7 +464,7 @@ public class TraceSchedule implements Comparable { public TraceSchedule steppedForward(TraceThread thread, long tickCount) { Sequence steps = this.steps.clone(); 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 { public TraceSchedule skippedForward(TraceThread thread, long tickCount) { Sequence steps = this.steps.clone(); 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 visited) { @@ -454,7 +501,7 @@ public class TraceSchedule implements Comparable { } Sequence steps = this.steps.clone(); 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 { public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) { Sequence pTicks = this.pSteps.clone(); 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 { public TraceSchedule skippedPcodeForward(TraceThread thread, int pTickCount) { Sequence pTicks = this.pSteps.clone(); 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 { } Sequence pTicks = this.pSteps.clone(); pTicks.rewind(pStepCount); - return new TraceSchedule(snap, steps.clone(), pTicks); + return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT); } private long keyOf(TraceThread thread) { @@ -530,6 +577,7 @@ public class TraceSchedule implements Comparable { * Returns the equivalent of executing this schedule then performing a given patch * * @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. * @return the resulting schedule */ @@ -538,19 +586,20 @@ public class TraceSchedule implements Comparable { Sequence pTicks = this.pSteps.clone(); pTicks.advance(new PatchStep(thread.getKey(), sleigh)); pTicks.coalescePatches(language); - return new TraceSchedule(snap, steps.clone(), pTicks); + return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT); } Sequence ticks = this.steps.clone(); ticks.advance(new PatchStep(keyOf(thread), sleigh)); 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 * * @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 */ public TraceSchedule patched(TraceThread thread, Language language, List sleigh) { @@ -560,14 +609,14 @@ public class TraceSchedule implements Comparable { pTicks.advance(new PatchStep(thread.getKey(), line)); } pTicks.coalescePatches(language); - return new TraceSchedule(snap, steps.clone(), pTicks); + return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT); } Sequence ticks = this.steps.clone(); for (String line : sleigh) { ticks.advance(new PatchStep(thread.getKey(), line)); } 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 { * *

* 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. * @return the complete schedule @@ -586,16 +635,25 @@ public class TraceSchedule implements Comparable { if (this.pSteps.isNop()) { Sequence ticks = this.steps.clone(); 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()) { - Sequence pTicks = this.steps.clone(); + Sequence pTicks = this.pSteps.clone(); 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"); } + /** + * 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 * @@ -611,4 +669,8 @@ public class TraceSchedule implements Comparable { result.remove(null); return result; } + + public TraceSchedule assumeRecorded() { + return new TraceSchedule(snap, steps, pSteps, Source.RECORD); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java index 15b0c5edd8..a0eb1fbe85 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java @@ -4,9 +4,9 @@ * 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. @@ -224,19 +224,30 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { expectU("0:10", "1:10"); expectU("0:t0-10", "0:t1-10"); // 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"); expectR("0:t0-10", "0:t0-11"); expectR("0:t0-10", "0:t0-10;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-11.1"); - expectR("0:t0-10", "0:t0-10;t1-5.1"); - expectR("0:t0-10", "0:t0-11;t1-5.1"); + expectR("0:t0-10", "0:t0-10.2"); + expectR("0:t0-10", "0:t0-11.2"); + expectR("0:t0-10", "0:t0-10;t1-5.2"); + expectR("0:t0-10", "0:t0-11;t1-5.2"); 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) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index bdb79ae7dc..c453910dfa 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -4,9 +4,9 @@ * 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. @@ -400,6 +400,7 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void stepInstruction() { + assertCompletedInstruction(); PcodeProgram inj = getInject(counter); if (inj != null) { instruction = null; @@ -576,7 +577,6 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void executeInstruction() { - assertCompletedInstruction(); instruction = decoder.decodeInstruction(counter, context); PcodeProgram insProg = PcodeProgram.fromInstruction(instruction); preExecuteInstruction(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeFrame.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeFrame.java index 01b6ec9d73..be91c89aff 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeFrame.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeFrame.java @@ -4,9 +4,9 @@ * 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. @@ -159,7 +159,7 @@ public class PcodeFrame { } /** - * The number of p-code ops executed + * Get and reset the number of p-code ops executed * *

* 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 */ - public int count() { + public int resetCount() { + int count = this.count; + this.count = 0; return count; }