GP-1595: Add global control actions for Target, Trace, and Emulator

This commit is contained in:
Dan 2022-10-18 09:59:53 -04:00
parent 81d880f3ab
commit 034730b785
66 changed files with 3335 additions and 763 deletions

View file

@ -22,8 +22,8 @@ import java.util.stream.Stream;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import ghidra.dbg.target.TargetMethod;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates;
@ -570,4 +570,29 @@ public interface TraceObject extends TraceUniqueObject {
}
return null;
}
/**
* Get the execution state, if applicable, of this object
*
* <p>
* This searches for the conventional stateful object defining this object's execution state. If
* such an object does not exist, null is returned. If one does exist, then its execution state
* at the given snap is returned. If that state is null, it is assumed
* {@link TargetExecutionState#INACTIVE}.
*
* @param snap the snap
* @return the state or null
*/
default TargetExecutionState getExecutionState(long snap) {
TraceObject stateful = querySuitableTargetInterface(TargetExecutionStateful.class);
if (stateful == null) {
return null;
}
TraceObjectValue stateVal =
stateful.getAttribute(snap, TargetExecutionStateful.STATE_ATTRIBUTE_NAME);
if (stateVal == null) {
return TargetExecutionState.INACTIVE;
}
return TargetExecutionState.valueOf((String) stateVal.getValue());
}
}

View file

@ -312,7 +312,7 @@ public class PatchStep implements Step {
}
@Override
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException {
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", sleigh + ";");
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());

View file

@ -0,0 +1,151 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.model.time.schedule;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A generator of an emulator's thread schedule
*/
public interface Scheduler {
/**
* Create a scheduler that allocates all slices to a single thread
*
* @param thread the thread to schedule
* @return the scheduler
*/
static Scheduler oneThread(TraceThread thread) {
long key = thread == null ? -1 : thread.getKey();
return new Scheduler() {
@Override
public TickStep nextSlice(Trace trace) {
return new TickStep(key, 1000);
}
};
}
interface RunResult {
/**
* Get the actual schedule executed
*
* <p>
* It is possible for the machine to be interrupted mid-instruction. If this is the case,
* the trace schedule will indicate the p-code steps taken.
*
* @return the schedule
*/
public TraceSchedule schedule();
/**
* Get the error that interrupted execution
*
* <p>
* Ideally, this is a {@link InterruptPcodeExecutionException}, indicating a breakpoint
* trapped the emulator, but it could be a number of things:
*
* <ul>
* <li>An instruction decode error</li>
* <li>An unimplemented instruction</li>
* <li>An unimplemented p-code userop</li>
* <li>An error accessing the machine state</li>
* <li>A runtime error in the implementation of a p-code userop</li>
* <li>A runtime error in the implementation of the emulator, in which case, a bug should be
* filed</li>
* </ul>
*
* @return the error
*/
public Throwable error();
}
/**
* The result of running a machine
*/
record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult {
}
/**
* Get the next step to schedule
*
* @return the (instruction-level) thread and tick count
*/
TickStep nextSlice(Trace trace);
/**
* Run a machine according to the given schedule until it is interrupted
*
* <p>
* This method will drop p-code steps from injections, including those from execution
* breakpoints. The goal is to ensure that the returned schedule can be used to recover the same
* state on a machine without injections. Unfortunately, injections which modify the machine
* state, other than unique variables, will defeat that goal.
*
* @param trace the trace whose threads to schedule
* @param eventThread the first thread to schedule if the scheduler doesn't specify
* @param machine the machine to run
* @param monitor a monitor for cancellation
* @return the result of execution
*/
default RunResult run(Trace trace, TraceThread eventThread, PcodeMachine<?> machine,
TaskMonitor monitor) {
TraceThreadManager tm = trace.getThreadManager();
TraceSchedule completedSteps = TraceSchedule.snap(0);
PcodeThread<?> emuThread = null;
int completedTicks = 0;
try {
while (true) {
TickStep slice = nextSlice(trace);
eventThread = slice.getThread(tm, eventThread);
emuThread = machine.getThread(eventThread.getPath(), true);
for (int i = 0; i < slice.tickCount; i++) {
monitor.checkCanceled();
emuThread.stepInstruction();
completedTicks++;
}
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
completedTicks = 0;
}
}
catch (PcodeExecutionException e) {
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
if (emuThread.getInstruction() == null) {
return new RecordRunResult(completedSteps, e);
}
PcodeFrame frame = e.getFrame();
// Rewind one so stepping retries the op causing the error
int count = frame.count() - 1;
if (frame == null || count == 0) {
// If we've decoded, but could execute the first op, just drop the p-code steps
return new RecordRunResult(completedSteps, e);
}
// The +1 accounts for the decode step
return new RecordRunResult(
completedSteps.steppedPcodeForward(eventThread, count + 1), e);
}
catch (CancelledException e) {
return new RecordRunResult(
completedSteps.steppedForward(eventThread, completedTicks), e);
}
}
}

View file

@ -384,8 +384,8 @@ public class Sequence implements Comparable<Sequence> {
* @return the last trace thread stepped during execution
* @throws CancelledException if execution is cancelled
*/
public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine,
Stepper<T> stepper, TaskMonitor monitor) throws CancelledException {
public TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<?> machine,
Stepper stepper, TaskMonitor monitor) throws CancelledException {
TraceThreadManager tm = trace.getThreadManager();
TraceThread thread = eventThread;
for (Step step : steps) {

View file

@ -66,7 +66,7 @@ public class SkipStep extends AbstractStep {
}
@Override
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException {
for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1);

View file

@ -170,20 +170,20 @@ public interface Step extends Comparable<Step> {
return compareStep(that).compareTo;
}
default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
PcodeMachine<T> machine, Stepper<T> stepper, TaskMonitor monitor)
default TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
PcodeMachine<?> machine, Stepper stepper, TaskMonitor monitor)
throws CancelledException {
TraceThread thread = getThread(tm, eventThread);
if (machine == null) {
// Just performing validation (specifically thread parts)
return thread;
}
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
PcodeThread<?> emuThread = machine.getThread(thread.getPath(), true);
execute(emuThread, stepper, monitor);
return thread;
}
<T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
<T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException;
long coalescePatches(Language language, List<Step> steps);

View file

@ -17,44 +17,41 @@ package ghidra.trace.model.time.schedule;
import ghidra.pcode.emu.PcodeThread;
public interface Stepper<T> {
@SuppressWarnings("rawtypes")
public interface Stepper {
enum Enum implements Stepper {
INSTRUCTION {
@Override
public void tick(PcodeThread thread) {
public void tick(PcodeThread<?> thread) {
thread.stepInstruction();
}
@Override
public void skip(PcodeThread thread) {
public void skip(PcodeThread<?> thread) {
thread.skipInstruction();
}
},
PCODE {
@Override
public void tick(PcodeThread thread) {
public void tick(PcodeThread<?> thread) {
thread.stepPcodeOp();
}
@Override
public void skip(PcodeThread thread) {
public void skip(PcodeThread<?> thread) {
thread.skipPcodeOp();
}
};
}
@SuppressWarnings("unchecked")
static <T> Stepper<T> instruction() {
static Stepper instruction() {
return Enum.INSTRUCTION;
}
@SuppressWarnings("unchecked")
static <T> Stepper<T> pcode() {
static Stepper pcode() {
return Enum.PCODE;
}
void tick(PcodeThread<T> thread);
void tick(PcodeThread<?> thread);
void skip(PcodeThread<T> thread);
void skip(PcodeThread<?> thread);
}

View file

@ -66,7 +66,7 @@ public class TickStep extends AbstractStep {
}
@Override
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException {
for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1);

View file

@ -561,6 +561,32 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
return new TraceSchedule(snap, ticks, new Sequence());
}
/**
* Compute the schedule resulting from this schedule advanced by the given schedule
*
* <p>
* This operation cannot be used to append instruction steps after p-code steps. Thus, if this
* schedule contains any p-code steps and {@code} next has instruction steps, an error will be
*
* @param next the schedule to append. Its snap is ignored.
* @return the complete schedule
* @throws IllegalArgumentException if the result would have instruction steps following p-code
* steps
*/
public TraceSchedule advanced(TraceSchedule next) {
if (this.pSteps.isNop()) {
Sequence ticks = this.steps.clone();
ticks.advance(next.steps);
return new TraceSchedule(this.snap, ticks, next.pSteps.clone());
}
else if (next.steps.isNop()) {
Sequence pTicks = this.steps.clone();
pTicks.advance(next.pSteps);
return new TraceSchedule(this.snap, this.steps.clone(), pTicks);
}
throw new IllegalArgumentException("Cannot have instructions steps following p-code steps");
}
/**
* Get the threads involved in the schedule
*