Merge remote-tracking branch 'origin/GP-1584_Dan_emuStateEdit--SQUASHED'

Conflicts:
	Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/images/DebuggerMemoryBytesPlugin.png
This commit is contained in:
Ryan Kurtz 2022-04-18 00:40:46 -04:00
commit e165574afb
75 changed files with 2872 additions and 817 deletions

View file

@ -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));
}

View file

@ -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();

View file

@ -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();

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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
*

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}
}