mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 13:16:48 +08:00
Merge remote-tracking branch 'origin/GP-1711_Dan_traceDisassemblyConflicts--SQUASHED'
This commit is contained in:
+36
-8
@@ -20,12 +20,14 @@ import java.util.Map.Entry;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||
import ghidra.trace.database.*;
|
||||
import ghidra.trace.database.DBTraceCacheForContainingQueries;
|
||||
import ghidra.trace.database.DBTraceCacheForContainingQueries.GetKey;
|
||||
import ghidra.trace.database.DBTraceCacheForSequenceQueries;
|
||||
import ghidra.trace.database.context.DBTraceRegisterContextSpace;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapAddressSetView;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace.TraceCodeChangeType;
|
||||
import ghidra.trace.model.listing.TraceBaseDefinedUnitsView;
|
||||
@@ -359,20 +361,28 @@ public abstract class AbstractBaseDBTraceDefinedUnitsView<T extends AbstractDBTr
|
||||
* Select a sub-lifespan from that given so that the box does not overlap an existing unit
|
||||
*
|
||||
* <p>
|
||||
* The selected lifespan will have the same start snap at that given. The box is the bounding
|
||||
* The selected lifespan will have the same start snap as that given. The box is the bounding
|
||||
* box of a unit the client is trying to create.
|
||||
*
|
||||
* @param span the lifespan of the box
|
||||
* @param extending if applicable, the unit whose lifespan is being extended
|
||||
* @param range the address range of the box
|
||||
* @return the selected sub-lifespan
|
||||
* @throws CodeUnitInsertionException if the start snap is contained in an existing unit
|
||||
*/
|
||||
protected Lifespan truncateSoonestDefined(Lifespan span, AddressRange range)
|
||||
throws CodeUnitInsertionException {
|
||||
protected Lifespan truncateSoonestDefined(Lifespan span, AbstractDBTraceCodeUnit<?> extending,
|
||||
AddressRange range) throws CodeUnitInsertionException {
|
||||
final Lifespan toScan;
|
||||
if (extending == null) {
|
||||
toScan = span;
|
||||
}
|
||||
else {
|
||||
assert span.lmax() > extending.getEndSnap();
|
||||
toScan = span.withMin(extending.getEndSnap() + 1);
|
||||
}
|
||||
T truncateBy =
|
||||
mapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)
|
||||
.starting(
|
||||
Rectangle2DDirection.BOTTOMMOST))
|
||||
mapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, toScan)
|
||||
.starting(Rectangle2DDirection.BOTTOMMOST))
|
||||
.firstValue();
|
||||
if (truncateBy == null) {
|
||||
return span;
|
||||
@@ -380,7 +390,25 @@ public abstract class AbstractBaseDBTraceDefinedUnitsView<T extends AbstractDBTr
|
||||
if (truncateBy.getStartSnap() <= span.lmin()) {
|
||||
throw new CodeUnitInsertionException("Code units cannot overlap");
|
||||
}
|
||||
return Lifespan.span(span.lmin(), truncateBy.getStartSnap() - 1);
|
||||
return span.withMax(truncateBy.getStartSnap() - 1);
|
||||
}
|
||||
|
||||
protected long computeTruncatedMax(Lifespan lifespan, T extending, AddressRange range)
|
||||
throws CodeUnitInsertionException {
|
||||
// First, truncate lifespan to the next code unit when upper bound is max
|
||||
if (!lifespan.maxIsFinite()) {
|
||||
lifespan = space.instructions.truncateSoonestDefined(lifespan, extending, range);
|
||||
lifespan = space.definedData.truncateSoonestDefined(lifespan, extending, range);
|
||||
}
|
||||
// Second, truncate lifespan to the next change of bytes in the range
|
||||
DBTraceMemorySpace memSpace =
|
||||
space.trace.getMemoryManager().getMemorySpace(space.space, true);
|
||||
Lifespan fullSpan = extending == null ? lifespan : lifespan.bound(extending.getLifespan());
|
||||
long endSnap = memSpace.getFirstChange(fullSpan, range);
|
||||
if (endSnap == Long.MIN_VALUE) {
|
||||
return lifespan.lmax();
|
||||
}
|
||||
return endSnap - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-3
@@ -20,7 +20,6 @@ import java.nio.ByteBuffer;
|
||||
import db.DBRecord;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
@@ -123,8 +122,13 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
|
||||
}
|
||||
// Copy from the cache
|
||||
int toCopyFromCache =
|
||||
Math.max(0, Math.min(byteCache.position() - addressOffset, buffer.remaining()));
|
||||
buffer.put(byteCache.array(), addressOffset, toCopyFromCache);
|
||||
Math.min(byteCache.position() - addressOffset, buffer.remaining());
|
||||
if (toCopyFromCache > 0) {
|
||||
buffer.put(byteCache.array(), addressOffset, toCopyFromCache);
|
||||
}
|
||||
else {
|
||||
toCopyFromCache = 0;
|
||||
}
|
||||
if (byteCache.position() >= end) {
|
||||
return toCopyFromCache;
|
||||
}
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ import java.io.IOException;
|
||||
import db.DBRecord;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.TypeDef;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
|
||||
+5
-18
@@ -131,21 +131,8 @@ public class DBTraceDefinedDataView extends AbstractBaseDBTraceDefinedUnitsView<
|
||||
Address endAddress = address.addNoWrap(length - 1);
|
||||
AddressRangeImpl createdRange = new AddressRangeImpl(address, endAddress);
|
||||
|
||||
// First, truncate lifespan to the next code unit when upper bound is max
|
||||
if (!lifespan.maxIsFinite()) {
|
||||
lifespan = space.instructions.truncateSoonestDefined(lifespan, createdRange);
|
||||
lifespan = space.definedData.truncateSoonestDefined(lifespan, createdRange);
|
||||
}
|
||||
|
||||
// Second, extend to the next change of bytes in the range within lifespan
|
||||
// Then, check that against existing code units.
|
||||
long endSnap = memSpace.getFirstChange(lifespan, createdRange);
|
||||
if (endSnap == Long.MIN_VALUE) {
|
||||
endSnap = lifespan.lmax();
|
||||
}
|
||||
else {
|
||||
endSnap--;
|
||||
}
|
||||
// Truncate, then check that against existing code units.
|
||||
long endSnap = computeTruncatedMax(lifespan, null, createdRange);
|
||||
TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(createdRange,
|
||||
Lifespan.span(startSnap, endSnap));
|
||||
if (!space.undefinedData.coversRange(tasr)) {
|
||||
@@ -158,13 +145,13 @@ public class DBTraceDefinedDataView extends AbstractBaseDBTraceDefinedUnitsView<
|
||||
}
|
||||
|
||||
long dataTypeID = space.dataTypeManager.getResolvedID(dataType);
|
||||
DBTraceData created = space.dataMapSpace.put(tasr, null);
|
||||
DBTraceData created = mapSpace.put(tasr, null);
|
||||
// TODO: data units with a guest platform
|
||||
created.set(space.trace.getPlatformManager().getHostPlatform(), dataTypeID);
|
||||
// TODO: Explicitly remove undefined from cache, or let weak refs take care of it?
|
||||
|
||||
cacheForContaining.notifyNewEntry(lifespan, createdRange, created);
|
||||
cacheForSequence.notifyNewEntry(lifespan, createdRange, created);
|
||||
cacheForContaining.notifyNewEntry(tasr.getLifespan(), createdRange, created);
|
||||
cacheForSequence.notifyNewEntry(tasr.getLifespan(), createdRange, created);
|
||||
space.undefinedData.invalidateCache();
|
||||
|
||||
if (dataType instanceof Composite || dataType instanceof Array ||
|
||||
|
||||
+122
-47
@@ -23,11 +23,11 @@ import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.lang.InstructionError.InstructionErrorType;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||
import ghidra.trace.database.context.DBTraceRegisterContextManager;
|
||||
import ghidra.trace.database.context.DBTraceRegisterContextSpace;
|
||||
import ghidra.trace.database.guest.InternalTracePlatform;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace.TraceCodeChangeType;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
@@ -59,6 +59,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
private final Address errorAddress;
|
||||
private final InstructionError conflict;
|
||||
private final CodeUnit conflictCodeUnit;
|
||||
private final boolean overwrite;
|
||||
|
||||
protected int count = 0;
|
||||
|
||||
@@ -75,10 +76,11 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
* @param errorAddress the address of the first error in the block, if any
|
||||
* @param conflict a description of the error, if any
|
||||
* @param conflictCodeUnit if a conflict, the code unit that already exists
|
||||
* @param overwrite true to overwrite existing defined units
|
||||
*/
|
||||
private InstructionBlockAdder(Set<Address> skipDelaySlots, Lifespan lifespan,
|
||||
InternalTracePlatform platform, InstructionBlock block, Address errorAddress,
|
||||
InstructionError conflict, CodeUnit conflictCodeUnit) {
|
||||
InstructionError conflict, CodeUnit conflictCodeUnit, boolean overwrite) {
|
||||
this.skipDelaySlots = skipDelaySlots;
|
||||
this.lifespan = lifespan;
|
||||
this.platform = platform;
|
||||
@@ -86,20 +88,113 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
this.errorAddress = errorAddress;
|
||||
this.conflict = conflict;
|
||||
this.conflictCodeUnit = conflictCodeUnit;
|
||||
this.overwrite = overwrite;
|
||||
}
|
||||
|
||||
protected void truncateOrDelete(TraceInstruction exists) {
|
||||
if (exists.getStartSnap() < lifespan.lmin()) {
|
||||
exists.setEndSnap(lifespan.lmin());
|
||||
}
|
||||
else {
|
||||
exists.delete();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSuitable(Instruction candidate, Instruction protoInstr) {
|
||||
try {
|
||||
return candidate.getPrototype().equals(protoInstr.getPrototype()) &&
|
||||
Arrays.equals(candidate.getBytes(), protoInstr.getBytes()) &&
|
||||
candidate.isFallThroughOverridden() == protoInstr.isFallThroughOverridden() &&
|
||||
Objects.equals(candidate.getFallThrough(), protoInstr.getFallThrough()) &&
|
||||
candidate.getFlowOverride() == protoInstr.getFlowOverride();
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Instruction doAdjustExisting(Address address, Instruction protoInstr)
|
||||
throws AddressOverflowException, CancelledException, CodeUnitInsertionException {
|
||||
DBTraceInstruction exists = getAt(lifespan.lmin(), address);
|
||||
if (exists == null || exists.getLength() != protoInstr.getLength()) {
|
||||
AddressRange range =
|
||||
new AddressRangeImpl(protoInstr.getAddress(), protoInstr.getLength());
|
||||
space.definedUnits.clear(lifespan, range, false, TaskMonitor.DUMMY);
|
||||
return null;
|
||||
}
|
||||
if (!isSuitable(exists, protoInstr)) {
|
||||
truncateOrDelete(exists);
|
||||
return null;
|
||||
}
|
||||
long curEnd = exists.getEndSnap();
|
||||
if (curEnd < lifespan.lmax()) {
|
||||
AddressRange range =
|
||||
new AddressRangeImpl(protoInstr.getAddress(), protoInstr.getLength());
|
||||
long endSnap = computeTruncatedMax(lifespan, exists, range);
|
||||
exists.setEndSnap(endSnap);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the preceding unit and see if it can be extended to "create" the desired one
|
||||
*
|
||||
* <p>
|
||||
* For overwrite, the caller should first use
|
||||
* {@link #doAdjustExisting(Address, InstructionPrototype, Instruction)}.
|
||||
*
|
||||
* @param address the starting address of the instruction
|
||||
* @param protoInstr the prototype instruction
|
||||
* @return the extended instruction, if it was a match
|
||||
* @throws AddressOverflowException should never
|
||||
* @throws CodeUnitInsertionException if there's no room to extend the preceding unit
|
||||
*/
|
||||
protected Instruction doExtendPreceding(Address address, Instruction protoInstr)
|
||||
throws AddressOverflowException, CodeUnitInsertionException {
|
||||
if (!lifespan.minIsFinite()) {
|
||||
return null;
|
||||
}
|
||||
DBTraceInstruction prec = getAt(lifespan.lmin() - 1, address);
|
||||
if (prec == null || prec.getLength() != protoInstr.getLength()) {
|
||||
return null;
|
||||
}
|
||||
if (!isSuitable(prec, protoInstr)) {
|
||||
return null;
|
||||
}
|
||||
AddressRange range =
|
||||
new AddressRangeImpl(protoInstr.getAddress(), protoInstr.getLength());
|
||||
long endSnap = computeTruncatedMax(lifespan, prec, range);
|
||||
if (!lifespan.contains(endSnap)) {
|
||||
// We can't extend it far enough
|
||||
return null;
|
||||
}
|
||||
prec.setEndSnap(endSnap);
|
||||
return prec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given instruction in the database
|
||||
*
|
||||
* @param address the address of the instruction
|
||||
* @param prototype the instruction prototype
|
||||
* @param protoInstr the parsed (usually pseudo) instruction
|
||||
* @return the created instruction
|
||||
*/
|
||||
protected Instruction doCreateInstruction(Address address,
|
||||
InstructionPrototype prototype, Instruction protoInstr) {
|
||||
protected Instruction doCreateInstruction(Address address, Instruction protoInstr) {
|
||||
try {
|
||||
Instruction created = doCreate(lifespan, address, platform, prototype, protoInstr);
|
||||
if (overwrite) {
|
||||
Instruction exists = doAdjustExisting(address, protoInstr);
|
||||
if (exists != null) {
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
|
||||
Instruction prec = doExtendPreceding(address, protoInstr);
|
||||
if (prec != null) {
|
||||
return prec;
|
||||
}
|
||||
|
||||
Instruction created =
|
||||
doCreate(lifespan, address, platform, protoInstr.getPrototype(), protoInstr);
|
||||
// copy override settings to replacement instruction
|
||||
if (protoInstr.isFallThroughOverridden()) {
|
||||
created.setFallThrough(protoInstr.getFallThrough());
|
||||
@@ -110,9 +205,9 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
}
|
||||
return created;
|
||||
}
|
||||
catch (CodeUnitInsertionException | AddressOverflowException e) {
|
||||
// End address already computed when protoInstr created.
|
||||
// We've also already checked for conflicts
|
||||
catch (AddressOverflowException | // End address already checked via protoInstr
|
||||
CodeUnitInsertionException | // We've already checked for conflicts
|
||||
CancelledException e) { // There's no monitor to cancel
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
@@ -160,8 +255,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
}
|
||||
|
||||
if (!skipDelaySlots.contains(startAddress)) {
|
||||
InstructionPrototype prototype = protoInstr.getPrototype();
|
||||
if (!areDelaySlots && prototype.hasDelaySlots()) {
|
||||
if (!areDelaySlots && protoInstr.getPrototype().hasDelaySlots()) {
|
||||
// Reverse their order then add them. This ensures pcode can be generated
|
||||
// for the delay-slotted instruction upon its creation.
|
||||
Deque<Instruction> delayed =
|
||||
@@ -172,8 +266,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
lastInstruction = replaceIfNotNull(lastInstruction,
|
||||
doAddInstructions(delayed.iterator(), true));
|
||||
}
|
||||
lastInstruction =
|
||||
doCreateInstruction(startAddress, prototype, protoInstr);
|
||||
lastInstruction = doCreateInstruction(startAddress, protoInstr);
|
||||
}
|
||||
if (errorAddress != null && conflictCodeUnit == null &&
|
||||
errorAddress.compareTo(startAddress) <= 0) {
|
||||
@@ -259,23 +352,8 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
Address endAddress = address.addNoWrap(prototype.getLength() - 1);
|
||||
AddressRangeImpl createdRange = new AddressRangeImpl(address, endAddress);
|
||||
|
||||
// First, truncate lifespan to the next code unit when upper bound is max
|
||||
if (!lifespan.maxIsFinite()) {
|
||||
lifespan = space.instructions.truncateSoonestDefined(lifespan, createdRange);
|
||||
lifespan = space.definedData.truncateSoonestDefined(lifespan, createdRange);
|
||||
}
|
||||
|
||||
// Second, truncate lifespan to the next change of bytes in the range
|
||||
// Then, check that against existing code units.
|
||||
DBTraceMemorySpace memSpace =
|
||||
space.trace.getMemoryManager().getMemorySpace(space.space, true);
|
||||
long endSnap = memSpace.getFirstChange(lifespan, createdRange);
|
||||
if (endSnap == Long.MIN_VALUE) {
|
||||
endSnap = lifespan.lmax();
|
||||
}
|
||||
else {
|
||||
endSnap--;
|
||||
}
|
||||
// Truncate, then check that against existing code units.
|
||||
long endSnap = computeTruncatedMax(lifespan, null, createdRange);
|
||||
TraceAddressSnapRange tasr =
|
||||
new ImmutableTraceAddressSnapRange(createdRange, lifespan.withMax(endSnap));
|
||||
|
||||
@@ -289,8 +367,8 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
DBTraceInstruction created = space.instructionMapSpace.put(tasr, null);
|
||||
created.set(platform, prototype, context);
|
||||
|
||||
cacheForContaining.notifyNewEntry(lifespan, createdRange, created);
|
||||
cacheForSequence.notifyNewEntry(lifespan, createdRange, created);
|
||||
cacheForContaining.notifyNewEntry(tasr.getLifespan(), createdRange, created);
|
||||
cacheForSequence.notifyNewEntry(tasr.getLifespan(), createdRange, created);
|
||||
space.undefinedData.invalidateCache();
|
||||
|
||||
// TODO: Save the context register into the context manager? Flow it?
|
||||
@@ -348,14 +426,19 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
* @param skipDelaySlots the addresses of delay-slotted instructions to skip
|
||||
* @param platform the instructions' platform (language, compiler)
|
||||
* @param block the block of instructions to add
|
||||
* @param overwrite true to overwrite existing defined units
|
||||
* @return the adder, or null
|
||||
*/
|
||||
protected InstructionBlockAdder startAddingBlock(Lifespan lifespan,
|
||||
Set<Address> skipDelaySlots, InternalTracePlatform platform, InstructionBlock block) {
|
||||
protected InstructionBlockAdder startAddingBlock(Lifespan lifespan, Set<Address> skipDelaySlots,
|
||||
InternalTracePlatform platform, InstructionBlock block, boolean overwrite) {
|
||||
if (overwrite) {
|
||||
return new InstructionBlockAdder(skipDelaySlots, lifespan, platform, block, null, null,
|
||||
null, overwrite);
|
||||
}
|
||||
InstructionError conflict = block.getInstructionConflict();
|
||||
if (conflict == null) {
|
||||
return new InstructionBlockAdder(skipDelaySlots, lifespan, platform, block, null, null,
|
||||
null);
|
||||
null, overwrite);
|
||||
}
|
||||
Address errorAddress = conflict.getInstructionAddress();
|
||||
if (errorAddress == null) {
|
||||
@@ -363,13 +446,13 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
}
|
||||
if (!conflict.getInstructionErrorType().isConflict) {
|
||||
return new InstructionBlockAdder(skipDelaySlots, lifespan, platform, block,
|
||||
errorAddress, conflict, null);
|
||||
errorAddress, conflict, null, overwrite);
|
||||
}
|
||||
long startSnap = lifespan.lmin();
|
||||
CodeUnit conflictCodeUnit =
|
||||
space.definedUnits.getAt(startSnap, conflict.getConflictAddress());
|
||||
return new InstructionBlockAdder(skipDelaySlots, lifespan, platform, block, errorAddress,
|
||||
conflict, conflictCodeUnit);
|
||||
conflict, conflictCodeUnit, overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,19 +551,14 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
|
||||
long startSnap = lifespan.lmin();
|
||||
Set<Address> skipDelaySlots = new HashSet<>();
|
||||
if (overwrite) {
|
||||
for (AddressRange range : instructionSet.getAddressSet()) {
|
||||
space.definedUnits.clear(lifespan, range, false, TaskMonitor.DUMMY);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!overwrite) {
|
||||
checkInstructionSet(startSnap, instructionSet, skipDelaySlots);
|
||||
}
|
||||
|
||||
// Add blocks
|
||||
for (InstructionBlock block : instructionSet) {
|
||||
InstructionBlockAdder adder =
|
||||
startAddingBlock(lifespan, skipDelaySlots, dbPlatform, block);
|
||||
startAddingBlock(lifespan, skipDelaySlots, dbPlatform, block, overwrite);
|
||||
if (adder == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -496,9 +574,6 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw new AssertionError(e); // No actual monitor
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
// Better have skipped any delay-slotted instructions whose delays overflowed
|
||||
throw new AssertionError(e);
|
||||
|
||||
+11
-4
@@ -299,21 +299,28 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
|
||||
return range;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "hiding" })
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void doSetRange(AddressRange range) {
|
||||
long newMinOffset = tree.mapSpace.assertInSpace(range.getMinAddress());
|
||||
long newMaxOffset = range.getMaxAddress().getOffset();
|
||||
if (minOffset == newMinOffset && maxOffset == newMaxOffset) {
|
||||
return;
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
DBTraceAddressSnapRangePropertyMapTree tree = this.tree;
|
||||
long newMinOffset = tree.mapSpace.assertInSpace(range.getMinAddress());
|
||||
tree.doUnparentEntry(this);
|
||||
minOffset = newMinOffset;
|
||||
maxOffset = range.getMaxAddress().getOffset();
|
||||
maxOffset = newMaxOffset;
|
||||
update(MIN_ADDRESS_COLUMN, MAX_ADDRESS_COLUMN);
|
||||
this.range = range;
|
||||
tree.doInsertDataEntry(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "hiding" })
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void doSetLifespan(Lifespan lifespan) {
|
||||
if (minSnap == lifespan.lmin() && maxSnap == lifespan.lmax()) {
|
||||
return;
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
DBTraceAddressSnapRangePropertyMapTree tree = this.tree;
|
||||
tree.doUnparentEntry(this);
|
||||
|
||||
+2
-1
@@ -634,7 +634,8 @@ public class DBTraceMemorySpace
|
||||
if (!remaining.isEmpty()) {
|
||||
lastSnap.snap = Long.MAX_VALUE;
|
||||
for (AddressRange rng : remaining) {
|
||||
changed.add(new ImmutableTraceAddressSnapRange(rng, Lifespan.nowOn(loc.snap)));
|
||||
changed.add(
|
||||
new ImmutableTraceAddressSnapRange(rng, Lifespan.nowOnMaybeScratch(loc.snap)));
|
||||
}
|
||||
}
|
||||
buf.position(pos);
|
||||
|
||||
+74
-33
@@ -17,11 +17,9 @@ package ghidra.trace.database.space;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import db.DBHandle;
|
||||
import db.DBRecord;
|
||||
import generic.CatenatedCollection;
|
||||
@@ -89,6 +87,19 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
|
||||
}
|
||||
}
|
||||
|
||||
private record Frame(TraceThread thread, int level) {
|
||||
}
|
||||
|
||||
private record TabledSpace(DBTraceSpaceEntry entry, AddressSpace space, TraceThread thread) {
|
||||
private boolean isRegisterSpace() {
|
||||
return space.isRegisterSpace();
|
||||
}
|
||||
|
||||
private Frame frame() {
|
||||
return new Frame(thread, entry.frameLevel);
|
||||
}
|
||||
}
|
||||
|
||||
protected final String name;
|
||||
protected final DBHandle dbh;
|
||||
protected final ReadWriteLock lock;
|
||||
@@ -100,7 +111,7 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
|
||||
// Note: use tree map so traversal is ordered by address space
|
||||
protected final Map<AddressSpace, M> memSpaces = new TreeMap<>();
|
||||
// Note: can use hash map here. I see no need to order these spaces
|
||||
protected final Map<Pair<TraceThread, Integer>, M> regSpaces = new HashMap<>();
|
||||
protected final Map<Frame, M> regSpaces = new HashMap<>();
|
||||
protected final Map<TraceObject, M> regSpacesByObject = new HashMap<>();
|
||||
|
||||
protected final Collection<M> memSpacesView =
|
||||
@@ -130,47 +141,61 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
|
||||
return DBTraceUtils.tableName(name, space, threadKey, frameLevel);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void loadSpaces() throws VersionException, IOException {
|
||||
for (DBTraceSpaceEntry ent : spaceStore.asMap().values()) {
|
||||
AddressFactory addressFactory = trace.getBaseAddressFactory();
|
||||
AddressSpace space;
|
||||
if (NO_ADDRESS_SPACE.getName().equals(ent.spaceName)) {
|
||||
space = NO_ADDRESS_SPACE;
|
||||
Map<Frame, TabledSpace> newRegSpaces = new HashMap<>();
|
||||
Map<AddressSpace, TabledSpace> newMemSpaces = new HashMap<>();
|
||||
for (TabledSpace ts : getTabledSpaces()) {
|
||||
if (ts.isRegisterSpace()) {
|
||||
newRegSpaces.put(ts.frame(), ts);
|
||||
}
|
||||
else {
|
||||
space = addressFactory.getAddressSpace(ent.spaceName);
|
||||
newMemSpaces.put(ts.space(), ts);
|
||||
}
|
||||
}
|
||||
regSpaces.keySet().retainAll(newRegSpaces.keySet());
|
||||
memSpaces.keySet().retainAll(newMemSpaces.keySet());
|
||||
for (Entry<Frame, TabledSpace> ent : newRegSpaces.entrySet()) {
|
||||
if (!regSpaces.containsKey(ent.getKey())) {
|
||||
regSpaces.put(ent.getKey(), createRegisterSpace(ent.getValue()));
|
||||
}
|
||||
}
|
||||
for (Entry<AddressSpace, TabledSpace> ent : newMemSpaces.entrySet()) {
|
||||
if (!memSpaces.containsKey(ent.getKey())) {
|
||||
memSpaces.put(ent.getKey(), createSpace(ent.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected AddressSpace getSpaceByName(AddressFactory factory, String name) {
|
||||
if (NO_ADDRESS_SPACE.getName().equals(name)) {
|
||||
return NO_ADDRESS_SPACE;
|
||||
}
|
||||
return factory.getAddressSpace(name);
|
||||
}
|
||||
|
||||
protected List<TabledSpace> getTabledSpaces() {
|
||||
AddressFactory factory = trace.getBaseAddressFactory();
|
||||
List<TabledSpace> result = new ArrayList<>();
|
||||
for (DBTraceSpaceEntry ent : spaceStore.asMap().values()) {
|
||||
AddressSpace space = getSpaceByName(factory, ent.spaceName);
|
||||
if (space == null) {
|
||||
Msg.error(this, "Space " + ent.spaceName + " does not exist in trace (language=" +
|
||||
baseLanguage + ").");
|
||||
continue;
|
||||
}
|
||||
else if (space.isRegisterSpace()) {
|
||||
if (space.isRegisterSpace()) {
|
||||
if (threadManager == null) {
|
||||
Msg.error(this, "Register spaces are not allowed without a thread manager.");
|
||||
continue;
|
||||
}
|
||||
TraceThread thread = threadManager.getThread(ent.threadKey);
|
||||
M regSpace;
|
||||
if (ent.space == null) {
|
||||
regSpace = createRegisterSpace(space, thread, ent);
|
||||
}
|
||||
else {
|
||||
regSpace = (M) ent.space;
|
||||
}
|
||||
regSpaces.put(ImmutablePair.of(thread, ent.getFrameLevel()), regSpace);
|
||||
result.add(new TabledSpace(ent, space, thread));
|
||||
}
|
||||
else {
|
||||
M memSpace;
|
||||
if (ent.space == null) {
|
||||
memSpace = createSpace(space, ent);
|
||||
}
|
||||
else {
|
||||
memSpace = (M) ent.space;
|
||||
}
|
||||
memSpaces.put(space, memSpace);
|
||||
result.add(new TabledSpace(ent, space, null));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected M getForSpace(AddressSpace space, boolean createIfAbsent) {
|
||||
@@ -210,7 +235,7 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
|
||||
return getForRegisterSpaceObjectThread((TraceObjectThread) thread, frameLevel,
|
||||
createIfAbsent);
|
||||
}
|
||||
Pair<TraceThread, Integer> frame = ImmutablePair.of(thread, frameLevel);
|
||||
Frame frame = new Frame(thread, frameLevel);
|
||||
if (!createIfAbsent) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return regSpaces.get(frame);
|
||||
@@ -340,6 +365,26 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
|
||||
protected abstract M createRegisterSpace(AddressSpace space, TraceThread thread,
|
||||
DBTraceSpaceEntry ent) throws VersionException, IOException;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private M createSpace(TabledSpace ts) throws VersionException, IOException {
|
||||
if (ts.entry.space != null) {
|
||||
return (M) ts.entry.space;
|
||||
}
|
||||
M space = createSpace(ts.space, ts.entry);
|
||||
ts.entry.space = space;
|
||||
return space;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private M createRegisterSpace(TabledSpace ts) throws VersionException, IOException {
|
||||
if (ts.entry.space != null) {
|
||||
return (M) ts.entry.space;
|
||||
}
|
||||
M space = createRegisterSpace(ts.space, ts.thread, ts.entry);
|
||||
ts.entry.space = space;
|
||||
return space;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbError(IOException e) {
|
||||
trace.dbError(e);
|
||||
@@ -349,10 +394,6 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
|
||||
public void invalidateCache(boolean all) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
spaceStore.invalidateCache();
|
||||
// TODO: Need to do a real delta here, not blow away and remake
|
||||
// Currently, object identities are not preserved by this operation
|
||||
memSpaces.clear();
|
||||
regSpaces.clear();
|
||||
loadSpaces();
|
||||
for (M m : memSpaces.values()) {
|
||||
m.invalidateCache();
|
||||
|
||||
+2
-11
@@ -20,17 +20,8 @@ import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
public interface DBTraceSpaceKey extends TraceAddressSpace {
|
||||
static class DefaultDBTraceSpaceKey implements DBTraceSpaceKey {
|
||||
private final TraceThread thread;
|
||||
private final AddressSpace space;
|
||||
private final int frameLevel;
|
||||
|
||||
private DefaultDBTraceSpaceKey(TraceThread thread, AddressSpace space, int frameLevel) {
|
||||
this.thread = thread;
|
||||
this.space = space;
|
||||
this.frameLevel = frameLevel;
|
||||
}
|
||||
|
||||
record DefaultDBTraceSpaceKey(TraceThread thread, AddressSpace space, int frameLevel)
|
||||
implements DBTraceSpaceKey {
|
||||
@Override
|
||||
public AddressSpace getAddressSpace() {
|
||||
return space;
|
||||
|
||||
+124
-1
@@ -22,6 +22,7 @@ import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.*;
|
||||
@@ -171,6 +172,23 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutBytesInScratchLeavesStaticDataUntouched() throws CodeUnitInsertionException {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
TraceData d0at4000 =
|
||||
b.addData(0, b.addr(0x4000), IntegerDataType.dataType, b.buf(1, 2, 3, 4));
|
||||
assertEquals(Lifespan.nowOn(0), d0at4000.getLifespan());
|
||||
assertEquals(new Scalar(32, 0x01020304), d0at4000.getValue());
|
||||
|
||||
b.trace.getMemoryManager().putBytes(-10, b.addr(0x4000), b.buf(5, 6, 7, 8));
|
||||
TraceData d10at4000 =
|
||||
b.trace.getCodeManager().definedData().getContaining(10, b.addr(0x4000));
|
||||
assertSame(d0at4000, d10at4000);
|
||||
assertEquals(Lifespan.nowOn(0), d10at4000.getLifespan());
|
||||
assertEquals(new Scalar(32, 0x01020304), d10at4000.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutBytesDeletesDynamicData() throws CodeUnitInsertionException {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
@@ -292,6 +310,110 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverlapErrorsMultithreaded() throws Throwable {
|
||||
ArrayList<CompletableFuture<Integer>> creators = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
creators.add(CompletableFuture.supplyAsync(() -> {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
return 0;
|
||||
}
|
||||
catch (CodeUnitInsertionException e) {
|
||||
return 1;
|
||||
}
|
||||
}));
|
||||
}
|
||||
CompletableFuture.allOf(creators.toArray(CompletableFuture[]::new)).get();
|
||||
assertEquals(9, creators.stream()
|
||||
.mapToInt(c -> c.getNow(null))
|
||||
.reduce(Integer::sum)
|
||||
.orElse(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverlapAllowedAfterAbort() throws Throwable {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
tid.abort();
|
||||
}
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
}
|
||||
}
|
||||
|
||||
public void testOverlapErrAfterInvalidate() throws Throwable {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
}
|
||||
b.trace.undo();
|
||||
b.trace.redo();
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
fail();
|
||||
}
|
||||
catch (CodeUnitInsertionException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is interesting because the pointer type def causes an update to the data type
|
||||
* settings <em>while the unit is still being created</em>. This will invalidate the trace's
|
||||
* caches. All of them, including the defined data units, which can become the cause of many
|
||||
* timing issues.
|
||||
*/
|
||||
@Test
|
||||
public void testOverlapErrWithDataTypeSettings() throws Throwable {
|
||||
AddressSpace space = b.trace.getBaseAddressFactory().getDefaultAddressSpace();
|
||||
PointerTypedef type = new PointerTypedef(null, VoidDataType.dataType, 8, null, space);
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), type);
|
||||
}
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), type);
|
||||
fail();
|
||||
}
|
||||
catch (CodeUnitInsertionException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverlapErrAfterSetEndSnap() throws Throwable {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
DBTraceDataAdapter data = b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
assertEquals(Lifespan.before(0), data.getLifespan());
|
||||
data.setEndSnap(-10);
|
||||
assertEquals(Lifespan.before(-9), data.getLifespan());
|
||||
}
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
b.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Lifespan.ALL, b.addr(0x4000), IntegerDataType.dataType);
|
||||
fail();
|
||||
}
|
||||
catch (CodeUnitInsertionException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertAllNullFunc(Function<TraceBaseCodeUnitsView<?>, TraceCodeUnit> func) {
|
||||
assertNull(func.apply(manager.codeUnits()));
|
||||
assertNull(func.apply(manager.data()));
|
||||
@@ -1538,7 +1660,8 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes
|
||||
coversTwoWays(manager.definedData(), Lifespan.span(0, 5), b.range(0x4000, 0x4007)));
|
||||
assertTrue(
|
||||
coversTwoWays(manager.definedData(), Lifespan.span(0, 9), b.range(0x4001, 0x4003)));
|
||||
assertFalse(intersectsTwoWays(manager.definedData(), Lifespan.ALL, b.range(0x0000, 0x3fff)));
|
||||
assertFalse(
|
||||
intersectsTwoWays(manager.definedData(), Lifespan.ALL, b.range(0x0000, 0x3fff)));
|
||||
assertFalse(
|
||||
intersectsTwoWays(manager.definedData(), Lifespan.ALL, b.range(0x4008, -0x0001)));
|
||||
assertFalse(intersectsTwoWays(manager.definedData(), Lifespan.toNow(-1), all));
|
||||
|
||||
+12
@@ -761,6 +761,18 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest
|
||||
assertEquals(4, data.getBytes(buf, 0));
|
||||
assertArrayEquals(b.arr(1, 2, 3, 4), buf.array());
|
||||
|
||||
buf = ByteBuffer.allocate(1);
|
||||
assertEquals(1, data.getBytes(buf, 4));
|
||||
assertArrayEquals(b.arr(0xeb), buf.array());
|
||||
|
||||
buf = ByteBuffer.allocate(1);
|
||||
assertEquals(1, data.getBytes(buf, 5));
|
||||
assertArrayEquals(b.arr(0xfe), buf.array());
|
||||
|
||||
buf = ByteBuffer.allocate(4);
|
||||
assertEquals(4, data.getBytes(buf, 2));
|
||||
assertArrayEquals(b.arr(3, 4, 0xeb, 0xfe), buf.array());
|
||||
|
||||
assertArrayEquals(b.arr(1, 2, 3, 4), data.getBytes());
|
||||
|
||||
byte[] arr = new byte[6];
|
||||
|
||||
+2
-6
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package ghidra.trace.database.module;
|
||||
|
||||
import static ghidra.lifecycle.Unfinished.TODO;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -334,7 +332,6 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("GP-479")
|
||||
public void testUndoIdentitiesPreserved() throws Exception {
|
||||
TraceModule mod1;
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
@@ -351,8 +348,7 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||
|
||||
b.trace.undo();
|
||||
|
||||
assertEquals(mod1, assertOne(moduleManager.getModulesByPath("Modules[first]")));
|
||||
TODO(); // TODO: mod1 should still be identical to that in database
|
||||
assertSame(mod1, assertOne(moduleManager.getModulesByPath("Modules[first]")));
|
||||
assertTrue(moduleManager.getModulesByPath("Modules[second]").isEmpty());
|
||||
}
|
||||
|
||||
|
||||
+140
-3
@@ -19,15 +19,19 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.app.cmd.disassemble.*;
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.disassemble.Disassembler;
|
||||
import ghidra.program.model.address.AddressOverflowException;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
@@ -37,7 +41,8 @@ import ghidra.trace.database.guest.DBTraceGuestPlatform;
|
||||
import ghidra.trace.database.listing.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.listing.TraceCodeUnit;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.memory.TraceOverlappedRegionException;
|
||||
import ghidra.trace.util.LanguageTestWatcher;
|
||||
@@ -306,4 +311,136 @@ public class DBTraceDisassemblerIntegrationTest extends AbstractGhidraHeadlessIn
|
||||
assertEquals("MOV ECX,EAX", cu2.toString());
|
||||
}
|
||||
}
|
||||
|
||||
record Repetition(Lifespan lifespan, boolean overwrite) {
|
||||
}
|
||||
|
||||
protected <T> List<T> toList(Iterable<? extends T> it) {
|
||||
return StreamSupport.stream(it.spliterator(), false).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected void runTestCoalesceInstructions(List<Repetition> repetitions) throws Exception {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
DBTraceMemoryManager memory = b.trace.getMemoryManager();
|
||||
DBTraceCodeManager code = b.trace.getCodeManager();
|
||||
|
||||
memory.createRegion(".text", 0, b.range(0x00400000, 0x00400fff));
|
||||
Assembler asm = Assemblers.getAssembler(b.language);
|
||||
Address entry = b.addr(0x00400000);
|
||||
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
|
||||
buf.assemble("imm r0, #123");
|
||||
buf.assemble("mov r1, r0");
|
||||
buf.assemble("ret");
|
||||
|
||||
long snap = Lifespan.isScratch(repetitions.get(0).lifespan.lmin()) ? Long.MIN_VALUE : 0;
|
||||
memory.putBytes(snap, entry, ByteBuffer.wrap(buf.getBytes()));
|
||||
|
||||
AddressFactory factory = b.trace.getBaseAddressFactory();
|
||||
Disassembler dis =
|
||||
Disassembler.getDisassembler(b.language, factory, TaskMonitor.DUMMY, null);
|
||||
InstructionSet set = new InstructionSet(factory);
|
||||
set.addBlock(dis.pseudoDisassembleBlock(memory.getBufferAt(snap, entry), null, 10));
|
||||
|
||||
List<TraceCodeUnit> units = null;
|
||||
TraceAddressSnapRange all =
|
||||
new ImmutableTraceAddressSnapRange(b.range(0, -1), Lifespan.ALL);
|
||||
for (Repetition rep : repetitions) {
|
||||
code.instructions().addInstructionSet(rep.lifespan, set, rep.overwrite);
|
||||
if (units == null) {
|
||||
units = toList(code.definedUnits().getIntersecting(all));
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Technically, getIntersecting makes no guarantee regarding order.
|
||||
* Nevertheless, the structure shouldn't be perturbed, so I think it's fair to
|
||||
* expect the same order.
|
||||
*/
|
||||
assertEquals(units, toList(code.definedUnits().getIntersecting(all)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestLanguage(ProgramBuilder._TOY64_BE)
|
||||
public void testCoalesceInstructionsMinTwiceNoOverwrite() throws Exception {
|
||||
runTestCoalesceInstructions(List.of(
|
||||
new Repetition(Lifespan.nowOn(Long.MIN_VALUE), false),
|
||||
new Repetition(Lifespan.nowOn(Long.MIN_VALUE), false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestLanguage(ProgramBuilder._TOY64_BE)
|
||||
public void testCoalesceInstructionsMinTwiceYesOverwrite() throws Exception {
|
||||
runTestCoalesceInstructions(List.of(
|
||||
new Repetition(Lifespan.nowOn(Long.MIN_VALUE), true),
|
||||
new Repetition(Lifespan.nowOn(Long.MIN_VALUE), true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestLanguage(ProgramBuilder._TOY64_BE)
|
||||
public void testCoalesceInstructionsZeroTwiceYesOverwrite() throws Exception {
|
||||
runTestCoalesceInstructions(List.of(
|
||||
new Repetition(Lifespan.nowOn(0), true),
|
||||
new Repetition(Lifespan.nowOn(0), true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestLanguage(ProgramBuilder._TOY64_BE)
|
||||
public void testCoalesceInstructionsZeroThenOneYesOverwrite() throws Exception {
|
||||
runTestCoalesceInstructions(List.of(
|
||||
new Repetition(Lifespan.nowOn(0), true),
|
||||
new Repetition(Lifespan.nowOn(1), true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestLanguage(ProgramBuilder._TOY64_BE)
|
||||
public void testCoalesceInstructionsZeroOnlyThenOneNoOverwrite() throws Exception {
|
||||
runTestCoalesceInstructions(List.of(
|
||||
new Repetition(Lifespan.at(0), false),
|
||||
new Repetition(Lifespan.nowOn(1), false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestLanguage(ProgramBuilder._TOY64_BE)
|
||||
public void testCoalesceInstructionsZeroOnlyThenOneYesOverwrite() throws Exception {
|
||||
runTestCoalesceInstructions(List.of(
|
||||
new Repetition(Lifespan.at(0), true),
|
||||
new Repetition(Lifespan.nowOn(1), true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCoalesceAcrossByteChanges() throws Exception {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
DBTraceMemoryManager memory = b.trace.getMemoryManager();
|
||||
DBTraceCodeManager code = b.trace.getCodeManager();
|
||||
|
||||
memory.createRegion(".text", 0, b.range(0x00400000, 0x00400fff));
|
||||
Assembler asm = Assemblers.getAssembler(b.language);
|
||||
Address entry = b.addr(0x00400000);
|
||||
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
|
||||
buf.assemble("imm r0, #123");
|
||||
buf.assemble("mov r1, r0");
|
||||
buf.assemble("ret");
|
||||
|
||||
memory.putBytes(-1, entry, ByteBuffer.wrap(buf.getBytes()));
|
||||
|
||||
AddressFactory factory = b.trace.getBaseAddressFactory();
|
||||
Disassembler dis =
|
||||
Disassembler.getDisassembler(b.language, factory, TaskMonitor.DUMMY, null);
|
||||
InstructionSet set = new InstructionSet(factory);
|
||||
set.addBlock(dis.pseudoDisassembleBlock(memory.getBufferAt(-1, entry), null, 10));
|
||||
|
||||
TraceAddressSnapRange all =
|
||||
new ImmutableTraceAddressSnapRange(b.range(0, -1), Lifespan.ALL);
|
||||
code.instructions().addInstructionSet(Lifespan.nowOn(-1), set, true);
|
||||
/**
|
||||
* This is already a bogus sort of operation: The prototypes may not match the bytes. In
|
||||
* any case, we should not expect coalescing.
|
||||
*/
|
||||
code.instructions().addInstructionSet(Lifespan.nowOn(0), set, true);
|
||||
List<TraceCodeUnit> units = toList(code.definedUnits().getIntersecting(all));
|
||||
assertEquals(6, units.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ public class PcodeExecutor<T> {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new PcodeExecutionException("Exception during pcode execution", frame, e);
|
||||
throw new PcodeExecutionException(e.getMessage(), frame, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -326,4 +326,8 @@ public class DBAnnotatedObject extends DatabaseObject {
|
||||
public boolean isDeleted() {
|
||||
return super.isDeleted(adapter.getLock());
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return store.getTableName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,6 +1275,15 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the table backing this store
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate this store's cache
|
||||
*
|
||||
|
||||
+6
-4
@@ -82,7 +82,7 @@ public abstract class AbstractConstraintsTree< //
|
||||
protected abstract NR createNodeEntry(DBCachedObjectStore<NR> store, DBRecord record);
|
||||
|
||||
protected void init() {
|
||||
assert root == null;
|
||||
// assert root == null;
|
||||
root = getOrCreateRoot();
|
||||
leafLevel = computeLeafLevel();
|
||||
}
|
||||
@@ -843,6 +843,7 @@ public abstract class AbstractConstraintsTree< //
|
||||
throw new AssertionError(
|
||||
"Leaf node " + n + " cannot contain nodes " + nodeChildren);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check that child count matches by counting over iterator
|
||||
@@ -911,9 +912,6 @@ public abstract class AbstractConstraintsTree< //
|
||||
" out of sync: cache=" + cachedChildren + " db=" + databasedChildren);
|
||||
}
|
||||
}
|
||||
if (leafLevel != computeLeafLevel()) {
|
||||
throw new AssertionError("Leaf level is incorrect");
|
||||
}
|
||||
visit(null, new TreeRecordVisitor() {
|
||||
@Override
|
||||
protected VisitResult beginNode(NR parent, NR n, QueryInclusion inclusion) {
|
||||
@@ -927,6 +925,9 @@ public abstract class AbstractConstraintsTree< //
|
||||
return VisitResult.NEXT;
|
||||
}
|
||||
}, false);
|
||||
if (leafLevel != computeLeafLevel()) {
|
||||
throw new AssertionError("Leaf level is incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract AbstractConstraintsTreeSpatialMap<DS, DR, NS, T, Q> asSpatialMap();
|
||||
@@ -945,6 +946,7 @@ public abstract class AbstractConstraintsTree< //
|
||||
cachedNodeChildren.clear();
|
||||
dataStore.invalidateCache();
|
||||
nodeStore.invalidateCache();
|
||||
init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user