mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1584: Unify state-editing story across Debugger UI.
This commit is contained in:
parent
067fd41b62
commit
12493ab734
75 changed files with 2873 additions and 818 deletions
|
@ -54,10 +54,12 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
|
|||
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.util.TraceChangeManager;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
|
@ -135,6 +137,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
protected DBTraceVariableSnapProgramView programView;
|
||||
protected Map<DBTraceVariableSnapProgramView, Void> programViews = new WeakHashMap<>();
|
||||
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashMap<>();
|
||||
protected ListenerSet<TraceProgramViewListener> viewListeners =
|
||||
new ListenerSet<>(TraceProgramViewListener.class);
|
||||
|
||||
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
|
||||
throws IOException, LanguageNotFoundException {
|
||||
|
@ -562,29 +566,34 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
// NOTE: addListener synchronizes on this and might generate callbacks immediately
|
||||
public synchronized DBTraceProgramView getFixedProgramView(long snap) {
|
||||
// NOTE: The new viewport will need to read from the time manager during init
|
||||
DBTraceProgramView view;
|
||||
try (LockHold hold = lockRead()) {
|
||||
synchronized (fixedProgramViews) {
|
||||
DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> {
|
||||
Msg.debug(this, "Creating fixed view at snap=" + snap);
|
||||
return new DBTraceProgramView(this, snap, baseCompilerSpec);
|
||||
});
|
||||
return view;
|
||||
view = fixedProgramViews.get(snap);
|
||||
if (view != null) {
|
||||
return view;
|
||||
}
|
||||
Msg.debug(this, "Creating fixed view at snap=" + snap);
|
||||
view = new DBTraceProgramView(this, snap, baseCompilerSpec);
|
||||
}
|
||||
}
|
||||
viewListeners.fire.viewCreated(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: Ditto getFixedProgramView
|
||||
public synchronized DBTraceVariableSnapProgramView createProgramView(long snap) {
|
||||
// NOTE: The new viewport will need to read from the time manager during init
|
||||
DBTraceVariableSnapProgramView view;
|
||||
try (LockHold hold = lockRead()) {
|
||||
synchronized (programViews) {
|
||||
DBTraceVariableSnapProgramView view =
|
||||
new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
||||
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
||||
programViews.put(view, null);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
viewListeners.fire.viewCreated(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -721,6 +730,18 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
return getOptions(TRACE_INFO).getDate(DATE_CREATED, new Date(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceProgramView> getAllProgramViews() {
|
||||
Collection<TraceProgramView> all = new ArrayList<>();
|
||||
synchronized (programViews) {
|
||||
all.addAll(programViews.keySet());
|
||||
}
|
||||
synchronized (fixedProgramViews) {
|
||||
all.addAll(fixedProgramViews.values());
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
protected void allViews(Consumer<DBTraceProgramView> action) {
|
||||
Collection<DBTraceProgramView> all = new ArrayList<>();
|
||||
synchronized (programViews) {
|
||||
|
@ -734,6 +755,16 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProgramViewListener(TraceProgramViewListener listener) {
|
||||
viewListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProgramViewListener(TraceProgramViewListener listener) {
|
||||
viewListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void updateViewsAddRegionBlock(TraceMemoryRegion region) {
|
||||
allViews(v -> v.updateMemoryAddRegionBlock(region));
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ import ghidra.framework.store.LockException;
|
|||
import ghidra.program.database.mem.*;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.trace.database.memory.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
|
@ -47,6 +48,8 @@ public abstract class AbstractDBTraceProgramViewMemory
|
|||
protected boolean forceFullView = false;
|
||||
protected long snap;
|
||||
|
||||
protected LiveMemoryHandler memoryWriteRedirect;
|
||||
|
||||
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
|
||||
this.program = program;
|
||||
this.memoryManager = program.trace.getMemoryManager();
|
||||
|
@ -155,7 +158,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
|||
|
||||
@Override
|
||||
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
|
||||
throw new UnsupportedOperationException();
|
||||
this.memoryWriteRedirect = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -329,6 +332,10 @@ public abstract class AbstractDBTraceProgramViewMemory
|
|||
|
||||
@Override
|
||||
public void setByte(Address addr, byte value) throws MemoryAccessException {
|
||||
if (memoryWriteRedirect != null) {
|
||||
memoryWriteRedirect.putByte(addr, value);
|
||||
return;
|
||||
}
|
||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
|
||||
throw new MemoryAccessException();
|
||||
|
@ -338,6 +345,10 @@ public abstract class AbstractDBTraceProgramViewMemory
|
|||
@Override
|
||||
public void setBytes(Address addr, byte[] source, int sIndex, int size)
|
||||
throws MemoryAccessException {
|
||||
if (memoryWriteRedirect != null) {
|
||||
memoryWriteRedirect.putBytes(addr, source, sIndex, size);
|
||||
return;
|
||||
}
|
||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
|
||||
throw new MemoryAccessException();
|
||||
|
|
|
@ -369,6 +369,10 @@ public interface Trace extends DataTypeManagerDomainObject {
|
|||
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
|
||||
}
|
||||
|
||||
public interface TraceProgramViewListener {
|
||||
void viewCreated(TraceProgramView view);
|
||||
}
|
||||
|
||||
Language getBaseLanguage();
|
||||
|
||||
CompilerSpec getBaseCompilerSpec();
|
||||
|
@ -412,6 +416,13 @@ public interface Trace extends DataTypeManagerDomainObject {
|
|||
|
||||
TraceVariableSnapProgramView createProgramView(long snap);
|
||||
|
||||
/**
|
||||
* Collect all program views, fixed or variable, of this trace.
|
||||
*
|
||||
* @return the current set of program views
|
||||
*/
|
||||
Collection<TraceProgramView> getAllProgramViews();
|
||||
|
||||
/**
|
||||
* Get the "canonical" program view for this trace
|
||||
*
|
||||
|
@ -423,6 +434,10 @@ public interface Trace extends DataTypeManagerDomainObject {
|
|||
*/
|
||||
TraceVariableSnapProgramView getProgramView();
|
||||
|
||||
void addProgramViewListener(TraceProgramViewListener listener);
|
||||
|
||||
void removeProgramViewListener(TraceProgramViewListener listener);
|
||||
|
||||
LockHold lockRead();
|
||||
|
||||
LockHold lockWrite();
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package ghidra.trace.model.program;
|
||||
|
||||
import ghidra.program.model.mem.LiveMemoryHandler;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
|
||||
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
||||
|
@ -24,4 +25,13 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
|||
void setForceFullView(boolean forceFullView);
|
||||
|
||||
boolean isForceFullView();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* For trace views, this only redirects memory writes.
|
||||
*/
|
||||
@Override
|
||||
void setLiveMemoryHandler(LiveMemoryHandler handler);
|
||||
}
|
||||
|
|
|
@ -15,21 +15,133 @@
|
|||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeProgram;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class PatchStep implements Step {
|
||||
protected final long threadKey;
|
||||
protected final String sleigh;
|
||||
protected final int hashCode;
|
||||
protected String sleigh;
|
||||
protected int hashCode;
|
||||
|
||||
public static String generateSleigh(Language language, Address address, byte[] data,
|
||||
int length) {
|
||||
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
|
||||
if (address.isMemoryAddress()) {
|
||||
AddressSpace space = address.getAddressSpace();
|
||||
if (language.getDefaultSpace() == space) {
|
||||
return String.format("*:%d 0x%s:%d=0x%s",
|
||||
length,
|
||||
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
|
||||
value.toString(16));
|
||||
}
|
||||
return String.format("*[%s]:%d 0x%s:%d=0x%s",
|
||||
space.getName(), length,
|
||||
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
|
||||
value.toString(16));
|
||||
}
|
||||
Register register = language.getRegister(address, length);
|
||||
if (register == null) {
|
||||
throw new AssertionError("Can only modify memory or register");
|
||||
}
|
||||
return String.format("%s=0x%s", register, value.toString(16));
|
||||
}
|
||||
|
||||
public static String generateSleigh(Language language, Address address, byte[] data) {
|
||||
return generateSleigh(language, address, data, data.length);
|
||||
}
|
||||
|
||||
protected static List<String> generateSleigh(Language language,
|
||||
Map<AddressSpace, SemisparseByteArray> patches) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Entry<AddressSpace, SemisparseByteArray> entry : patches.entrySet()) {
|
||||
generateSleigh(result, language, entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static void generateSleigh(List<String> result, Language language, AddressSpace space,
|
||||
SemisparseByteArray array) {
|
||||
if (space.isRegisterSpace()) {
|
||||
generateRegisterSleigh(result, language, space, array);
|
||||
}
|
||||
else {
|
||||
generateMemorySleigh(result, language, space, array);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void generateMemorySleigh(List<String> result, Language language,
|
||||
AddressSpace space, SemisparseByteArray array) {
|
||||
byte[] data = new byte[8];
|
||||
for (Range<UnsignedLong> range : array.getInitialized(0, -1).asRanges()) {
|
||||
assert range.lowerBoundType() == BoundType.CLOSED;
|
||||
Address start = space.getAddress(range.lowerEndpoint().longValue());
|
||||
Address end = space.getAddress(range.upperEndpoint().longValue() -
|
||||
(range.upperBoundType() == BoundType.OPEN ? 1 : 0));
|
||||
for (AddressRange chunk : new AddressRangeChunker(start, end, data.length)) {
|
||||
Address min = chunk.getMinAddress();
|
||||
int length = (int) chunk.getLength();
|
||||
array.getData(min.getOffset(), data, 0, length);
|
||||
result.add(generateSleigh(language, min, data, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Range<UnsignedLong> rangeOfRegister(Register r) {
|
||||
long lower = r.getAddress().getOffset();
|
||||
long upper = lower + r.getNumBytes();
|
||||
return Range.closedOpen(UnsignedLong.fromLongBits(lower), UnsignedLong.fromLongBits(upper));
|
||||
}
|
||||
|
||||
protected static boolean isContained(Register r, RangeSet<UnsignedLong> remains) {
|
||||
return remains.encloses(rangeOfRegister(r));
|
||||
}
|
||||
|
||||
protected static void generateRegisterSleigh(List<String> result, Language language,
|
||||
AddressSpace space, SemisparseByteArray array) {
|
||||
byte[] data = new byte[8];
|
||||
RangeSet<UnsignedLong> remains = TreeRangeSet.create(array.getInitialized(0, -1));
|
||||
while (!remains.isEmpty()) {
|
||||
Range<UnsignedLong> span = remains.span();
|
||||
assert span.lowerBoundType() == BoundType.CLOSED;
|
||||
Address min = space.getAddress(span.lowerEndpoint().longValue());
|
||||
Register register = Stream.of(language.getRegisters(min))
|
||||
.filter(r -> r.getAddress().equals(min))
|
||||
.filter(r -> r.getNumBytes() <= data.length)
|
||||
.filter(r -> isContained(r, remains))
|
||||
.sorted(Comparator.comparing(r -> -r.getNumBytes()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (register == null) {
|
||||
throw new IllegalArgumentException("Could not find a register for " + min);
|
||||
}
|
||||
int length = register.getNumBytes();
|
||||
array.getData(min.getOffset(), data, 0, length);
|
||||
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
|
||||
result.add(String.format("%s=0x%s", register, value.toString(16)));
|
||||
remains.remove(rangeOfRegister(register));
|
||||
}
|
||||
}
|
||||
|
||||
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||
// TODO: Can I parse and validate the sleigh here?
|
||||
|
@ -45,6 +157,11 @@ public class PatchStep implements Step {
|
|||
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
|
||||
}
|
||||
|
||||
private void setSleigh(String sleigh) {
|
||||
this.sleigh = sleigh;
|
||||
this.hashCode = Objects.hash(threadKey, sleigh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
|
@ -162,4 +279,120 @@ public class PatchStep implements Step {
|
|||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
|
||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long coalescePatches(Language language, List<Step> steps) {
|
||||
long threadKey = -1;
|
||||
int toRemove = 0;
|
||||
Map<AddressSpace, SemisparseByteArray> patches = new TreeMap<>();
|
||||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||||
Step step = steps.get(i);
|
||||
long stk = step.getThreadKey();
|
||||
if (threadKey == -1) {
|
||||
threadKey = stk;
|
||||
}
|
||||
else if (stk != -1 && stk != threadKey) {
|
||||
break;
|
||||
}
|
||||
if (!(step instanceof PatchStep)) {
|
||||
break;
|
||||
}
|
||||
PatchStep ps = (PatchStep) step;
|
||||
Map<AddressSpace, SemisparseByteArray> subs = ps.getPatches(language);
|
||||
if (subs == null) {
|
||||
break;
|
||||
}
|
||||
mergePatches(subs, patches);
|
||||
patches = subs;
|
||||
toRemove++;
|
||||
}
|
||||
List<String> sleighPatches = generateSleigh(language, patches);
|
||||
assert sleighPatches.size() <= toRemove;
|
||||
for (String sleighPatch : sleighPatches) {
|
||||
PatchStep ps = (PatchStep) steps.get(steps.size() - toRemove);
|
||||
ps.setSleigh(sleighPatch);
|
||||
toRemove--;
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
protected void mergePatches(Map<AddressSpace, SemisparseByteArray> into,
|
||||
Map<AddressSpace, SemisparseByteArray> from) {
|
||||
for (Entry<AddressSpace, SemisparseByteArray> entry : from.entrySet()) {
|
||||
if (!into.containsKey(entry.getKey())) {
|
||||
into.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
else {
|
||||
into.get(entry.getKey()).putAll(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<AddressSpace, SemisparseByteArray> getPatches(Language language) {
|
||||
PcodeProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language,
|
||||
"schedule", List.of(sleigh + ";"), SleighUseropLibrary.nil());
|
||||
// SemisparseArray is a bit overkill, no?
|
||||
Map<AddressSpace, SemisparseByteArray> result = new TreeMap<>();
|
||||
for (PcodeOp op : prog.getCode()) {
|
||||
// Only accept patches in form [mem/reg] = [constant]
|
||||
switch (op.getOpcode()) {
|
||||
case PcodeOp.COPY:
|
||||
if (!getPatchCopyOp(language, result, op)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case PcodeOp.STORE:
|
||||
if (!getPatchStoreOp(language, result, op)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean getPatchCopyOp(Language language,
|
||||
Map<AddressSpace, SemisparseByteArray> result, PcodeOp op) {
|
||||
Varnode output = op.getOutput();
|
||||
if (!output.isAddress() && !output.isRegister()) {
|
||||
return false;
|
||||
}
|
||||
Varnode input = op.getInput(0);
|
||||
if (!input.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
Address address = output.getAddress();
|
||||
SemisparseByteArray array = result.computeIfAbsent(address.getAddressSpace(),
|
||||
as -> new SemisparseByteArray());
|
||||
array.putData(address.getOffset(),
|
||||
Utils.longToBytes(input.getOffset(), input.getSize(),
|
||||
language.isBigEndian()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean getPatchStoreOp(Language language,
|
||||
Map<AddressSpace, SemisparseByteArray> result,
|
||||
PcodeOp op) {
|
||||
Varnode vnSpace = op.getInput(0);
|
||||
if (!vnSpace.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
AddressSpace space =
|
||||
language.getAddressFactory().getAddressSpace((int) vnSpace.getOffset());
|
||||
Varnode vnOffset = op.getInput(1);
|
||||
if (!vnOffset.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
Varnode vnValue = op.getInput(2);
|
||||
if (!vnValue.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
SemisparseByteArray array = result.computeIfAbsent(space, as -> new SemisparseByteArray());
|
||||
array.putData(vnOffset.getOffset(), Utils.longToBytes(vnValue.getOffset(),
|
||||
vnValue.getSize(), language.isBigEndian()));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ 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;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
|
@ -122,7 +123,7 @@ public class Sequence implements Comparable<Sequence> {
|
|||
return;
|
||||
}
|
||||
if (steps.isEmpty()) {
|
||||
steps.add(step);
|
||||
steps.add(step.clone());
|
||||
return;
|
||||
}
|
||||
Step last = steps.get(steps.size() - 1);
|
||||
|
@ -157,6 +158,17 @@ public class Sequence implements Comparable<Sequence> {
|
|||
steps.addAll(clone.subList(2, size));
|
||||
}
|
||||
|
||||
public void coalescePatches(Language language) {
|
||||
if (steps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Step last = steps.get(steps.size() - 1);
|
||||
long toRemove = last.coalescePatches(language, steps);
|
||||
for (; toRemove > 0; toRemove--) {
|
||||
steps.remove(steps.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind this sequence the given step count
|
||||
*
|
||||
|
|
|
@ -15,10 +15,12 @@
|
|||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -171,4 +173,6 @@ public interface Step extends Comparable<Step> {
|
|||
|
||||
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||
TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
long coalescePatches(Language language, List<Step> steps);
|
||||
}
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -194,4 +196,9 @@ public class TickStep implements Step {
|
|||
stepAction.accept(emuThread);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long coalescePatches(Language language, List<Step> steps) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -487,6 +487,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
|
||||
private long keyOf(TraceThread thread) {
|
||||
return thread == null ? -1 : thread.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing this schedule then performing a given patch
|
||||
*
|
||||
|
@ -497,10 +501,12 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
if (!this.pSteps.isNop()) {
|
||||
Sequence pTicks = this.pSteps.clone();
|
||||
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||
pTicks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
Sequence ticks = this.steps.clone();
|
||||
ticks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||
ticks.advance(new PatchStep(keyOf(thread), sleigh));
|
||||
ticks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||
return new TraceSchedule(snap, ticks, new Sequence());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
public PcodeFrame getFrame() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Instruction getInstruction() {
|
||||
return null;
|
||||
|
@ -564,4 +564,27 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-4"), machine, TaskMonitor.DUMMY);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoalescePatches() throws Exception {
|
||||
// TODO: Should parse require coalescing? Can't without passing a language...
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
thread = tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||
}
|
||||
TraceSchedule time = TraceSchedule.parse("0");
|
||||
time = time.patched(thread, "r0l=1");
|
||||
assertEquals("0:t0-{r0l=0x1}", time.toString());
|
||||
time = time.patched(thread, "r0h=2");
|
||||
assertEquals("0:t0-{r0=0x200000001}", time.toString());
|
||||
time = time.patched(thread, "r1l=3").patched(thread, "*[ram]:4 0xcafe:8=0xdeadbeef");
|
||||
assertEquals("0:t0-{*:4 0xcafe:8=0xdeadbeef};t0-{r0=0x200000001};t0-{r1l=0x3}",
|
||||
time.toString());
|
||||
|
||||
time = time.patched(thread, "*:8 0xcb00:8 = 0x1122334455667788");
|
||||
assertEquals("0:t0-{*:8 0xcafe:8=0xdead112233445566};t0-{*:2 0xcb06:8=0x7788};" +
|
||||
"t0-{r0=0x200000001};t0-{r1l=0x3}", time.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue