mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Merge remote-tracking branch
'origin/GP-1678_Dan_objectRecorder--SQUASHED' Conflicts: Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java
This commit is contained in:
commit
0e8e418bfa
110 changed files with 3191 additions and 497 deletions
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.AbstractPcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
|
@ -96,7 +98,8 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
|
|||
ls.writeCacheDown(trace, destSnap, traceThread, 0);
|
||||
if (synthesizeStacks) {
|
||||
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
|
||||
stack.getFrame(0, true).setProgramCounter(emuThread.getCounter());
|
||||
stack.getFrame(0, true)
|
||||
.setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -268,7 +268,9 @@ public class DBTraceObjectBreakpointLocation
|
|||
@Override
|
||||
public TraceObjectBreakpointSpec getSpecification() {
|
||||
try (LockHold hold = object.getTrace().lockRead()) {
|
||||
return object.queryAncestorsInterface(getLifespan(), TraceObjectBreakpointSpec.class)
|
||||
return object
|
||||
.queryCanonicalAncestorsInterface(getLifespan(),
|
||||
TraceObjectBreakpointSpec.class)
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package ghidra.trace.database.breakpoint;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
@ -31,6 +32,7 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.Trace.TraceBreakpointChangeType;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||
|
@ -46,7 +48,7 @@ public class DBTraceObjectBreakpointSpec
|
|||
implements TraceObjectBreakpointSpec, DBTraceObjectInterface {
|
||||
private final DBTraceObject object;
|
||||
|
||||
private Set<TraceBreakpointKind> kinds;
|
||||
private TraceBreakpointKindSet kinds;
|
||||
|
||||
public DBTraceObjectBreakpointSpec(DBTraceObject object) {
|
||||
this.object = object;
|
||||
|
@ -147,28 +149,25 @@ public class DBTraceObjectBreakpointSpec
|
|||
// TODO: Target-Trace mapping is implied by encoded name. Seems bad.
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
|
||||
kinds.stream().map(k -> k.name()).collect(Collectors.joining(",")));
|
||||
this.kinds = Set.copyOf(kinds);
|
||||
TraceBreakpointKindSet.encode(kinds));
|
||||
this.kinds = TraceBreakpointKindSet.copyOf(kinds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TraceBreakpointKind> getKinds() {
|
||||
Set<TraceBreakpointKind> result = new HashSet<>();
|
||||
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
|
||||
TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, String.class, null);
|
||||
if (kindsStr == null) {
|
||||
return kinds;
|
||||
}
|
||||
for (String name : kindsStr.split(",")) {
|
||||
try {
|
||||
result.add(TraceBreakpointKind.valueOf(name));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.warn(this, "Could not decode breakpoint kind from trace database: " + name);
|
||||
}
|
||||
try {
|
||||
return kinds = TraceBreakpointKindSet.decode(kindsStr, true);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.warn(this, "Unrecognized breakpoint kind(s) in trace database: " + e);
|
||||
return kinds = TraceBreakpointKindSet.decode(kindsStr, false);
|
||||
}
|
||||
return kinds = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -210,13 +209,17 @@ public class DBTraceObjectBreakpointSpec
|
|||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
||||
String key = cast.getAffectedObject().getEntryKey();
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
String key = affected.getEntryKey();
|
||||
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
|
||||
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key);
|
||||
if (!applies) {
|
||||
return null;
|
||||
}
|
||||
assert cast.getAffectedObject().getParent() == object;
|
||||
assert affected.getParent() == object;
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return null; // Incomplete object
|
||||
}
|
||||
for (TraceObjectBreakpointLocation loc : getLocations()) {
|
||||
DBTraceObjectBreakpointLocation dbLoc = (DBTraceObjectBreakpointLocation) loc;
|
||||
TraceAddressSpace space = dbLoc.getTraceAddressSpace();
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.google.common.collect.Range;
|
|||
import ghidra.dbg.target.TargetMemoryRegion;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
|
@ -71,6 +72,27 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
protected TraceChangeType<TraceMemoryRegion, Void> getDeletedType() {
|
||||
return TraceMemoryRegionChangeType.DELETED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraAdded() {
|
||||
updateViewsAdded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
updateViewsLifespanChanged(oldLifespan, newLifespan);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
|
||||
Object newValue) {
|
||||
updateViewsValueChanged(lifespan, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraDeleted() {
|
||||
updateViewsDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
private final DBTraceObject object;
|
||||
|
@ -96,7 +118,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
public void setName(String name) {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||
object.getTrace().updateViewsChangeRegionBlockName(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +137,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
}
|
||||
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
|
||||
newLifespan);
|
||||
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +177,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
return;
|
||||
}
|
||||
object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
|
||||
object.getTrace().updateViewsChangeRegionBlockRange(this, oldRange, newRange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +248,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
Boolean val = flags.contains(flag) ? true : null;
|
||||
object.setValue(lifespan, keyForFlag(flag), val);
|
||||
}
|
||||
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +257,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
for (TraceMemoryFlag flag : flags) {
|
||||
object.setValue(lifespan, keyForFlag(flag), true);
|
||||
}
|
||||
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +266,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
for (TraceMemoryFlag flag : flags) {
|
||||
object.setValue(lifespan, keyForFlag(flag), null);
|
||||
}
|
||||
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,7 +312,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
public void delete() {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.deleteTree();
|
||||
object.getTrace().updateViewsDeleteRegionBlock(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,4 +324,35 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||
return translator.translate(rec);
|
||||
}
|
||||
|
||||
protected void updateViewsAdded() {
|
||||
object.getTrace().updateViewsAddRegionBlock(this);
|
||||
}
|
||||
|
||||
protected void updateViewsLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
|
||||
}
|
||||
|
||||
protected void updateViewsValueChanged(Range<Long> lifespan, String key, Object oldValue,
|
||||
Object newValue) {
|
||||
DBTrace trace = object.getTrace();
|
||||
switch (key) {
|
||||
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
|
||||
trace.updateViewsChangeRegionBlockRange(this,
|
||||
(AddressRange) oldValue, (AddressRange) newValue);
|
||||
return;
|
||||
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
|
||||
trace.updateViewsChangeRegionBlockName(this);
|
||||
return;
|
||||
case TargetMemoryRegion.READABLE_ATTRIBUTE_NAME:
|
||||
case TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME:
|
||||
case TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME:
|
||||
trace.updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateViewsDeleted() {
|
||||
object.getTrace().updateViewsDeleteRegionBlock(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,15 @@ import ghidra.dbg.target.TargetRegister;
|
|||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.memory.TraceObjectRegister;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||
import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
|
||||
public class DBTraceObjectRegister implements TraceObjectRegister {
|
||||
public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObjectInterface {
|
||||
private final DBTraceObject object;
|
||||
|
||||
public DBTraceObjectRegister(DBTraceObject object) {
|
||||
|
@ -102,4 +104,10 @@ public class DBTraceObjectRegister implements TraceObjectRegister {
|
|||
return TraceMemoryState.values()[TraceObjectInterfaceUtils.getValue(object, snap, KEY_STATE,
|
||||
Integer.class, TraceMemoryState.UNKNOWN.ordinal())];
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||
// TODO: Once we decide how to map registers....
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,14 +107,14 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
|||
@Override
|
||||
public void setName(String name) {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||
object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
|
||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
||||
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,7 +24,8 @@ import com.google.common.collect.Range;
|
|||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.trace.database.target.*;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
import ghidra.trace.model.Trace.TraceStackChangeType;
|
||||
import ghidra.trace.model.stack.*;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
@ -52,7 +53,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
}
|
||||
|
||||
@Override
|
||||
protected TraceChangeType<TraceStack, Void> getChangedType() {
|
||||
protected TraceChangeType<TraceStack, ?> getChangedType() {
|
||||
return TraceStackChangeType.CHANGED;
|
||||
}
|
||||
|
||||
|
@ -106,7 +107,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
PathMatcher matcher = object.getTargetSchema().searchFor(TargetStackFrame.class, true);
|
||||
List<String> relKeyList =
|
||||
matcher.applyIndices(PathUtils.makeIndex(level)).getSingletonPath();
|
||||
matcher.applyKeys(PathUtils.makeIndex(level)).getSingletonPath();
|
||||
if (relKeyList == null) {
|
||||
throw new IllegalStateException("Could not determine where to create new frame");
|
||||
}
|
||||
|
@ -117,8 +118,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
}
|
||||
|
||||
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
|
||||
// TODO: All attributes or just those known to StackFrame?
|
||||
to.setProgramCounter(from.getProgramCounter());
|
||||
// TODO: All attributes within a given span, intersected to that span?
|
||||
to.setProgramCounter(to.getObject().getLifespan(),
|
||||
from.getProgramCounter(from.getObject().getMaxSnap()));
|
||||
}
|
||||
|
||||
protected void shiftFrameAttributes(int from, int to, int count,
|
||||
|
@ -141,12 +143,13 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) {
|
||||
for (int i = start; i < end; i++) {
|
||||
TraceObjectStackFrame frame = frames.get(i);
|
||||
frame.setProgramCounter(null);
|
||||
frame.setProgramCounter(frame.getObject().getLifespan(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDepth(int depth, boolean atInner) {
|
||||
// TODO: Need a span parameter
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
List<TraceObjectStackFrame> frames = // Want mutable list
|
||||
doGetFrames().collect(Collectors.toCollection(ArrayList::new));
|
||||
|
@ -179,7 +182,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
protected TraceStackFrame doGetFrame(int level) {
|
||||
TargetObjectSchema schema = object.getTargetSchema();
|
||||
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
|
||||
matcher = matcher.applyIndices(PathUtils.makeIndex(level));
|
||||
matcher = matcher.applyKeys(PathUtils.makeIndex(level));
|
||||
return object.getSuccessors(object.getLifespan(), matcher)
|
||||
.findAny()
|
||||
.map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class))
|
||||
|
|
|
@ -17,6 +17,8 @@ package ghidra.trace.database.stack;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -72,23 +74,25 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
}
|
||||
|
||||
@Override
|
||||
public Address getProgramCounter() {
|
||||
return TraceObjectInterfaceUtils.getValue(object, object.getMaxSnap(),
|
||||
public Address getProgramCounter(long snap) {
|
||||
return TraceObjectInterfaceUtils.getValue(object, snap,
|
||||
TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgramCounter(Address pc) {
|
||||
public void setProgramCounter(Range<Long> span, Address pc) {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
if (pc == Address.NO_ADDRESS) {
|
||||
pc = null;
|
||||
}
|
||||
object.setValue(object.getLifespan(), TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
|
||||
object.setValue(object.getLifespan().intersection(span),
|
||||
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
// TODO: Do I need to add a snap argument?
|
||||
// TODO: One day, we'll have dynamic columns in the debugger
|
||||
/**
|
||||
* I don't use an attribute for this, because there's not a nice way track the "identity" of
|
||||
|
@ -101,7 +105,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
* follow the "same frame" as its level changes.
|
||||
*/
|
||||
try (LockHold hold = object.getTrace().lockRead()) {
|
||||
Address pc = getProgramCounter();
|
||||
Address pc = getProgramCounter(object.getMaxSnap());
|
||||
return pc == null ? null
|
||||
: object.getTrace()
|
||||
.getCommentAdapter()
|
||||
|
@ -111,11 +115,12 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
|
||||
@Override
|
||||
public void setComment(String comment) {
|
||||
// TODO: Do I need to add a span argument?
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.getTrace()
|
||||
.getCommentAdapter()
|
||||
.setComment(object.getLifespan(), getProgramCounter(), CodeUnit.EOL_COMMENT,
|
||||
comment);
|
||||
.setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()),
|
||||
CodeUnit.EOL_COMMENT, comment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,23 +129,38 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
return object;
|
||||
}
|
||||
|
||||
protected boolean isPcChange(TraceChangeRecord<?, ?> rec) {
|
||||
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
|
||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
||||
return TargetStackFrame.PC_ATTRIBUTE_NAME.equals(cast.getAffectedObject().getEntryKey());
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
assert affected.getParent() == object;
|
||||
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
|
||||
return false;
|
||||
}
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected long snapFor(TraceChangeRecord<?, ?> rec) {
|
||||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
||||
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap();
|
||||
}
|
||||
return object.getMinSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType() ||
|
||||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() ||
|
||||
rec.getEventType() == TraceObjectChangeType.DELETED.getType() ||
|
||||
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() &&
|
||||
isPcChange(rec)) {
|
||||
// NB. Affected object may be the wrapped object, or the value entry
|
||||
changeApplies(rec)) {
|
||||
TraceAddressSpace space =
|
||||
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME);
|
||||
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, getStack(), null,
|
||||
null);
|
||||
TraceObjectStack stack = getStack();
|
||||
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack,
|
||||
0L, snapFor(rec));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -212,8 +212,8 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
|
|||
}
|
||||
doUpdateFrameKeys();
|
||||
}
|
||||
manager.trace
|
||||
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this));
|
||||
manager.trace.setChanged(
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this, 0L, getSnap()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,6 +18,8 @@ package ghidra.trace.database.stack;
|
|||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -98,12 +100,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
|||
}
|
||||
|
||||
@Override
|
||||
public Address getProgramCounter() {
|
||||
public Address getProgramCounter(long snap) {
|
||||
return pc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgramCounter(Address pc) {
|
||||
public void setProgramCounter(Range<Long> span, Address pc) {
|
||||
//System.err.println("setPC(threadKey=" + stack.getThread().getKey() + ",snap=" +
|
||||
// stack.getSnap() + ",level=" + level + ",pc=" + pc + ");");
|
||||
manager.trace.assertValidAddress(pc);
|
||||
|
@ -115,7 +117,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
|||
update(PC_COLUMN);
|
||||
}
|
||||
manager.trace.setChanged(
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack));
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
|
||||
stack.getSnap()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,7 +133,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
|||
update(COMMENT_COLUMN);
|
||||
}
|
||||
manager.trace.setChanged(
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack));
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
|
||||
stack.getSnap()));
|
||||
}
|
||||
|
||||
@Internal
|
||||
|
|
|
@ -205,6 +205,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap);
|
||||
}
|
||||
|
||||
protected void doSetLifespanAndEmit(Range<Long> lifespan) {
|
||||
Range<Long> oldLifespan = getLifespan();
|
||||
doSetLifespan(lifespan);
|
||||
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
|
||||
oldLifespan, lifespan));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTrace getTrace() {
|
||||
return manager.trace;
|
||||
|
@ -247,6 +254,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue getCanonicalParent(long snap) {
|
||||
// TODO: If this is invoked often, perhaps keep as field
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (isRoot()) {
|
||||
return manager.valueStore.getObjectAt(0);
|
||||
}
|
||||
String canonicalKey = path.key();
|
||||
TraceObjectKeyPath canonicalTail = path.parent();
|
||||
return manager.valuesByChild.getLazily(this)
|
||||
.stream()
|
||||
.filter(v -> canonicalKey.equals(v.getEntryKey()))
|
||||
.filter(v -> v.getLifespan().contains(snap))
|
||||
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRoot() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
|
@ -275,10 +301,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
public void setLifespan(Range<Long> lifespan) {
|
||||
// TODO: Could derive fixed attributes from schema and set their lifespans, too....
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
Range<Long> oldLifespan = getLifespan();
|
||||
doSetLifespan(lifespan);
|
||||
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
|
||||
oldLifespan, lifespan));
|
||||
doSetLifespanAndEmit(lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,7 +358,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
@Override
|
||||
public Collection<? extends DBTraceObjectValue> getParents() {
|
||||
return manager.valuesByChild.get(this);
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return manager.valuesByChild.get(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) {
|
||||
|
@ -422,6 +447,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Could/should this return Stream instead?
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(Range<Long> span,
|
||||
String key) {
|
||||
return doGetValues(DBTraceUtils.lowerEndpoint(span), DBTraceUtils.upperEndpoint(span), key);
|
||||
|
@ -638,8 +664,15 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getSuccessors(
|
||||
Range<Long> span, PathPredicates relativePredicates) {
|
||||
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetSuccessors(span, DBTraceObjectValPath.of(), relativePredicates);
|
||||
Stream<? extends DBTraceObjectValPath> succcessors =
|
||||
doGetSuccessors(span, empty, relativePredicates);
|
||||
if (relativePredicates.matches(List.of())) {
|
||||
// Pre-cat the empty path (not the empty stream)
|
||||
return Stream.concat(Stream.of(empty), succcessors);
|
||||
}
|
||||
return succcessors;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -647,7 +680,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
|
||||
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
|
||||
if (nextKeys.isEmpty()) {
|
||||
return null;
|
||||
return Stream.empty();
|
||||
}
|
||||
if (nextKeys.size() != 1) {
|
||||
throw new IllegalArgumentException("predicates must be a singleton");
|
||||
|
@ -663,8 +696,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getOrderedSuccessors(Range<Long> span,
|
||||
TraceObjectKeyPath relativePath, boolean forward) {
|
||||
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetOrderedSuccessors(span, DBTraceObjectValPath.of(),
|
||||
if (relativePath.isRoot()) {
|
||||
return Stream.of(empty); // Not the empty stream
|
||||
}
|
||||
return doGetOrderedSuccessors(span, empty,
|
||||
new PathPattern(relativePath.getKeyList()), forward);
|
||||
}
|
||||
}
|
||||
|
@ -686,22 +723,22 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
if (key == TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME &&
|
||||
value instanceof Address) {
|
||||
address = (Address) value;
|
||||
Object lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
InternalTraceObjectValue lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME);
|
||||
if (!(lengthObj instanceof Integer)) {
|
||||
if (lengthObj == null || !(lengthObj.getValue() instanceof Integer)) {
|
||||
return;
|
||||
}
|
||||
length = (Integer) lengthObj;
|
||||
length = (Integer) lengthObj.getValue();
|
||||
}
|
||||
else if (key == TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME &&
|
||||
value instanceof Integer) {
|
||||
length = (Integer) value;
|
||||
Object addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
InternalTraceObjectValue addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME);
|
||||
if (!(addressObj instanceof Address)) {
|
||||
if (addressObj == null || !(addressObj.getValue() instanceof Address)) {
|
||||
return;
|
||||
}
|
||||
address = (Address) addressObj;
|
||||
address = (Address) addressObj.getValue();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
|
@ -715,6 +752,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
protected void emitIfCanonicalInsertion(InternalTraceObjectValue value) {
|
||||
if (value == null || !(value.getValue() instanceof DBTraceObject)) {
|
||||
return;
|
||||
}
|
||||
DBTraceObject child = (DBTraceObject) value.getValue();
|
||||
if (this.path.extend(value.getEntryKey()).equals(child.getCanonicalPath())) {
|
||||
child.emitEvents(
|
||||
new TraceChangeRecord<>(TraceObjectChangeType.INSERTED, null, child, value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
|
||||
ConflictResolution resolution) {
|
||||
|
@ -750,6 +798,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
// NB. It will cause another event. good.
|
||||
applyBreakpointRangeHack(lifespan, key, value, resolution);
|
||||
emitIfCanonicalInsertion(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -783,18 +832,23 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
||||
Class<I> ifClass) {
|
||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
public Stream<? extends DBTraceObjectValPath> queryAncestorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
||||
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
||||
return getAncestors(span, matcher).map(p -> p.getFirstParent(this).queryInterface(ifClass));
|
||||
return getAncestors(span, matcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
||||
Range<Long> span, Class<I> ifClass) {
|
||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
||||
Class<I> ifClass) {
|
||||
return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||
.map(p -> p.getFirstParent(this).queryInterface(ifClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
||||
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
||||
List<String> parentPath = getCanonicalPath().getKeyList();
|
||||
|
@ -806,20 +860,35 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath))
|
||||
.stream()
|
||||
.filter(o -> DBTraceUtils.intersect(span, o.getLifespan()))
|
||||
.map(o -> o.queryInterface(ifClass))
|
||||
.filter(i -> i != null);
|
||||
// TODO: Post filter until GP-1301
|
||||
.filter(o -> o.getTargetSchema().getInterfaces().contains(targetIf));
|
||||
}
|
||||
}
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
||||
Range<Long> span, Class<I> ifClass) {
|
||||
return queryCanonicalAncestorsTargetInterface(span,
|
||||
TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||
.map(o -> o.queryInterface(ifClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
|
||||
// TODO: Post filter until GP-1301
|
||||
return getSuccessors(span, matcher).filter(
|
||||
p -> p.getLastChild(this).getTargetSchema().getInterfaces().contains(targetIf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> querySuccessorsInterface(Range<Long> span,
|
||||
Class<I> ifClass) {
|
||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
|
||||
return getSuccessors(span, matcher).map(p -> p.getLastChild(this).queryInterface(ifClass))
|
||||
.filter(i -> i != null); // because GP-1301
|
||||
return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||
.map(p -> p.getLastChild(this).queryInterface(ifClass));
|
||||
}
|
||||
|
||||
protected void doDelete() {
|
||||
|
@ -869,13 +938,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
List<Range<Long>> removed = DBTraceUtils.subtract(lifespan, span);
|
||||
if (removed.isEmpty()) {
|
||||
doDeleteReferringValues();
|
||||
doDelete();
|
||||
return null;
|
||||
}
|
||||
if (removed.size() == 2) {
|
||||
throw new IllegalArgumentException("Cannot create a gap in an object's lifespan");
|
||||
}
|
||||
doSetLifespan(removed.get(0));
|
||||
doSetLifespanAndEmit(removed.get(0));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,21 +40,39 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
|
||||
protected abstract TraceChangeType<T, Range<Long>> getLifespanChangedType();
|
||||
|
||||
protected abstract TraceChangeType<T, Void> getChangedType();
|
||||
protected abstract TraceChangeType<T, ?> getChangedType();
|
||||
|
||||
protected abstract boolean appliesToKey(String key);
|
||||
|
||||
protected abstract TraceChangeType<T, Void> getDeletedType();
|
||||
|
||||
protected void emitExtraAdded() {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
|
||||
Object newValue) {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
protected void emitExtraDeleted() {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
|
||||
TraceAddressSpace space = spaceValueKey == null ? null
|
||||
: spaceForValue(object, object.getMinSnap(), spaceValueKey);
|
||||
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType()) {
|
||||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType()) {
|
||||
TraceChangeType<T, Void> type = getAddedType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
assert rec.getAffectedObject() == object;
|
||||
emitExtraAdded();
|
||||
return new TraceChangeRecord<>(type, space, iface, null,
|
||||
null);
|
||||
}
|
||||
|
@ -69,6 +87,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
assert rec.getAffectedObject() == object;
|
||||
TraceChangeRecord<TraceObject, Range<Long>> cast =
|
||||
TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec);
|
||||
emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue());
|
||||
return new TraceChangeRecord<>(type, space, iface,
|
||||
cast.getOldValue(), cast.getNewValue());
|
||||
}
|
||||
|
@ -76,17 +95,23 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
if (object.isDeleted()) {
|
||||
return null;
|
||||
}
|
||||
TraceChangeType<T, Void> type = getChangedType();
|
||||
TraceChangeType<T, ?> type = getChangedType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
||||
String key = cast.getAffectedObject().getEntryKey();
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
String key = affected.getEntryKey();
|
||||
if (!appliesToKey(key)) {
|
||||
return null;
|
||||
}
|
||||
assert cast.getAffectedObject().getParent() == object;
|
||||
assert affected.getParent() == object;
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return null; // Object is not complete
|
||||
}
|
||||
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
|
||||
cast.getNewValue());
|
||||
return new TraceChangeRecord<>(type, space, iface, null, null);
|
||||
}
|
||||
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
|
||||
|
@ -95,6 +120,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
return null;
|
||||
}
|
||||
assert rec.getAffectedObject() == object;
|
||||
emitExtraDeleted();
|
||||
return new TraceChangeRecord<>(type, space, iface, null, null);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -43,7 +43,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd
|
|||
import ghidra.trace.database.module.TraceObjectSection;
|
||||
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
|
@ -61,6 +60,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec;
|
||||
|
@ -235,7 +235,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
}
|
||||
|
||||
protected Object validatePrimitive(Object child) {
|
||||
PrimitiveCodec.getCodec(child.getClass());
|
||||
try {
|
||||
PrimitiveCodec.getCodec(child.getClass());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Cannot encode " + child, e);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
|
@ -300,7 +305,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
setSchema(schema);
|
||||
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all());
|
||||
assert root.getKey() == 0;
|
||||
InternalTraceObjectValue val = doCreateValue(Range.all(), null, null, root);
|
||||
InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
|
||||
assert val.getKey() == 0;
|
||||
return val;
|
||||
}
|
||||
|
@ -378,6 +383,14 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
PathMatcher matcher = rootSchema.searchFor(targetIf, true);
|
||||
return getValuePaths(span, matcher)
|
||||
.filter(p -> {
|
||||
TraceObject object = p.getLastChild(getRootObject());
|
||||
if (object == null) {
|
||||
Msg.error(this, "NULL VALUE! " + p.getLastEntry());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass));
|
||||
}
|
||||
|
||||
|
@ -561,15 +574,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
try (LockHold hold = trace.lockWrite()) {
|
||||
TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan,
|
||||
TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE);
|
||||
/**
|
||||
* TODO: Test that when the ADDED events hits, that it actually appears in queries. I
|
||||
* suspect this will work since the events and/or event processors should be delayed
|
||||
* until the write lock is released. Certainly, a query would require the read lock.
|
||||
*/
|
||||
region.setName(path);
|
||||
region.setRange(range);
|
||||
region.setFlags(flags);
|
||||
trace.updateViewsAddRegionBlock(region);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.io.IOException;
|
|||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
@ -48,7 +49,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
|
||||
protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) {
|
||||
this.parent = parent;
|
||||
this.key = key;
|
||||
this.key = Objects.requireNonNull(key);
|
||||
this.minSnap = minSnap;
|
||||
}
|
||||
|
||||
|
@ -75,19 +76,15 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value.key == null) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * 2);
|
||||
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
|
||||
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
byte[] keyBytes = value.key.getBytes(cs);
|
||||
ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2);
|
||||
|
||||
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
|
||||
|
||||
buf.put(keyBytes);
|
||||
buf.put((byte) 0);
|
||||
|
||||
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
|
||||
|
||||
return buf.array();
|
||||
|
@ -101,16 +98,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
|
||||
DBTraceObject parent =
|
||||
DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE);
|
||||
String key;
|
||||
if (enc.length > Long.BYTES * 2) {
|
||||
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
|
||||
assert nullPos != -1;
|
||||
key = new String(enc, buf.position(), nullPos - buf.position(), cs);
|
||||
buf.position(nullPos + 1);
|
||||
}
|
||||
else {
|
||||
key = null;
|
||||
}
|
||||
|
||||
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
|
||||
assert nullPos != -1;
|
||||
String key = new String(enc, buf.position(), nullPos - buf.position(), cs);
|
||||
buf.position(nullPos + 1);
|
||||
|
||||
long minSnap = buf.getLong() ^ Long.MIN_VALUE;
|
||||
|
||||
return new PrimaryTriple(parent, key, minSnap);
|
||||
|
@ -186,14 +179,17 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
indexed = true,
|
||||
codec = PrimaryTripleDBFieldCodec.class)
|
||||
private PrimaryTriple triple;
|
||||
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
|
||||
@DBAnnotatedField(
|
||||
column = MAX_SNAP_COLUMN_NAME)
|
||||
private long maxSnap;
|
||||
@DBAnnotatedField(
|
||||
column = CHILD_COLUMN_NAME,
|
||||
indexed = true,
|
||||
codec = DBTraceObjectDBFieldCodec.class)
|
||||
private DBTraceObject child;
|
||||
@DBAnnotatedField(column = PRIMITIVE_COLUMN_NAME, codec = VariantDBFieldCodec.class)
|
||||
@DBAnnotatedField(
|
||||
column = PRIMITIVE_COLUMN_NAME,
|
||||
codec = VariantDBFieldCodec.class)
|
||||
private Object primitive;
|
||||
|
||||
protected final DBTraceObjectManager manager;
|
||||
|
@ -260,12 +256,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return triple.parent;
|
||||
return triple == null ? null : triple.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryKey() {
|
||||
return triple.key;
|
||||
return triple == null ? null : triple.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -71,10 +71,13 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
|||
}
|
||||
}
|
||||
else {
|
||||
entry.doTruncateOrDelete(range);
|
||||
InternalTraceObjectValue created = entry.doTruncateOrDelete(range);
|
||||
if (!entry.isDeleted()) {
|
||||
kept.add(entry);
|
||||
}
|
||||
if (created != null) {
|
||||
kept.add(created);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,10 +57,23 @@ public interface Trace extends DataTypeManagerDomainObject {
|
|||
public static final class TraceObjectChangeType<T, U>
|
||||
extends DefaultTraceChangeType<T, U> {
|
||||
/**
|
||||
* An object was created, but not necessarily inserted.
|
||||
* An object was created, but not yet inserted.
|
||||
*
|
||||
* <p>
|
||||
* Between the {@link #CREATED} and {@link #INSERTED} events, an object is considered
|
||||
* "incomplete," because it is likely missing its attributes. Thus, a trace client must take
|
||||
* care to ensure all attributes, especially fixed attributes, are added to the object
|
||||
* before it is inserted at its canonical path. Listeners may use
|
||||
* {@link TraceObject#getCanonicalParent(long)} to check if an object is complete for a
|
||||
* given snapshot.
|
||||
*/
|
||||
public static final TraceObjectChangeType<TraceObject, Void> CREATED =
|
||||
new TraceObjectChangeType<>();
|
||||
/**
|
||||
* An object was inserted at its canonical path.
|
||||
*/
|
||||
public static final TraceObjectChangeType<TraceObject, TraceObjectValue> INSERTED =
|
||||
new TraceObjectChangeType<>();
|
||||
/**
|
||||
* An object's lifespan changed.
|
||||
*/
|
||||
|
@ -75,9 +88,9 @@ public interface Trace extends DataTypeManagerDomainObject {
|
|||
* An object's value changed.
|
||||
*
|
||||
* <p>
|
||||
* If the old value is uniform for the new value's lifespan, that value is passed as the old
|
||||
* value. Otherwise, {@code null} is passed for the old value. If the value was cleared,
|
||||
* {@code null} is passed for the new value.
|
||||
* If the old value was equal for the entirety of the new value's lifespan, that old value
|
||||
* is passed as the old value. Otherwise, {@code null} is passed for the old value. If the
|
||||
* value was cleared, {@code null} is passed for the new value.
|
||||
*/
|
||||
public static final TraceObjectChangeType<TraceObjectValue, Object> VALUE_CHANGED =
|
||||
new TraceObjectChangeType<>();
|
||||
|
@ -307,7 +320,12 @@ public interface Trace extends DataTypeManagerDomainObject {
|
|||
public static final class TraceStackChangeType<U>
|
||||
extends DefaultTraceChangeType<TraceStack, U> {
|
||||
public static final TraceStackChangeType<Void> ADDED = new TraceStackChangeType<>();
|
||||
public static final TraceStackChangeType<Void> CHANGED = new TraceStackChangeType<>();
|
||||
/**
|
||||
* NOTE: The "new value" is the (min) snap where the change occurred. For StackFrame, it's
|
||||
* Stack.getSnap(), for ObjectStackFrame, the min snap of the value entry. The "old value"
|
||||
* is always 0.
|
||||
*/
|
||||
public static final TraceStackChangeType<Long> CHANGED = new TraceStackChangeType<>();
|
||||
public static final TraceStackChangeType<Void> DELETED = new TraceStackChangeType<>();
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.trace.model.stack;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
public interface TraceStackFrame {
|
||||
|
@ -22,9 +24,9 @@ public interface TraceStackFrame {
|
|||
|
||||
int getLevel();
|
||||
|
||||
Address getProgramCounter();
|
||||
Address getProgramCounter(long snap);
|
||||
|
||||
void setProgramCounter(Address pc);
|
||||
void setProgramCounter(Range<Long> span, Address pc);
|
||||
|
||||
String getComment();
|
||||
|
||||
|
|
|
@ -72,6 +72,20 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
*/
|
||||
void insert(ConflictResolution resolution);
|
||||
|
||||
/**
|
||||
* Get the parent value along this object's canonical path for a given snapshot
|
||||
*
|
||||
* <p>
|
||||
* To be the canonical parent value at a given snapshot, three things must be true: 1) The
|
||||
* parent object must have this object's path with the last key removed. 2) The parent value's
|
||||
* entry key must be equal to the final key of this object's path. 3) The value's lifespan must
|
||||
* contain the given snapshot. If no value satisfies these, null is returned.
|
||||
*
|
||||
* @param snap the snapshot key
|
||||
* @return the canonical parent value, or null
|
||||
*/
|
||||
TraceObjectValue getCanonicalParent(long snap);
|
||||
|
||||
/**
|
||||
* Check if this object is the root
|
||||
*
|
||||
|
@ -309,10 +323,15 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
/**
|
||||
* Set a value for the given lifespan, truncating existing entries
|
||||
*
|
||||
* <p>
|
||||
* Setting a value of {@code null} effectively deletes the value for the given lifespan and
|
||||
* returns {@code null}. Values of the same key intersecting the given lifespan or either
|
||||
* truncated or deleted.
|
||||
*
|
||||
* @param lifespan the lifespan of the value
|
||||
* @param key the key to set
|
||||
* @param value the new value
|
||||
* @return the created value entry
|
||||
* @return the created value entry, or null
|
||||
*/
|
||||
TraceObjectValue setValue(Range<Long> lifespan, String key, Object value);
|
||||
|
||||
|
@ -361,6 +380,16 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
*/
|
||||
TargetObjectSchema getTargetSchema();
|
||||
|
||||
/**
|
||||
* Search for ancestors having the given target interface
|
||||
*
|
||||
* @param span the span which the found objects must intersect
|
||||
* @param targetIf the interface class
|
||||
* @return the stream of found paths to values
|
||||
*/
|
||||
Stream<? extends TraceObjectValPath> queryAncestorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf);
|
||||
|
||||
/**
|
||||
* Search for ancestors providing the given interface and retrieve those interfaces
|
||||
*
|
||||
|
@ -372,6 +401,19 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
<I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
||||
Class<I> ifClass);
|
||||
|
||||
/**
|
||||
* Search for ancestors on the canonical path having the given target interface
|
||||
*
|
||||
* <p>
|
||||
* The object may not yet be inserted at its canonical path
|
||||
*
|
||||
* @param span the span which the found objects must intersect
|
||||
* @param targetIf the interface class
|
||||
* @return the stream of objects
|
||||
*/
|
||||
Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf);
|
||||
|
||||
/**
|
||||
* Search for ancestors on the canonical path providing the given interface
|
||||
*
|
||||
|
@ -386,6 +428,9 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
<I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
||||
Range<Long> span, Class<I> ifClass);
|
||||
|
||||
Stream<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf);
|
||||
|
||||
/**
|
||||
* Search for successors providing the given interface and retrieve those interfaces
|
||||
*
|
||||
|
|
|
@ -28,7 +28,8 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.time.*;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
|
|
@ -143,7 +143,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
tb.trace.getStackManager()
|
||||
.getStack(thread, 1, false)
|
||||
.getFrame(0, false)
|
||||
.getProgramCounter());
|
||||
.getProgramCounter(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import java.util.stream.StreamSupport;
|
|||
|
||||
import org.junit.*;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
|
@ -137,13 +139,13 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
|||
|
||||
TraceStack stack1 = stackManager.getStack(thread, 0, true);
|
||||
stack1.setDepth(2, true);
|
||||
(frame1a = stack1.getFrame(0, false)).setProgramCounter(b.addr(0x0040100));
|
||||
(frame1b = stack1.getFrame(1, false)).setProgramCounter(b.addr(0x0040300));
|
||||
(frame1a = stack1.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040100));
|
||||
(frame1b = stack1.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040300));
|
||||
|
||||
TraceStack stack2 = stackManager.getStack(thread, 1, true);
|
||||
stack2.setDepth(2, true);
|
||||
(frame2a = stack2.getFrame(0, false)).setProgramCounter(b.addr(0x0040200));
|
||||
(frame2b = stack2.getFrame(1, false)).setProgramCounter(b.addr(0x0040400));
|
||||
(frame2a = stack2.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040200));
|
||||
(frame2b = stack2.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040400));
|
||||
}
|
||||
|
||||
assertEquals(Set.of(frame1a, frame2a, frame1b, frame2b), toSet(stackManager
|
||||
|
@ -271,11 +273,11 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
|||
stack.setDepth(1, true);
|
||||
frame = stack.getFrame(0, false);
|
||||
|
||||
assertNull(frame.getProgramCounter());
|
||||
frame.setProgramCounter(b.addr(0x00400123));
|
||||
assertNull(frame.getProgramCounter(Long.MAX_VALUE));
|
||||
frame.setProgramCounter(Range.all(), b.addr(0x00400123));
|
||||
}
|
||||
|
||||
assertEquals(b.addr(0x00400123), frame.getProgramCounter());
|
||||
assertEquals(b.addr(0x00400123), frame.getProgramCounter(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -289,7 +291,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
|||
stack.setDepth(1, true);
|
||||
frame = stack.getFrame(0, false);
|
||||
// NB. Object-mode sets comment at pc in listing, not on frame itself
|
||||
frame.setProgramCounter(b.addr(0x00400123));
|
||||
frame.setProgramCounter(Range.all(), b.addr(0x00400123));
|
||||
|
||||
assertNull(frame.getComment());
|
||||
frame.setComment("Hello, World!");
|
||||
|
|
|
@ -507,6 +507,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
public void testGetSuccessors() {
|
||||
populateModel(3);
|
||||
|
||||
assertEquals(1, root.getSuccessors(Range.all(), PathPredicates.parse("")).count());
|
||||
|
||||
assertEquals(1, root.getSuccessors(Range.all(), PathPredicates.parse("Targets")).count());
|
||||
|
||||
assertEquals(1,
|
||||
|
@ -531,6 +533,29 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
root.getSuccessors(Range.all(), PathPredicates.parse("anAttribute.nope")).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrderedSuccessors() {
|
||||
populateModel(3);
|
||||
|
||||
assertEquals(List.of(root),
|
||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), true)
|
||||
.map(p -> p.getLastChild(root))
|
||||
.collect(Collectors.toList()));
|
||||
assertEquals(List.of(root),
|
||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), false)
|
||||
.map(p -> p.getLastChild(root))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
assertEquals(List.of(targets.get(0), targets.get(1), targets.get(2)),
|
||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), true)
|
||||
.map(p -> p.getLastChild(root))
|
||||
.collect(Collectors.toList()));
|
||||
assertEquals(List.of(targets.get(2), targets.get(1), targets.get(0)),
|
||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), false)
|
||||
.map(p -> p.getLastChild(root))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetValue_TruncatesOrDeletes() {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
|
@ -794,6 +819,38 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetValue_NullContainedTruncates() {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||
assertNull(root.setValue(Range.closed(0L, 9L), "a", null));
|
||||
assertEquals(0, root.getValues().size());
|
||||
|
||||
assertNotNull(root.setValue(Range.closed(0L, 9L), "a", 1));
|
||||
assertEquals(1, root.getValues().size());
|
||||
|
||||
assertNull(root.setValue(Range.singleton(5L), "a", null));
|
||||
assertEquals(2, root.getValues().size());
|
||||
|
||||
assertEquals(List.of(Range.closed(0L, 4L), Range.closed(6L, 9L)),
|
||||
root.getOrderedValues(Range.all(), "a", true)
|
||||
.map(v -> v.getLifespan())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetValue_NullSameDeletes() {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||
assertNotNull(root.setValue(Range.closed(0L, 9L), "a", 1));
|
||||
assertEquals(1, root.getValues().size());
|
||||
|
||||
assertNull(root.setValue(Range.closed(0L, 9L), "a", null));
|
||||
assertEquals(0, root.getValues().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttribute() {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
|
@ -815,7 +872,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testObjectDelete() {
|
||||
public void testObjectDelete() throws Exception {
|
||||
populateModel(3);
|
||||
|
||||
// Delete a leaf
|
||||
|
@ -844,6 +901,12 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
assertEquals(2,
|
||||
manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[]")).count());
|
||||
assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||
assertEquals(2, targetContainer.getValues().size());
|
||||
|
||||
b.trace.undo();
|
||||
b.trace.redo();
|
||||
|
||||
assertEquals(2, targetContainer.getValues().size());
|
||||
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
targetContainer.delete();
|
||||
|
@ -876,7 +939,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testObjectTruncateOrDelete() {
|
||||
public void testRootObjectTruncateOrDelete() {
|
||||
try (UndoableTransaction tid = b.startTransaction()) {
|
||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue