mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Merge remote-tracking branch 'origin/GP-1970_Dan_noTraceObjectLifespan--SQUASHED'
This commit is contained in:
commit
37a8ffb492
57 changed files with 1798 additions and 1139 deletions
|
@ -381,7 +381,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateStack() {
|
protected void updateStack() {
|
||||||
Set<TraceStackFrame> toAdd = new LinkedHashSet<>(currentStack.getFrames());
|
Set<TraceStackFrame> toAdd = new LinkedHashSet<>(currentStack.getFrames(current.getSnap()));
|
||||||
for (Iterator<StackFrameRow> it = stackTableModel.getModelData().iterator(); it
|
for (Iterator<StackFrameRow> it = stackTableModel.getModelData().iterator(); it
|
||||||
.hasNext();) {
|
.hasNext();) {
|
||||||
StackFrameRow row = it.next();
|
StackFrameRow row = it.next();
|
||||||
|
@ -409,13 +409,13 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||||
contextChanged();
|
contextChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentStack == stack) {
|
if (currentStack == stack && stack.hasFixedFrames()) {
|
||||||
stackTableModel.fireTableDataChanged();
|
stackTableModel.fireTableDataChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentStack = stack;
|
currentStack = stack;
|
||||||
stackTableModel.clear();
|
stackTableModel.clear();
|
||||||
for (TraceStackFrame frame : currentStack.getFrames()) {
|
for (TraceStackFrame frame : currentStack.getFrames(current.getSnap())) {
|
||||||
stackTableModel.add(new StackFrameRow(this, frame));
|
stackTableModel.add(new StackFrameRow(this, frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,13 +75,13 @@ public class StackFrameRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getComment() {
|
public String getComment() {
|
||||||
return frame == null ? "" : frame.getComment();
|
return frame == null ? "" : frame.getComment(getSnap());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setComment(String comment) {
|
public void setComment(String comment) {
|
||||||
try (UndoableTransaction tid = UndoableTransaction
|
try (UndoableTransaction tid = UndoableTransaction
|
||||||
.start(frame.getStack().getThread().getTrace(), "Frame comment", true)) {
|
.start(frame.getStack().getThread().getTrace(), "Frame comment", true)) {
|
||||||
frame.setComment(comment);
|
frame.setComment(getSnap(), comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -277,7 +277,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||||
private void breakpointDeleted(TraceBreakpoint tb) {
|
private void breakpointDeleted(TraceBreakpoint tb) {
|
||||||
if (!tb.getLifespan().contains(info.recorder.getSnap())) {
|
if (!tb.getLifespan().contains(info.recorder.getSnap())) {
|
||||||
// NOTE: User/script probably removed historical breakpoint
|
// NOTE: User/script probably removed historical breakpoint
|
||||||
assert false;
|
// assert false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info.forgetTraceBreakpoint(c.r, tb);
|
info.forgetTraceBreakpoint(c.r, tb);
|
||||||
|
|
|
@ -420,7 +420,10 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
@Override
|
@Override
|
||||||
public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
|
public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
|
||||||
TraceObject traceObject = objectRecorder.toTrace(successor);
|
TraceObject traceObject = objectRecorder.toTrace(successor);
|
||||||
return traceObject.queryCanonicalAncestorsInterface(Range.singleton(getSnap()),
|
if (traceObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return traceObject.queryCanonicalAncestorsInterface(
|
||||||
TraceObjectThread.class).findFirst().orElse(null);
|
TraceObjectThread.class).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +435,10 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
@Override
|
@Override
|
||||||
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
|
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
|
||||||
TraceObject traceObject = objectRecorder.toTrace(successor);
|
TraceObject traceObject = objectRecorder.toTrace(successor);
|
||||||
return traceObject.queryCanonicalAncestorsInterface(Range.singleton(getSnap()),
|
if (traceObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return traceObject.queryCanonicalAncestorsInterface(
|
||||||
TraceObjectStackFrame.class).findFirst().orElse(null);
|
TraceObjectStackFrame.class).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,7 +552,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
TraceObject object = thread.getObject();
|
TraceObject object = thread.getObject();
|
||||||
this.process = object
|
this.process = object
|
||||||
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
|
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
|
||||||
.map(p -> p.getFirstParent(object))
|
.map(p -> p.getSource(object))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
@ -562,7 +568,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
}
|
}
|
||||||
return object
|
return object
|
||||||
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
|
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
|
||||||
.map(p -> p.getFirstParent(object))
|
.map(p -> p.getSource(object))
|
||||||
.anyMatch(p -> p == process);
|
.anyMatch(p -> p == process);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,14 @@ class ObjectRecorder {
|
||||||
traceObject = objectManager.getRootObject();
|
traceObject = objectManager.getRootObject();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
traceObject = objectManager
|
traceObject = objectManager.createObject(TraceObjectKeyPath.of(object.getPath()));
|
||||||
.createObject(TraceObjectKeyPath.of(object.getPath()), Range.atLeast(snap));
|
|
||||||
}
|
}
|
||||||
synchronized (objectMap) {
|
synchronized (objectMap) {
|
||||||
objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject));
|
IDKeyed<TraceObject> exists =
|
||||||
|
objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject));
|
||||||
|
if (exists != null) {
|
||||||
|
Msg.error(this, "Received created for an object that already exists: " + exists);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +105,7 @@ class ObjectRecorder {
|
||||||
Msg.error(this, "Unknown object was invalidated: " + object);
|
Msg.error(this, "Unknown object was invalidated: " + object);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
traceObject.obj.truncateOrDelete(Range.atLeast(snap));
|
traceObject.obj.removeTree(Range.atLeast(snap));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String encodeEnum(Enum<?> e) {
|
protected String encodeEnum(Enum<?> e) {
|
||||||
|
@ -269,7 +272,7 @@ class ObjectRecorder {
|
||||||
if (found == null) {
|
if (found == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TraceObject last = found.getLastChild(null);
|
TraceObject last = found.getDestination(null);
|
||||||
if (last == null) {
|
if (last == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -284,7 +287,7 @@ class ObjectRecorder {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
return seed.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), targetIf)
|
return seed.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), targetIf)
|
||||||
.map(p -> toTarget(p.getLastChild(seed)).as(targetIf))
|
.map(p -> toTarget(p.getDestination(seed)).as(targetIf))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,11 +95,11 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
|
|
||||||
TraceStackFrame frame = stack.getFrame(0, false);
|
TraceStackFrame frame = stack.getFrame(0, false);
|
||||||
frame.setProgramCounter(Range.all(), tb.addr(0x00400100));
|
frame.setProgramCounter(Range.all(), tb.addr(0x00400100));
|
||||||
frame.setComment("Hello");
|
frame.setComment(stack.getSnap(), "Hello");
|
||||||
|
|
||||||
frame = stack.getFrame(1, false);
|
frame = stack.getFrame(1, false);
|
||||||
frame.setProgramCounter(Range.all(), tb.addr(0x00400200));
|
frame.setProgramCounter(Range.all(), tb.addr(0x00400200));
|
||||||
frame.setComment("World");
|
frame.setComment(stack.getSnap(), "World");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -169,19 +169,22 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
|
||||||
public void testRecordThreadNameReuse() throws Throwable {
|
public void testRecordThreadNameReuse() throws Throwable {
|
||||||
startRecording();
|
startRecording();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
TraceThread thread1a = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
|
waitRecorder(recorder);
|
||||||
|
TraceThread thread1 = recorder.getTraceThread(mb.testThread1);
|
||||||
|
assertNotNull(thread1);
|
||||||
|
TraceObject object1 = ((TraceObjectThread) thread1).getObject();
|
||||||
|
|
||||||
recorder.forceSnapshot();
|
recorder.forceSnapshot();
|
||||||
mb.testProcess1.threads.removeThreads(mb.testThread1);
|
mb.testProcess1.threads.removeThreads(mb.testThread1);
|
||||||
|
waitRecorder(recorder);
|
||||||
waitForPass(() -> assertEquals(Range.singleton(0L), thread1a.getLifespan()));
|
assertEquals(Range.singleton(0L), thread1.getLifespan());
|
||||||
assertNull(recorder.getTraceThread(mb.testThread1));
|
assertNull(recorder.getTraceThread(mb.testThread1));
|
||||||
|
|
||||||
recorder.forceSnapshot();
|
recorder.forceSnapshot();
|
||||||
mb.testThread1 = mb.testProcess1.addThread(1);
|
mb.testThread1 = mb.testProcess1.addThread(1);
|
||||||
TraceThread thread1b = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
|
waitRecorder(recorder);
|
||||||
|
assertSame(thread1, recorder.getTraceThread(mb.testThread1));
|
||||||
assertNotSame(thread1a, thread1b);
|
assertEquals(Set.of(Range.singleton(0L), Range.atLeast(2L)), object1.getLife().asRanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -510,7 +513,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU
|
||||||
TraceObject traceBank = thread.getObject()
|
TraceObject traceBank = thread.getObject()
|
||||||
.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()),
|
.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()),
|
||||||
TargetRegisterBank.class)
|
TargetRegisterBank.class)
|
||||||
.map(p -> p.getLastChild(thread.getObject()))
|
.map(p -> p.getDestination(thread.getObject()))
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.dbg.util;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public enum AllPathsMatcher implements PathPredicates {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PathPredicates or(PathPredicates that) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(List<String> path) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean successorCouldMatch(List<String> path, boolean strict) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean ancestorMatches(List<String> path, boolean strict) {
|
||||||
|
if (path.isEmpty() && strict) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getNextKeys(List<String> path) {
|
||||||
|
return Set.of("", "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getNextNames(List<String> path) {
|
||||||
|
return Set.of("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getNextIndices(List<String> path) {
|
||||||
|
return Set.of("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSingletonPath() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PathPattern getSingletonPattern() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PathPredicates applyKeys(List<String> keys) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,10 @@ public interface PathPredicates {
|
||||||
return new PathPattern(PathUtils.parse(pattern));
|
return new PathPattern(PathUtils.parse(pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PathPredicates all() {
|
||||||
|
return AllPathsMatcher.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
PathPredicates or(PathPredicates that);
|
PathPredicates or(PathPredicates that);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -81,6 +81,7 @@ public class DBTraceObjectBreakpointLocation
|
||||||
|
|
||||||
// Keep copies here for when the object gets invalidated
|
// Keep copies here for when the object gets invalidated
|
||||||
private AddressRange range;
|
private AddressRange range;
|
||||||
|
private Range<Long> lifespan;
|
||||||
|
|
||||||
public DBTraceObjectBreakpointLocation(DBTraceObject object) {
|
public DBTraceObjectBreakpointLocation(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
@ -98,29 +99,43 @@ public class DBTraceObjectBreakpointLocation
|
||||||
return object.getCanonicalPath().toString();
|
return object.getCanonicalPath().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(Range<Long> lifespan, String name) {
|
||||||
|
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
setName(getLifespan(), name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
|
||||||
|
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRange(AddressRange range) {
|
public void setRange(Range<Long> lifespan, AddressRange range) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), KEY_RANGE, range);
|
object.setValue(lifespan, KEY_RANGE, range);
|
||||||
this.range = range;
|
this.range = range;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AddressRange getRange() {
|
public AddressRange getRange() {
|
||||||
return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE,
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
AddressRange.class, range);
|
if (object.getLife().isEmpty()) {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE,
|
||||||
|
AddressRange.class, range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,17 +161,33 @@ public class DBTraceObjectBreakpointLocation
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
TraceObjectInterfaceUtils.setLifespan(TraceObjectBreakpointLocation.class, object,
|
TraceObjectInterfaceUtils.setLifespan(TraceObjectBreakpointLocation.class, object,
|
||||||
lifespan);
|
lifespan);
|
||||||
|
this.lifespan = lifespan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<Long> getLifespan() {
|
public Range<Long> getLifespan() {
|
||||||
return object.getLifespan();
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
|
Range<Long> computed = computeSpan();
|
||||||
|
if (computed != null) {
|
||||||
|
lifespan = computed;
|
||||||
|
}
|
||||||
|
return lifespan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Range<Long> computeSpan() {
|
||||||
|
Range<Long> span = TraceObjectBreakpointLocation.super.computeSpan();
|
||||||
|
if (span != null) {
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
return getSpecification().computeSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPlacedSnap() {
|
public long getPlacedSnap() {
|
||||||
return object.getMinSnap();
|
return DBTraceUtils.lowerEndpoint(getLifespan());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,7 +199,7 @@ public class DBTraceObjectBreakpointLocation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getClearedSnap() {
|
public long getClearedSnap() {
|
||||||
return object.getMaxSnap();
|
return DBTraceUtils.upperEndpoint(getLifespan());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -183,10 +214,15 @@ public class DBTraceObjectBreakpointLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(Range<Long> lifespan, boolean enabled) {
|
||||||
|
object.setValue(lifespan, TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
|
setEnabled(getLifespan(), enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,13 +239,20 @@ public class DBTraceObjectBreakpointLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setKinds(Collection<TraceBreakpointKind> kinds) {
|
public void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
TraceObjectBreakpointSpec spec = getSpecification();
|
TraceObjectBreakpointSpec spec = getSpecification();
|
||||||
if (spec.getObject() != this.getObject()) {
|
if (spec.getObject() != this.getObject()) {
|
||||||
throw new UnsupportedOperationException("Set via the specification instead");
|
throw new UnsupportedOperationException("Set via the specification instead");
|
||||||
}
|
}
|
||||||
spec.setKinds(kinds);
|
spec.setKinds(lifespan, kinds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKinds(Collection<TraceBreakpointKind> kinds) {
|
||||||
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
setKinds(getLifespan(), kinds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,17 +278,22 @@ public class DBTraceObjectBreakpointLocation
|
||||||
|
|
||||||
PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false);
|
PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false);
|
||||||
return object.getAncestors(getLifespan(), procMatcher)
|
return object.getAncestors(getLifespan(), procMatcher)
|
||||||
.flatMap(proc -> proc.getFirstParent(object)
|
.flatMap(proc -> proc.getSource(object)
|
||||||
.querySuccessorsInterface(getLifespan(),
|
.querySuccessorsInterface(getLifespan(),
|
||||||
TraceObjectThread.class))
|
TraceObjectThread.class))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setComment(Range<Long> lifespan, String comment) {
|
||||||
|
object.setValue(lifespan, KEY_COMMENT, comment);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setComment(String comment) {
|
public void setComment(String comment) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), KEY_COMMENT, comment);
|
setComment(getLifespan(), comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +305,9 @@ public class DBTraceObjectBreakpointLocation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
object.deleteTree();
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.removeTree(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -268,16 +318,14 @@ public class DBTraceObjectBreakpointLocation
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectBreakpointSpec getSpecification() {
|
public TraceObjectBreakpointSpec getSpecification() {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return object
|
return object.queryCanonicalAncestorsInterface(TraceObjectBreakpointSpec.class)
|
||||||
.queryCanonicalAncestorsInterface(getLifespan(),
|
|
||||||
TraceObjectBreakpointSpec.class)
|
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceAddressSpace getTraceAddressSpace() {
|
public TraceAddressSpace getTraceAddressSpace() {
|
||||||
return spaceForValue(object.getMinSnap(), KEY_RANGE);
|
return spaceForValue(computeMinSnap(), KEY_RANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -99,12 +99,12 @@ public class DBTraceObjectBreakpointSpec
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<Long> getLifespan() {
|
public Range<Long> getLifespan() {
|
||||||
return object.getLifespan();
|
return computeSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPlacedSnap() {
|
public long getPlacedSnap() {
|
||||||
return object.getMinSnap();
|
return computeMinSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,7 +116,7 @@ public class DBTraceObjectBreakpointSpec
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getClearedSnap() {
|
public long getClearedSnap() {
|
||||||
return object.getMaxSnap();
|
return computeMaxSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -144,16 +144,23 @@ public class DBTraceObjectBreakpointSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setKinds(Collection<TraceBreakpointKind> kinds) {
|
public void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds) {
|
||||||
// TODO: More efficient encoding
|
// TODO: More efficient encoding
|
||||||
// TODO: Target-Trace mapping is implied by encoded name. Seems bad.
|
// TODO: Target-Trace mapping is implied by encoded name. Seems bad.
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
|
object.setValue(lifespan, TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
|
||||||
TraceBreakpointKindSet.encode(kinds));
|
TraceBreakpointKindSet.encode(kinds));
|
||||||
this.kinds = TraceBreakpointKindSet.copyOf(kinds);
|
this.kinds = TraceBreakpointKindSet.copyOf(kinds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKinds(Collection<TraceBreakpointKind> kinds) {
|
||||||
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
setKinds(getLifespan(), kinds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<TraceBreakpointKind> getKinds() {
|
public Set<TraceBreakpointKind> getKinds() {
|
||||||
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
|
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
|
||||||
|
@ -187,7 +194,9 @@ public class DBTraceObjectBreakpointSpec
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
object.deleteTree();
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.removeTree(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -206,9 +215,9 @@ public class DBTraceObjectBreakpointSpec
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) {
|
||||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
TraceChangeRecord<TraceObjectValue, Void> cast =
|
||||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
||||||
TraceObjectValue affected = cast.getAffectedObject();
|
TraceObjectValue affected = cast.getAffectedObject();
|
||||||
String key = affected.getEntryKey();
|
String key = affected.getEntryKey();
|
||||||
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
|
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
|
||||||
|
|
|
@ -98,6 +98,10 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
private final RegionChangeTranslator translator;
|
private final RegionChangeTranslator translator;
|
||||||
|
|
||||||
|
// Keep copies here for when the object gets invalidated
|
||||||
|
private AddressRange range;
|
||||||
|
private Range<Long> lifespan;
|
||||||
|
|
||||||
public DBTraceObjectMemoryRegion(DBTraceObject object) {
|
public DBTraceObjectMemoryRegion(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
|
||||||
|
@ -114,10 +118,15 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
return object.getCanonicalPath().toString();
|
return object.getCanonicalPath().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(Range<Long> lifespan, String name) {
|
||||||
|
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
setName(computeSpan(), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,18 +140,21 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
@Override
|
@Override
|
||||||
public void setLifespan(Range<Long> newLifespan) throws DuplicateNameException {
|
public void setLifespan(Range<Long> newLifespan) throws DuplicateNameException {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
Range<Long> oldLifespan = getLifespan();
|
|
||||||
if (Objects.equals(oldLifespan, newLifespan)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
|
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
|
||||||
newLifespan);
|
newLifespan);
|
||||||
|
this.lifespan = newLifespan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<Long> getLifespan() {
|
public Range<Long> getLifespan() {
|
||||||
return object.getLifespan();
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
|
Range<Long> computed = computeSpan();
|
||||||
|
if (computed != null) {
|
||||||
|
lifespan = computed;
|
||||||
|
}
|
||||||
|
return lifespan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -154,7 +166,7 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCreationSnap() {
|
public long getCreationSnap() {
|
||||||
return object.getMinSnap();
|
return computeMinSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -166,25 +178,32 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getDestructionSnap() {
|
public long getDestructionSnap() {
|
||||||
return object.getMaxSnap();
|
return computeMaxSnap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRange(Range<Long> lifespan, AddressRange newRange) {
|
||||||
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.setValue(lifespan, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
|
||||||
|
this.range = newRange;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRange(AddressRange newRange) {
|
public void setRange(AddressRange newRange) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
AddressRange oldRange = getRange();
|
setRange(computeSpan(), newRange);
|
||||||
if (Objects.equals(oldRange, newRange)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AddressRange getRange() {
|
public AddressRange getRange() {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return TraceObjectInterfaceUtils.getValue(object, getCreationSnap(),
|
if (object.getLife().isEmpty()) {
|
||||||
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
return range;
|
||||||
|
}
|
||||||
|
return range = TraceObjectInterfaceUtils.getValue(object, getCreationSnap(),
|
||||||
|
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +330,7 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.deleteTree();
|
object.removeTree(computeSpan());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectThread getThread() {
|
public TraceObjectThread getThread() {
|
||||||
return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class)
|
return object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLength() {
|
public int getLength() {
|
||||||
return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(),
|
return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
|
||||||
TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
|
TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,10 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
private final ModuleChangeTranslator translator;
|
private final ModuleChangeTranslator translator;
|
||||||
|
|
||||||
|
// Keep copies here for when the object gets invalidated
|
||||||
|
private AddressRange range;
|
||||||
|
private Range<Long> lifespan;
|
||||||
|
|
||||||
public DBTraceObjectModule(DBTraceObject object) {
|
public DBTraceObjectModule(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
|
||||||
|
@ -104,10 +108,15 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
return object.getCanonicalPath().toString();
|
return object.getCanonicalPath().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(Range<Long> lifespan, String name) {
|
||||||
|
object.setValue(lifespan, TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
|
setName(computeSpan(), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,17 +126,30 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
|
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRange(Range<Long> lifespan, AddressRange range) {
|
||||||
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.setValue(lifespan, TargetModule.RANGE_ATTRIBUTE_NAME, range);
|
||||||
|
this.range = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRange(AddressRange range) {
|
public void setRange(AddressRange range) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range);
|
setRange(computeSpan(), range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AddressRange getRange() {
|
public AddressRange getRange() {
|
||||||
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
if (object.getLife().isEmpty()) {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
return range = TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
|
||||||
|
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -172,15 +194,23 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
public void setLifespan(Range<Long> lifespan) throws DuplicateNameException {
|
public void setLifespan(Range<Long> lifespan) throws DuplicateNameException {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
TraceObjectInterfaceUtils.setLifespan(TraceObjectModule.class, object, lifespan);
|
TraceObjectInterfaceUtils.setLifespan(TraceObjectModule.class, object, lifespan);
|
||||||
|
this.lifespan = lifespan;
|
||||||
for (TraceObjectSection section : getSections()) {
|
for (TraceObjectSection section : getSections()) {
|
||||||
section.getObject().setLifespan(lifespan);
|
TraceObjectInterfaceUtils.setLifespan(TraceObjectSection.class, section.getObject(),
|
||||||
|
lifespan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<Long> getLifespan() {
|
public Range<Long> getLifespan() {
|
||||||
return object.getLifespan();
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
|
Range<Long> computed = computeSpan();
|
||||||
|
if (computed != null) {
|
||||||
|
lifespan = computed;
|
||||||
|
}
|
||||||
|
return lifespan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -192,7 +222,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLoadedSnap() {
|
public long getLoadedSnap() {
|
||||||
return object.getMinSnap();
|
return computeMinSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -204,7 +234,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getUnloadedSnap() {
|
public long getUnloadedSnap() {
|
||||||
return object.getMaxSnap();
|
return computeMaxSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -220,14 +250,16 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true);
|
PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true);
|
||||||
PathMatcher applied = matcher.applyKeys(List.of(sectionName));
|
PathMatcher applied = matcher.applyKeys(List.of(sectionName));
|
||||||
return object.getSuccessors(getLifespan(), applied)
|
return object.getSuccessors(getLifespan(), applied)
|
||||||
.map(p -> p.getLastChild(object).queryInterface(TraceObjectSection.class))
|
.map(p -> p.getDestination(object).queryInterface(TraceObjectSection.class))
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
object.deleteTree();
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.removeTree(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -23,13 +23,13 @@ import ghidra.trace.database.target.DBTraceObject;
|
||||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceSectionChangeType;
|
import ghidra.trace.model.Trace.TraceSectionChangeType;
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.TraceObjectModule;
|
||||||
|
import ghidra.trace.model.modules.TraceSection;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.trace.util.TraceChangeType;
|
import ghidra.trace.util.TraceChangeType;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
|
||||||
|
|
||||||
public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface {
|
public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface {
|
||||||
|
|
||||||
|
@ -68,6 +68,9 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
private final SectionTranslator translator;
|
private final SectionTranslator translator;
|
||||||
|
|
||||||
|
// Keep copies here for when the object gets invalidated
|
||||||
|
private AddressRange range;
|
||||||
|
|
||||||
public DBTraceObjectSection(DBTraceObject object) {
|
public DBTraceObjectSection(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
|
||||||
|
@ -80,9 +83,9 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceModule getModule() {
|
public TraceObjectModule getModule() {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return object.queryAncestorsInterface(object.getLifespan(), TraceObjectModule.class)
|
return object.queryCanonicalAncestorsInterface(TraceObjectModule.class)
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
@ -94,30 +97,56 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) throws DuplicateNameException {
|
public void setName(Range<Long> lifespan, String name) {
|
||||||
object.setValue(object.getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
setName(computeSpan(), name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(),
|
return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRange(AddressRange range) {
|
public void setRange(Range<Long> lifespan, AddressRange range) {
|
||||||
object.setValue(object.getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range);
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.setValue(lifespan, TargetModule.RANGE_ATTRIBUTE_NAME, range);
|
||||||
|
this.range = range;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AddressRange getRange() {
|
public AddressRange getRange() {
|
||||||
return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(),
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
if (object.getLife().isEmpty()) {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
return range = TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
|
||||||
|
TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Range<Long> computeSpan() {
|
||||||
|
Range<Long> span = DBTraceObjectInterface.super.computeSpan();
|
||||||
|
if (span != null) {
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
return getModule().computeSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
object.deleteTree();
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.removeTree(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.module;
|
package ghidra.trace.database.module;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.target.TargetSection;
|
import ghidra.dbg.target.TargetSection;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
@ -30,5 +32,7 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
|
||||||
TargetSection.RANGE_ATTRIBUTE_NAME
|
TargetSection.RANGE_ATTRIBUTE_NAME
|
||||||
})
|
})
|
||||||
public interface TraceObjectSection extends TraceSection, TraceObjectInterface {
|
public interface TraceObjectSection extends TraceSection, TraceObjectInterface {
|
||||||
void setRange(AddressRange range);
|
void setName(Range<Long> lifespan, String name);
|
||||||
|
|
||||||
|
void setRange(Range<Long> lifespan, AddressRange range);
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
@Override
|
@Override
|
||||||
public TraceThread getThread() {
|
public TraceThread getThread() {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class)
|
return object.queryAncestorsInterface(computeSpan(), TraceObjectThread.class)
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
@ -88,14 +88,14 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getSnap() {
|
public long getSnap() {
|
||||||
return object.getMinSnap();
|
return computeMinSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDepth() {
|
public int getDepth() {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return object
|
return object
|
||||||
.querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class)
|
.querySuccessorsInterface(computeSpan(), TraceObjectStackFrame.class)
|
||||||
.map(f -> f.getLevel())
|
.map(f -> f.getLevel())
|
||||||
.reduce(Integer::max)
|
.reduce(Integer::max)
|
||||||
.map(m -> m + 1)
|
.map(m -> m + 1)
|
||||||
|
@ -119,8 +119,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
|
|
||||||
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
|
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
|
||||||
// TODO: All attributes within a given span, intersected to that span?
|
// TODO: All attributes within a given span, intersected to that span?
|
||||||
to.setProgramCounter(to.getObject().getLifespan(),
|
to.setProgramCounter(computeSpan(), from.getProgramCounter(computeMaxSnap()));
|
||||||
from.getProgramCounter(from.getObject().getMaxSnap()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void shiftFrameAttributes(int from, int to, int count,
|
protected void shiftFrameAttributes(int from, int to, int count,
|
||||||
|
@ -143,7 +142,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) {
|
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) {
|
||||||
for (int i = start; i < end; i++) {
|
for (int i = start; i < end; i++) {
|
||||||
TraceObjectStackFrame frame = frames.get(i);
|
TraceObjectStackFrame frame = frames.get(i);
|
||||||
frame.setProgramCounter(frame.getObject().getLifespan(), null);
|
frame.setProgramCounter(frame.computeSpan(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +151,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
// TODO: Need a span parameter
|
// TODO: Need a span parameter
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
List<TraceObjectStackFrame> frames = // Want mutable list
|
List<TraceObjectStackFrame> frames = // Want mutable list
|
||||||
doGetFrames().collect(Collectors.toCollection(ArrayList::new));
|
doGetFrames(computeMinSnap()).collect(Collectors.toCollection(ArrayList::new));
|
||||||
int curDepth = frames.size();
|
int curDepth = frames.size();
|
||||||
if (curDepth == depth) {
|
if (curDepth == depth) {
|
||||||
return;
|
return;
|
||||||
|
@ -163,7 +162,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
shiftFrameAttributes(diff, 0, depth, frames);
|
shiftFrameAttributes(diff, 0, depth, frames);
|
||||||
}
|
}
|
||||||
for (int i = depth; i < curDepth; i++) {
|
for (int i = depth; i < curDepth; i++) {
|
||||||
frames.get(i).getObject().deleteTree();
|
frames.get(i).getObject().removeTree(computeSpan());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -183,9 +182,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
TargetObjectSchema schema = object.getTargetSchema();
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
|
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
|
||||||
matcher = matcher.applyKeys(PathUtils.makeIndex(level));
|
matcher = matcher.applyKeys(PathUtils.makeIndex(level));
|
||||||
return object.getSuccessors(object.getLifespan(), matcher)
|
return object.getSuccessors(computeSpan(), matcher)
|
||||||
.findAny()
|
.findAny()
|
||||||
.map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class))
|
.map(p -> p.getDestination(object).queryInterface(TraceObjectStackFrame.class))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,22 +206,24 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<TraceObjectStackFrame> doGetFrames() {
|
protected Stream<TraceObjectStackFrame> doGetFrames(long snap) {
|
||||||
return object
|
return object
|
||||||
.querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class)
|
.querySuccessorsInterface(Range.singleton(snap), TraceObjectStackFrame.class)
|
||||||
.sorted(Comparator.comparing(f -> f.getLevel()));
|
.sorted(Comparator.comparing(f -> f.getLevel()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TraceStackFrame> getFrames() {
|
public List<TraceStackFrame> getFrames(long snap) {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return doGetFrames().collect(Collectors.toList());
|
return doGetFrames(snap).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
object.deleteTree();
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.removeTree(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -234,4 +235,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
||||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||||
return translator.translate(rec);
|
return translator.translate(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFixedFrames() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,13 @@ package ghidra.trace.database.stack;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.*;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
import ghidra.dbg.target.TargetStackFrame;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.CodeUnit;
|
import ghidra.program.model.listing.CodeUnit;
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.database.target.DBTraceObject;
|
import ghidra.trace.database.target.DBTraceObject;
|
||||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
|
@ -32,12 +33,14 @@ import ghidra.trace.model.stack.TraceObjectStackFrame;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
|
|
||||||
public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface {
|
public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface {
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
|
// TODO: Memorizing life is not optimal.
|
||||||
|
// GP-1887 means to expose multiple lifespans in, e.g., TraceThread
|
||||||
|
private RangeSet<Long> life = TreeRangeSet.create();
|
||||||
|
|
||||||
public DBTraceObjectStackFrame(DBTraceObject object) {
|
public DBTraceObjectStackFrame(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
@ -46,8 +49,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectStack getStack() {
|
public TraceObjectStack getStack() {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
return object
|
return object.queryCanonicalAncestorsInterface(TraceObjectStack.class)
|
||||||
.queryCanonicalAncestorsInterface(object.getLifespan(), TraceObjectStack.class)
|
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
}
|
}
|
||||||
|
@ -85,16 +87,14 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
||||||
if (pc == Address.NO_ADDRESS) {
|
if (pc == Address.NO_ADDRESS) {
|
||||||
pc = null;
|
pc = null;
|
||||||
}
|
}
|
||||||
object.setValue(object.getLifespan().intersection(span),
|
object.setValue(span, TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
|
||||||
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getComment() {
|
public String getComment(long snap) {
|
||||||
// TODO: Do I need to add a snap argument?
|
|
||||||
// TODO: One day, we'll have dynamic columns in the debugger
|
// 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
|
* I don't use an attribute for this, because there's not a nice way track the "identity" of
|
||||||
* a stack frame. If the frame is re-used (the recommendation for connector development),
|
* a stack frame. If the frame is re-used (the recommendation for connector development),
|
||||||
* the same comment may not necessarily apply. It'd be nice if the connector re-assigned
|
* the same comment may not necessarily apply. It'd be nice if the connector re-assigned
|
||||||
|
@ -105,21 +105,23 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
||||||
* follow the "same frame" as its level changes.
|
* follow the "same frame" as its level changes.
|
||||||
*/
|
*/
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
Address pc = getProgramCounter(object.getMaxSnap());
|
Address pc = getProgramCounter(snap);
|
||||||
return pc == null ? null
|
return pc == null ? null
|
||||||
: object.getTrace()
|
: object.getTrace()
|
||||||
.getCommentAdapter()
|
.getCommentAdapter()
|
||||||
.getComment(object.getMaxSnap(), pc, CodeUnit.EOL_COMMENT);
|
.getComment(snap, pc, CodeUnit.EOL_COMMENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setComment(String comment) {
|
public void setComment(long snap, String comment) {
|
||||||
// TODO: Do I need to add a span argument?
|
/* See rant in getComment */
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
TraceObjectValue pcAttr =
|
||||||
|
object.getValue(snap, TargetStackFrame.PC_ATTRIBUTE_NAME);
|
||||||
object.getTrace()
|
object.getTrace()
|
||||||
.getCommentAdapter()
|
.getCommentAdapter()
|
||||||
.setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()),
|
.setComment(pcAttr.getLifespan(), (Address) pcAttr.getValue(),
|
||||||
CodeUnit.EOL_COMMENT, comment);
|
CodeUnit.EOL_COMMENT, comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,8 +132,8 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
|
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
|
||||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
TraceChangeRecord<TraceObjectValue, Void> cast =
|
||||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
||||||
TraceObjectValue affected = cast.getAffectedObject();
|
TraceObjectValue affected = cast.getAffectedObject();
|
||||||
assert affected.getParent() == object;
|
assert affected.getParent() == object;
|
||||||
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
|
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
|
||||||
|
@ -143,24 +145,45 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected long snapFor(TraceChangeRecord<?, ?> rec) {
|
@Override
|
||||||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
public Range<Long> computeSpan() {
|
||||||
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap();
|
Range<Long> span = DBTraceObjectInterface.super.computeSpan();
|
||||||
|
if (span != null) {
|
||||||
|
return span;
|
||||||
}
|
}
|
||||||
return object.getMinSnap();
|
return getStack().computeSpan();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long snapFor(TraceChangeRecord<?, ?> rec) {
|
||||||
|
if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) {
|
||||||
|
return TraceObjectChangeType.VALUE_CREATED.cast(rec).getAffectedObject().getMinSnap();
|
||||||
|
}
|
||||||
|
return computeMinSnap();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceChangeRecord<?, ?> createChangeRecord() {
|
||||||
|
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, getStack(), 0L,
|
||||||
|
DBTraceUtils.lowerEndpoint(life.span()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() ||
|
int type = rec.getEventType();
|
||||||
rec.getEventType() == TraceObjectChangeType.DELETED.getType() ||
|
if (type == TraceObjectChangeType.LIFE_CHANGED.getType()) {
|
||||||
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() &&
|
RangeSet<Long> newLife = object.getLife();
|
||||||
changeApplies(rec)) {
|
if (!newLife.isEmpty()) {
|
||||||
TraceAddressSpace space =
|
life = newLife;
|
||||||
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME);
|
}
|
||||||
TraceObjectStack stack = getStack();
|
return createChangeRecord();
|
||||||
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack,
|
}
|
||||||
0L, snapFor(rec));
|
else if (type == TraceObjectChangeType.VALUE_CREATED.getType() && changeApplies(rec)) {
|
||||||
|
return createChangeRecord();
|
||||||
|
}
|
||||||
|
else if (type == TraceObjectChangeType.DELETED.getType()) {
|
||||||
|
if (life.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return createChangeRecord();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TraceStackFrame> getFrames() {
|
public List<TraceStackFrame> getFrames(long snap) {
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||||
return List.copyOf(frames);
|
return List.copyOf(frames);
|
||||||
}
|
}
|
||||||
|
@ -254,4 +254,9 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
|
||||||
manager.trace
|
manager.trace
|
||||||
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.DELETED, null, this));
|
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.DELETED, null, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFixedFrames() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,12 +122,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getComment() {
|
public String getComment(long snap) {
|
||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setComment(String comment) {
|
public void setComment(long snap, String comment) {
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
update(COMMENT_COLUMN);
|
update(COMMENT_COLUMN);
|
||||||
|
|
|
@ -23,8 +23,7 @@ import java.util.stream.*;
|
||||||
|
|
||||||
import org.apache.commons.collections4.IteratorUtils;
|
import org.apache.commons.collections4.IteratorUtils;
|
||||||
|
|
||||||
import com.google.common.collect.Iterators;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.collect.Range;
|
|
||||||
|
|
||||||
import db.DBRecord;
|
import db.DBRecord;
|
||||||
import db.StringField;
|
import db.StringField;
|
||||||
|
@ -32,7 +31,6 @@ import ghidra.dbg.target.TargetBreakpointLocation;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.*;
|
import ghidra.dbg.util.*;
|
||||||
import ghidra.lifecycle.Experimental;
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.trace.database.DBTrace;
|
import ghidra.trace.database.DBTrace;
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
|
@ -47,8 +45,7 @@ import ghidra.trace.database.stack.DBTraceObjectStack;
|
||||||
import ghidra.trace.database.stack.DBTraceObjectStackFrame;
|
import ghidra.trace.database.stack.DBTraceObjectStackFrame;
|
||||||
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
||||||
import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter;
|
import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter;
|
||||||
import ghidra.trace.database.target.LifespanCorrector.Direction;
|
import ghidra.trace.database.target.InternalTreeTraversal.Visitor;
|
||||||
import ghidra.trace.database.target.LifespanCorrector.Operation;
|
|
||||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation;
|
import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation;
|
||||||
|
@ -128,30 +125,18 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
// Canonical path
|
// Canonical path
|
||||||
static final String PATH_COLUMN_NAME = "Path";
|
static final String PATH_COLUMN_NAME = "Path";
|
||||||
static final String MIN_SNAP_COLUMN_NAME = "MinSnap";
|
|
||||||
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
|
||||||
|
|
||||||
@DBAnnotatedColumn(PATH_COLUMN_NAME)
|
@DBAnnotatedColumn(PATH_COLUMN_NAME)
|
||||||
static DBObjectColumn PATH_COLUMN;
|
static DBObjectColumn PATH_COLUMN;
|
||||||
@DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME)
|
|
||||||
static DBObjectColumn MIN_SNAP_COLUMN;
|
|
||||||
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
|
||||||
static DBObjectColumn MAX_SNAP_COLUMN;
|
|
||||||
|
|
||||||
@DBAnnotatedField(
|
@DBAnnotatedField(
|
||||||
column = PATH_COLUMN_NAME,
|
column = PATH_COLUMN_NAME,
|
||||||
codec = ObjectPathDBFieldCodec.class,
|
codec = ObjectPathDBFieldCodec.class,
|
||||||
indexed = true)
|
indexed = true)
|
||||||
private TraceObjectKeyPath path;
|
private TraceObjectKeyPath path;
|
||||||
@DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME)
|
|
||||||
private long minSnap;
|
|
||||||
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
|
|
||||||
private long maxSnap;
|
|
||||||
|
|
||||||
protected final DBTraceObjectManager manager;
|
protected final DBTraceObjectManager manager;
|
||||||
|
|
||||||
private Range<Long> lifespan;
|
|
||||||
|
|
||||||
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
||||||
|
|
||||||
public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
||||||
|
@ -168,7 +153,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
freshIfaces();
|
freshIfaces();
|
||||||
}
|
}
|
||||||
lifespan = DBTraceUtils.toRange(minSnap, maxSnap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -189,29 +173,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
Collectors.toUnmodifiableMap(e -> e.getKey(), e -> e.getValue().apply(this)));
|
Collectors.toUnmodifiableMap(e -> e.getKey(), e -> e.getValue().apply(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void set(TraceObjectKeyPath path, Range<Long> lifespan) {
|
protected void set(TraceObjectKeyPath path) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.lifespan = lifespan;
|
|
||||||
this.doSetLifespan(lifespan);
|
|
||||||
update(PATH_COLUMN);
|
update(PATH_COLUMN);
|
||||||
|
|
||||||
freshIfaces();
|
freshIfaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSetLifespan(Range<Long> lifespan) {
|
|
||||||
this.minSnap = DBTraceUtils.lowerEndpoint(lifespan);
|
|
||||||
this.maxSnap = DBTraceUtils.upperEndpoint(lifespan);
|
|
||||||
update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
|
|
||||||
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
|
@Override
|
||||||
public DBTrace getTrace() {
|
public DBTrace getTrace() {
|
||||||
return manager.trace;
|
return manager.trace;
|
||||||
|
@ -234,42 +202,97 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insert(ConflictResolution resolution) {
|
public RangeSet<Long> getLife() {
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
RangeSet<Long> result = TreeRangeSet.create();
|
||||||
|
// NOTE: connected ranges should already be coalesced
|
||||||
|
// No need to apply discreet domain
|
||||||
|
getCanonicalParents(Range.all()).forEach(v -> result.add(v.getLifespan()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DBTraceObject doCreateCanonicalParentObject() {
|
||||||
|
return manager.doCreateObject(path.parent());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DBTraceObject doGetCanonicalParentObject() {
|
||||||
|
return manager.doGetObject(path.parent());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doInsert(Range<Long> lifespan, ConflictResolution resolution) {
|
||||||
|
if (path.isRoot()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DBTraceObject parent = doCreateCanonicalParentObject();
|
||||||
|
parent.setValue(lifespan, path.key(), this, resolution);
|
||||||
|
parent.doInsert(lifespan, resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insert(Range<Long> lifespan, ConflictResolution resolution) {
|
||||||
try (LockHold hold = manager.trace.lockWrite()) {
|
try (LockHold hold = manager.trace.lockWrite()) {
|
||||||
for (InternalTraceObjectValue val : getParents()) {
|
doInsert(lifespan, resolution);
|
||||||
if (val.isCanonical() && DBTraceUtils.intersect(val.getLifespan(), lifespan)) {
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
protected void doRemove(Range<Long> span) {
|
||||||
|
if (isRoot()) {
|
||||||
|
throw new IllegalArgumentException("Cannot remove the root object");
|
||||||
|
}
|
||||||
|
DBTraceObject parent = doGetCanonicalParentObject();
|
||||||
|
parent.setValue(span, path.key(), null);
|
||||||
|
// Do not recurse on parent
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Range<Long> span) {
|
||||||
|
try (LockHold hold = manager.trace.lockWrite()) {
|
||||||
|
doRemove(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doRemoveTree(Range<Long> span) {
|
||||||
|
for (InternalTraceObjectValue value : getValues()) {
|
||||||
|
value.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||||
|
if (value.isCanonical()) {
|
||||||
|
value.getChild().doRemoveTree(span);
|
||||||
}
|
}
|
||||||
TraceObjectKeyPath parentPath = path.parent();
|
}
|
||||||
for (DBTraceObject parent : manager.getObjectsByCanonicalPath(parentPath)) {
|
}
|
||||||
if (DBTraceUtils.intersect(parent.getLifespan(), lifespan)) {
|
|
||||||
parent.setValue(lifespan, path.key(), this, resolution);
|
@Override
|
||||||
return;
|
public void removeTree(Range<Long> span) {
|
||||||
}
|
try (LockHold hold = manager.trace.lockWrite()) {
|
||||||
}
|
getCanonicalParents(span).forEach(v -> v.doTruncateOrDeleteAndEmitLifeChange(span));
|
||||||
DBTraceObject parent = manager.createObject(parentPath, lifespan);
|
doRemoveTree(span);
|
||||||
parent.setValue(lifespan, path.key(), this, resolution);
|
|
||||||
parent.insert(resolution);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectValue getCanonicalParent(long snap) {
|
public TraceObjectValue getCanonicalParent(long snap) {
|
||||||
// TODO: If this is invoked often, perhaps keep as field
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
if (isRoot()) {
|
if (isRoot()) {
|
||||||
return manager.valueStore.getObjectAt(0);
|
return manager.valueStore.getObjectAt(0);
|
||||||
}
|
}
|
||||||
|
return getCanonicalParents(Range.singleton(snap)).findAny().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends DBTraceObjectValue> getCanonicalParents(Range<Long> lifespan) {
|
||||||
|
// TODO: If this is invoked often, perhaps index
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
if (isRoot()) {
|
||||||
|
return Stream.of(manager.valueStore.getObjectAt(0));
|
||||||
|
}
|
||||||
String canonicalKey = path.key();
|
String canonicalKey = path.key();
|
||||||
TraceObjectKeyPath canonicalTail = path.parent();
|
TraceObjectKeyPath canonicalTail = path.parent();
|
||||||
return manager.valuesByChild.getLazily(this)
|
return manager.valuesByChild.getLazily(this)
|
||||||
.stream()
|
.stream()
|
||||||
.filter(v -> canonicalKey.equals(v.getEntryKey()))
|
.filter(v -> canonicalKey.equals(v.getEntryKey()))
|
||||||
.filter(v -> v.getLifespan().contains(snap))
|
.filter(v -> DBTraceUtils.intersect(v.getLifespan(), lifespan))
|
||||||
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()))
|
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()));
|
||||||
.findAny()
|
|
||||||
.orElse(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,65 +303,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<TraceObjectValPath> doGetAllPaths(Range<Long> span,
|
|
||||||
DBTraceObjectValPath post) {
|
|
||||||
if (isRoot()) {
|
|
||||||
return Stream.of(post);
|
|
||||||
}
|
|
||||||
return getParents().stream()
|
|
||||||
.filter(e -> !post.contains(e))
|
|
||||||
.flatMap(e -> e.doGetAllPaths(span, post));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<TraceObjectValPath> getAllPaths(Range<Long> span) {
|
public Stream<? extends DBTraceObjectValPath> getAllPaths(Range<Long> span) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetAllPaths(span, DBTraceObjectValPath.of());
|
if (isRoot()) {
|
||||||
}
|
return Stream.of(DBTraceObjectValPath.of());
|
||||||
}
|
}
|
||||||
|
return doStreamVisitor(span, InternalAllPathsVisitor.INSTANCE);
|
||||||
@Override
|
|
||||||
public void setLifespan(Range<Long> lifespan) {
|
|
||||||
// TODO: Could derive fixed attributes from schema and set their lifespans, too....
|
|
||||||
try (LockHold hold = manager.trace.lockWrite()) {
|
|
||||||
doSetLifespanAndEmit(lifespan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Experimental
|
|
||||||
public void correctLifespans(Direction direction, Operation operation,
|
|
||||||
ConflictResolution resolution) {
|
|
||||||
new LifespanCorrector(direction, operation, resolution).correctLifespans(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Range<Long> getLifespan() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return lifespan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMinSnap(long minSnap) {
|
|
||||||
setLifespan(DBTraceUtils.toRange(minSnap, maxSnap));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMinSnap() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return minSnap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxSnap(long maxSnap) {
|
|
||||||
setLifespan(DBTraceUtils.toRange(minSnap, maxSnap));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMaxSnap() {
|
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
|
||||||
return maxSnap;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,24 +345,65 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean doHasAnyNonRangedValues() {
|
||||||
|
for (DBTraceObjectValue val : manager.valuesByTriple
|
||||||
|
.tail(new PrimaryTriple(this, "", Long.MIN_VALUE), true)
|
||||||
|
.values()) {
|
||||||
|
if (val.getParent() != this) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void collectRangedValues(Collection<? super DBTraceObjectAddressRangeValue> result) {
|
||||||
|
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
||||||
|
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
||||||
|
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
||||||
|
if (val.getParent() != this) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doHasAnyRangedValues() {
|
||||||
|
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
||||||
|
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
||||||
|
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
||||||
|
if (val.getParent() == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Collection<? extends InternalTraceObjectValue> doGetValues() {
|
||||||
|
List<InternalTraceObjectValue> result = new ArrayList<>();
|
||||||
|
collectNonRangedValues(result);
|
||||||
|
collectRangedValues(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doHasAnyValues() {
|
||||||
|
return doHasAnyNonRangedValues() || doHasAnyRangedValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doHasAnyParents() {
|
||||||
|
return manager.valuesByChild.containsKey(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doIsConnected() {
|
||||||
|
return doHasAnyParents() || doHasAnyValues();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends InternalTraceObjectValue> getValues() {
|
public Collection<? extends InternalTraceObjectValue> getValues() {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
return doGetValues();
|
||||||
collectNonRangedValues(result);
|
|
||||||
|
|
||||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, //
|
|
||||||
?> space : manager.rangeValueMap
|
|
||||||
.getActiveMemorySpaces()) {
|
|
||||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
|
||||||
if (val.getParent() != this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,67 +619,27 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
return getValue(snap, name);
|
return getValue(snap, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<? extends DBTraceObjectValPath> doGetAncestors(Range<Long> span,
|
protected Stream<? extends DBTraceObjectValPath> doStreamVisitor(Range<Long> span,
|
||||||
DBTraceObjectValPath post, PathPredicates predicates) {
|
Visitor visitor) {
|
||||||
if (predicates.matches(getCanonicalPath().getKeyList())) {
|
return InternalTreeTraversal.INSTANCE.walkObject(visitor, this, span,
|
||||||
return Stream.of(post);
|
DBTraceObjectValPath.of());
|
||||||
}
|
|
||||||
if (isRoot()) {
|
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
return getParents().stream()
|
|
||||||
.filter(e -> !post.contains(e))
|
|
||||||
.flatMap(e -> e.doGetAncestors(span, post, predicates));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends DBTraceObjectValPath> getAncestors(
|
public Stream<? extends DBTraceObjectValPath> getAncestors(
|
||||||
Range<Long> span, PathPredicates rootPredicates) {
|
Range<Long> span, PathPredicates rootPredicates) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetAncestors(span, DBTraceObjectValPath.of(), rootPredicates);
|
return doStreamVisitor(span, new InternalAncestorsVisitor(rootPredicates));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<? extends DBTraceObjectValPath> doGetSuccessors(
|
|
||||||
Range<Long> span, DBTraceObjectValPath pre, PathPredicates predicates) {
|
|
||||||
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
|
|
||||||
if (nextKeys.isEmpty()) {
|
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<? extends DBTraceObjectValue> attrStream;
|
|
||||||
if (nextKeys.contains("")) {
|
|
||||||
attrStream = doGetAttributes().stream()
|
|
||||||
.filter(v -> DBTraceUtils.intersect(span, v.getLifespan()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
attrStream = Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<? extends DBTraceObjectValue> elemStream;
|
|
||||||
if (nextKeys.contains("[]")) {
|
|
||||||
elemStream = doGetElements().stream()
|
|
||||||
.filter(v -> DBTraceUtils.intersect(span, v.getLifespan()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
elemStream = Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<InternalTraceObjectValue> restStream = nextKeys.stream()
|
|
||||||
.filter(k -> !"".equals(k) && !"[]".equals(k))
|
|
||||||
.flatMap(k -> doGetValues(span, k).stream());
|
|
||||||
|
|
||||||
return Stream.concat(Stream.concat(attrStream, elemStream), restStream)
|
|
||||||
.flatMap(v -> v.doGetSuccessors(span, pre, predicates));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends DBTraceObjectValPath> getSuccessors(
|
public Stream<? extends DBTraceObjectValPath> getSuccessors(
|
||||||
Range<Long> span, PathPredicates relativePredicates) {
|
Range<Long> span, PathPredicates relativePredicates) {
|
||||||
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
Stream<? extends DBTraceObjectValPath> succcessors =
|
Stream<? extends DBTraceObjectValPath> succcessors =
|
||||||
doGetSuccessors(span, empty, relativePredicates);
|
doStreamVisitor(span, new InternalSuccessorsVisitor(relativePredicates));
|
||||||
if (relativePredicates.matches(List.of())) {
|
if (relativePredicates.matches(List.of())) {
|
||||||
// Pre-cat the empty path (not the empty stream)
|
// Pre-cat the empty path (not the empty stream)
|
||||||
return Stream.concat(Stream.of(empty), succcessors);
|
return Stream.concat(Stream.of(empty), succcessors);
|
||||||
|
@ -676,23 +648,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
|
|
||||||
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
|
|
||||||
if (nextKeys.isEmpty()) {
|
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
if (nextKeys.size() != 1) {
|
|
||||||
throw new IllegalArgumentException("predicates must be a singleton");
|
|
||||||
}
|
|
||||||
String next = nextKeys.iterator().next();
|
|
||||||
if (PathPattern.isWildcard(next)) {
|
|
||||||
throw new IllegalArgumentException("predicates must be a singleton");
|
|
||||||
}
|
|
||||||
return doGetOrderedValues(span, next, forward)
|
|
||||||
.flatMap(v -> v.doGetOrderedSuccessors(span, pre, predicates, forward));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends DBTraceObjectValPath> getOrderedSuccessors(Range<Long> span,
|
public Stream<? extends DBTraceObjectValPath> getOrderedSuccessors(Range<Long> span,
|
||||||
TraceObjectKeyPath relativePath, boolean forward) {
|
TraceObjectKeyPath relativePath, boolean forward) {
|
||||||
|
@ -701,8 +656,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
if (relativePath.isRoot()) {
|
if (relativePath.isRoot()) {
|
||||||
return Stream.of(empty); // Not the empty stream
|
return Stream.of(empty); // Not the empty stream
|
||||||
}
|
}
|
||||||
return doGetOrderedSuccessors(span, empty,
|
return doStreamVisitor(span,
|
||||||
new PathPattern(relativePath.getKeyList()), forward);
|
new InternalOrderedSuccessorsVisitor(relativePath, forward));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,17 +707,6 @@ 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
|
@Override
|
||||||
public InternalTraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
|
public InternalTraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
|
||||||
ConflictResolution resolution) {
|
ConflictResolution resolution) {
|
||||||
|
@ -770,35 +714,49 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
if (isDeleted()) {
|
if (isDeleted()) {
|
||||||
throw new IllegalStateException("Cannot set value on deleted object.");
|
throw new IllegalStateException("Cannot set value on deleted object.");
|
||||||
}
|
}
|
||||||
InternalTraceObjectValue oldEntry = getValue(DBTraceUtils.lowerEndpoint(lifespan), key);
|
|
||||||
Object oldVal = null;
|
|
||||||
if (oldEntry != null && oldEntry.getLifespan().encloses(lifespan)) {
|
|
||||||
oldVal = oldEntry.getValue();
|
|
||||||
}
|
|
||||||
if (resolution == ConflictResolution.DENY) {
|
if (resolution == ConflictResolution.DENY) {
|
||||||
doCheckConflicts(lifespan, key, value);
|
doCheckConflicts(lifespan, key, value);
|
||||||
}
|
}
|
||||||
InternalTraceObjectValue result = new ValueLifespanSetter(lifespan, value) {
|
var setter = new ValueLifespanSetter(lifespan, value) {
|
||||||
|
DBTraceObject canonicalLifeChanged = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
||||||
Long upper) {
|
Long upper) {
|
||||||
return Collections.unmodifiableCollection(doGetValues(lower, upper, key));
|
return Collections.unmodifiableCollection(doGetValues(lower, upper, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void remove(InternalTraceObjectValue entry) {
|
||||||
|
if (entry.isCanonical()) {
|
||||||
|
canonicalLifeChanged = entry.getChild();
|
||||||
|
}
|
||||||
|
super.remove(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InternalTraceObjectValue put(Range<Long> range, Object value) {
|
||||||
|
InternalTraceObjectValue entry = super.put(range, value);
|
||||||
|
if (entry != null && entry.isCanonical()) {
|
||||||
|
canonicalLifeChanged = entry.getChild();
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected InternalTraceObjectValue create(Range<Long> range, Object value) {
|
protected InternalTraceObjectValue create(Range<Long> range, Object value) {
|
||||||
return doCreateValue(range, key, value);
|
return doCreateValue(range, key, value);
|
||||||
}
|
}
|
||||||
}.set(lifespan, value);
|
};
|
||||||
if (result == null && oldEntry == null) {
|
InternalTraceObjectValue result = setter.set(lifespan, value);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CHANGED,
|
|
||||||
null, result != null ? result : oldEntry, oldVal, value));
|
|
||||||
|
|
||||||
// NB. It will cause another event. good.
|
// NB. This hack will cause more value events. good.
|
||||||
applyBreakpointRangeHack(lifespan, key, value, resolution);
|
applyBreakpointRangeHack(lifespan, key, value, resolution);
|
||||||
emitIfCanonicalInsertion(result);
|
DBTraceObject child = setter.canonicalLifeChanged;
|
||||||
|
if (child != null) {
|
||||||
|
child.emitEvents(
|
||||||
|
new TraceChangeRecord<>(TraceObjectChangeType.LIFE_CHANGED, null, child));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -843,36 +801,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
||||||
Class<I> ifClass) {
|
Class<I> ifClass) {
|
||||||
return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||||
.map(p -> p.getFirstParent(this).queryInterface(ifClass));
|
.map(p -> p.getSource(this).queryInterface(ifClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
|
public Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(
|
||||||
Class<? extends TargetObject> targetIf) {
|
Class<? extends TargetObject> targetIf) {
|
||||||
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
||||||
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
||||||
List<String> parentPath = getCanonicalPath().getKeyList();
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
if (!matcher.ancestorMatches(parentPath, false)) {
|
return path.streamMatchingAncestry(matcher)
|
||||||
return Stream.of();
|
.map(kp -> manager.getObjectByCanonicalPath(kp));
|
||||||
}
|
}
|
||||||
for (; !parentPath.isEmpty(); parentPath = PathUtils.parent(parentPath)) {
|
|
||||||
if (matcher.matches(parentPath)) {
|
|
||||||
return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath))
|
|
||||||
.stream()
|
|
||||||
.filter(o -> DBTraceUtils.intersect(span, o.getLifespan()))
|
|
||||||
// TODO: Post filter until GP-1301
|
|
||||||
.filter(o -> o.getTargetSchema().getInterfaces().contains(targetIf));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Stream.of();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
||||||
Range<Long> span, Class<I> ifClass) {
|
Class<I> ifClass) {
|
||||||
return queryCanonicalAncestorsTargetInterface(span,
|
return queryCanonicalAncestorsTargetInterface(TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||||
TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
.map(o -> o.queryInterface(ifClass));
|
||||||
.map(o -> o.queryInterface(ifClass));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -881,14 +828,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
|
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
|
||||||
// TODO: Post filter until GP-1301
|
// TODO: Post filter until GP-1301
|
||||||
return getSuccessors(span, matcher).filter(
|
return getSuccessors(span, matcher).filter(
|
||||||
p -> p.getLastChild(this).getTargetSchema().getInterfaces().contains(targetIf));
|
p -> p.getDestination(this).getTargetSchema().getInterfaces().contains(targetIf));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <I extends TraceObjectInterface> Stream<I> querySuccessorsInterface(Range<Long> span,
|
public <I extends TraceObjectInterface> Stream<I> querySuccessorsInterface(Range<Long> span,
|
||||||
Class<I> ifClass) {
|
Class<I> ifClass) {
|
||||||
return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||||
.map(p -> p.getLastChild(this).queryInterface(ifClass));
|
.map(p -> p.getDestination(this).queryInterface(ifClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doDelete() {
|
protected void doDelete() {
|
||||||
|
@ -897,18 +844,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
protected void doDeleteReferringValues() {
|
protected void doDeleteReferringValues() {
|
||||||
for (InternalTraceObjectValue child : getValues()) {
|
for (InternalTraceObjectValue child : getValues()) {
|
||||||
child.doDelete();
|
child.doDeleteAndEmit();
|
||||||
}
|
}
|
||||||
for (DBTraceObjectValue parent : getParents()) {
|
for (DBTraceObjectValue parent : getParents()) {
|
||||||
parent.doDelete();
|
parent.doDeleteAndEmit();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doDeleteSuccessors() {
|
|
||||||
List<DBTraceObjectValue> children = new ArrayList<>();
|
|
||||||
collectNonRangedValues(children);
|
|
||||||
for (DBTraceObjectValue child : children) {
|
|
||||||
child.doDeleteSuccessors();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,36 +859,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doDeleteTree() {
|
|
||||||
doDeleteSuccessors();
|
|
||||||
doDeleteReferringValues();
|
|
||||||
doDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteTree() {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
|
||||||
doDeleteTree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DBTraceObject truncateOrDelete(Range<Long> span) {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
doSetLifespanAndEmit(removed.get(0));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void emitEvents(TraceChangeRecord<?, ?> rec) {
|
protected void emitEvents(TraceChangeRecord<?, ?> rec) {
|
||||||
manager.trace.setChanged(rec);
|
manager.trace.setChanged(rec);
|
||||||
for (TraceObjectInterface iface : ifaces.values()) {
|
for (TraceObjectInterface iface : ifaces.values()) {
|
||||||
|
|
|
@ -15,12 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.target;
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import db.DBRecord;
|
import db.DBRecord;
|
||||||
import ghidra.dbg.util.PathPredicates;
|
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
||||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||||
|
@ -124,6 +121,11 @@ public class DBTraceObjectAddressRangeValue
|
||||||
throw new ClassCastException();
|
throw new ClassCastException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getChildOrNull() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCanonical() {
|
public boolean isCanonical() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -162,22 +164,6 @@ public class DBTraceObjectAddressRangeValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<? extends DBTraceObjectValPath> doGetSuccessors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath pre, PathPredicates predicates) {
|
|
||||||
DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this);
|
|
||||||
if (predicates.matches(path.getKeyList())) {
|
|
||||||
return Stream.of(path);
|
|
||||||
}
|
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
|
|
||||||
return doGetSuccessors(span, pre, predicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doDelete() {
|
public void doDelete() {
|
||||||
manager.rangeValueMap.deleteData(this);
|
manager.rangeValueMap.deleteData(this);
|
||||||
|
@ -186,19 +172,14 @@ public class DBTraceObjectAddressRangeValue
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||||
doDelete();
|
doDeleteAndEmit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteTree() {
|
|
||||||
delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectValue truncateOrDelete(Range<Long> span) {
|
public TraceObjectValue truncateOrDelete(Range<Long> span) {
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||||
return doTruncateOrDelete(span);
|
return doTruncateOrDeleteAndEmitLifeChange(span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.target;
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.*;
|
||||||
|
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
import ghidra.trace.model.TraceUniqueObject;
|
import ghidra.trace.model.TraceUniqueObject;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
|
@ -29,6 +30,9 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
private final String spaceValueKey;
|
private final String spaceValueKey;
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
private final T iface;
|
private final T iface;
|
||||||
|
// TODO: Memorizing life is not optimal.
|
||||||
|
// GP-1887 means to expose multiple lifespans in, e.g., TraceThread
|
||||||
|
private RangeSet<Long> life = TreeRangeSet.create();
|
||||||
|
|
||||||
public Translator(String spaceValueKey, DBTraceObject object, T iface) {
|
public Translator(String spaceValueKey, DBTraceObject object, T iface) {
|
||||||
this.spaceValueKey = spaceValueKey;
|
this.spaceValueKey = spaceValueKey;
|
||||||
|
@ -63,35 +67,67 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
// Extension point
|
// Extension point
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
|
protected TraceAddressSpace getSpace(RangeSet<Long> life) {
|
||||||
TraceAddressSpace space = spaceValueKey == null ? null
|
if (life.isEmpty()) {
|
||||||
: spaceForValue(object, object.getMinSnap(), spaceValueKey);
|
return null;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
if (rec.getEventType() == TraceObjectChangeType.LIFESPAN_CHANGED.getType()) {
|
return spaceValueKey == null ? null
|
||||||
|
: spaceForValue(object, DBTraceUtils.lowerEndpoint(life.span()), spaceValueKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceChangeRecord<?, ?> translateAdded() {
|
||||||
|
TraceChangeType<T, Void> type = getAddedType();
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
emitExtraAdded();
|
||||||
|
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceChangeRecord<?, ?> translateLifespanChanged(RangeSet<Long> oldLife) {
|
||||||
|
TraceChangeType<T, Range<Long>> type = getLifespanChangedType();
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Range<Long> oldLifespan = oldLife.span();
|
||||||
|
Range<Long> newLifespan = life.span();
|
||||||
|
emitExtraLifespanChanged(oldLifespan, newLifespan);
|
||||||
|
return new TraceChangeRecord<>(type, getSpace(life), iface, oldLifespan, newLifespan);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceChangeRecord<?, ?> translateDeleted(RangeSet<Long> life) {
|
||||||
|
TraceChangeType<T, Void> type = getDeletedType();
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
emitExtraDeleted();
|
||||||
|
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
|
||||||
|
if (rec.getEventType() == TraceObjectChangeType.LIFE_CHANGED.getType()) {
|
||||||
if (object.isDeleted()) {
|
if (object.isDeleted()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TraceChangeType<T, Range<Long>> type = getLifespanChangedType();
|
|
||||||
if (type == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
assert rec.getAffectedObject() == object;
|
assert rec.getAffectedObject() == object;
|
||||||
TraceChangeRecord<TraceObject, Range<Long>> cast =
|
RangeSet<Long> oldLife = life;
|
||||||
TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec);
|
life = object.getLife();
|
||||||
emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue());
|
boolean oldHasLife = !oldLife.isEmpty();
|
||||||
return new TraceChangeRecord<>(type, space, iface,
|
boolean newHasLife = !life.isEmpty();
|
||||||
cast.getOldValue(), cast.getNewValue());
|
if (newHasLife && oldHasLife) {
|
||||||
|
return translateLifespanChanged(oldLife);
|
||||||
|
}
|
||||||
|
else if (newHasLife) {
|
||||||
|
return translateAdded();
|
||||||
|
}
|
||||||
|
else if (oldHasLife) {
|
||||||
|
return translateDeleted(oldLife);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new AssertionError("Life changed from empty to empty?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
if (rec.getEventType() == TraceObjectChangeType.VALUE_CREATED.getType()) {
|
||||||
if (object.isDeleted()) {
|
if (object.isDeleted()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -99,8 +135,8 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
TraceChangeRecord<TraceObjectValue, Void> cast =
|
||||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
||||||
TraceObjectValue affected = cast.getAffectedObject();
|
TraceObjectValue affected = cast.getAffectedObject();
|
||||||
String key = affected.getEntryKey();
|
String key = affected.getEntryKey();
|
||||||
if (!appliesToKey(key)) {
|
if (!appliesToKey(key)) {
|
||||||
|
@ -112,16 +148,10 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
}
|
}
|
||||||
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
|
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
|
||||||
cast.getNewValue());
|
cast.getNewValue());
|
||||||
return new TraceChangeRecord<>(type, space, iface, null, null);
|
return new TraceChangeRecord<>(type, getSpace(life), iface, null, null);
|
||||||
}
|
}
|
||||||
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
|
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
|
||||||
TraceChangeType<T, Void> type = getDeletedType();
|
return translateDeleted(life);
|
||||||
if (type == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
assert rec.getAffectedObject() == object;
|
|
||||||
emitExtraDeleted();
|
|
||||||
return new TraceChangeRecord<>(type, space, iface, null, null);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -162,6 +192,6 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean isDeleted() {
|
default boolean isDeleted() {
|
||||||
return getObject().isDeleted();
|
return getObject().getLife().isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
protected final DBCachedObjectIndex<DBTraceObject, DBTraceObjectValue> valuesByChild;
|
protected final DBCachedObjectIndex<DBTraceObject, DBTraceObjectValue> valuesByChild;
|
||||||
|
|
||||||
protected final Collection<TraceObject> objectsView;
|
protected final Collection<TraceObject> objectsView;
|
||||||
|
protected final Collection<TraceObjectValue> valuesView;
|
||||||
|
|
||||||
protected TargetObjectSchema rootSchema;
|
protected TargetObjectSchema rootSchema;
|
||||||
|
|
||||||
|
@ -193,6 +194,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN);
|
valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN);
|
||||||
|
|
||||||
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
|
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
|
||||||
|
valuesView = Collections.unmodifiableCollection(valueStore.asMap().values());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadRootSchema() {
|
protected void loadRootSchema() {
|
||||||
|
@ -276,18 +278,31 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
DBTraceObjectValue entry = valueStore.create();
|
DBTraceObjectValue entry = valueStore.create();
|
||||||
entry.set(lifespan, parent, key, value);
|
entry.set(lifespan, parent, key, value);
|
||||||
|
if (parent != null) {
|
||||||
|
// Don't need event for root value created
|
||||||
|
parent.emitEvents(
|
||||||
|
new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry));
|
||||||
|
}
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DBTraceObject doCreateObject(TraceObjectKeyPath path, Range<Long> lifespan) {
|
protected DBTraceObject doCreateObject(TraceObjectKeyPath path) {
|
||||||
DBTraceObject obj = objectStore.create();
|
DBTraceObject obj = objectsByPath.getOne(path);
|
||||||
obj.set(path, lifespan);
|
if (obj != null) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
obj = objectStore.create();
|
||||||
|
obj.set(path);
|
||||||
obj.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.CREATED, null, obj));
|
obj.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.CREATED, null, obj));
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected DBTraceObject doGetObject(TraceObjectKeyPath path) {
|
||||||
|
return objectsByPath.getOne(path);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DBTraceObject createObject(TraceObjectKeyPath path, Range<Long> lifespan) {
|
public DBTraceObject createObject(TraceObjectKeyPath path) {
|
||||||
if (path.isRoot()) {
|
if (path.isRoot()) {
|
||||||
throw new IllegalArgumentException("Cannot create non-root object with root path");
|
throw new IllegalArgumentException("Cannot create non-root object with root path");
|
||||||
}
|
}
|
||||||
|
@ -295,7 +310,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
if (rootSchema == null) {
|
if (rootSchema == null) {
|
||||||
throw new IllegalStateException("No schema! Create the root object, first.");
|
throw new IllegalStateException("No schema! Create the root object, first.");
|
||||||
}
|
}
|
||||||
return doCreateObject(path, lifespan);
|
return doCreateObject(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +318,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
public TraceObjectValue createRootObject(TargetObjectSchema schema) {
|
public TraceObjectValue createRootObject(TargetObjectSchema schema) {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
setSchema(schema);
|
setSchema(schema);
|
||||||
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all());
|
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of());
|
||||||
assert root.getKey() == 0;
|
assert root.getKey() == 0;
|
||||||
InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
|
InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
|
||||||
assert val.getKey() == 0;
|
assert val.getKey() == 0;
|
||||||
|
@ -331,29 +346,29 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends DBTraceObject> getObjectsByCanonicalPath(
|
public DBTraceObject getObjectByCanonicalPath(TraceObjectKeyPath path) {
|
||||||
TraceObjectKeyPath path) {
|
return objectsByPath.getOne(path);
|
||||||
return objectsByPath.get(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends DBTraceObject> getObjectsByPath(Range<Long> span,
|
public Stream<? extends DBTraceObject> getObjectsByPath(Range<Long> span,
|
||||||
TraceObjectKeyPath path) {
|
TraceObjectKeyPath path) {
|
||||||
|
DBTraceObject root = getRootObject();
|
||||||
return getValuePaths(span, new PathPattern(path.getKeyList()))
|
return getValuePaths(span, new PathPattern(path.getKeyList()))
|
||||||
.map(p -> p.getLastChild(getRootObject()))
|
.map(p -> p.getDestinationValue(root))
|
||||||
.filter(DBTraceObject.class::isInstance)
|
.filter(DBTraceObject.class::isInstance)
|
||||||
.map(DBTraceObject.class::cast);
|
.map(DBTraceObject.class::cast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<? extends DBTraceObjectValPath> getValuePaths(
|
public Stream<? extends DBTraceObjectValPath> getValuePaths(Range<Long> span,
|
||||||
Range<Long> span, PathPredicates predicates) {
|
PathPredicates predicates) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
DBTraceObjectValue rootVal = valueStore.getObjectAt(0);
|
DBTraceObjectValue rootVal = valueStore.getObjectAt(0);
|
||||||
if (rootVal == null) {
|
if (rootVal == null) {
|
||||||
return Stream.of();
|
return Stream.of();
|
||||||
}
|
}
|
||||||
return rootVal.doGetSuccessors(span, null, predicates);
|
return rootVal.doStreamVisitor(span, new InternalSuccessorsVisitor(predicates));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +377,11 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
return objectsView;
|
return objectsView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends TraceObjectValue> getAllValues() {
|
||||||
|
return valuesView;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends TraceObjectValue> getValuesIntersecting(Range<Long> span,
|
public Collection<? extends TraceObjectValue> getValuesIntersecting(Range<Long> span,
|
||||||
AddressRange range) {
|
AddressRange range) {
|
||||||
|
@ -384,14 +404,25 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
PathMatcher matcher = rootSchema.searchFor(targetIf, true);
|
PathMatcher matcher = rootSchema.searchFor(targetIf, true);
|
||||||
return getValuePaths(span, matcher)
|
return getValuePaths(span, matcher)
|
||||||
.filter(p -> {
|
.filter(p -> {
|
||||||
TraceObject object = p.getLastChild(getRootObject());
|
TraceObject object = p.getDestination(getRootObject());
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
Msg.error(this, "NULL VALUE! " + p.getLastEntry());
|
Msg.error(this, "NULL VALUE! " + p.getLastEntry());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass));
|
.map(p -> p.getDestination(getRootObject()).queryInterface(ifClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cullDisconnectedObjects() {
|
||||||
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
|
for (DBTraceObject obj : objectStore.asMap().values()) {
|
||||||
|
if (!obj.doIsConnected()) {
|
||||||
|
obj.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -419,7 +450,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <I extends TraceObjectInterface> I doAddWithInterface(List<String> keyList,
|
protected <I extends TraceObjectInterface> I doAddWithInterface(List<String> keyList,
|
||||||
Range<Long> lifespan, Class<I> iface, ConflictResolution resolution) {
|
Class<I> iface) {
|
||||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(iface);
|
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(iface);
|
||||||
TargetObjectSchema schema = rootSchema.getSuccessorSchema(keyList);
|
TargetObjectSchema schema = rootSchema.getSuccessorSchema(keyList);
|
||||||
if (!schema.getInterfaces().contains(targetIf)) {
|
if (!schema.getInterfaces().contains(targetIf)) {
|
||||||
|
@ -427,14 +458,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
"Schema " + schema + " at " + PathUtils.toString(keyList) +
|
"Schema " + schema + " at " + PathUtils.toString(keyList) +
|
||||||
" does not provide interface " + iface.getSimpleName());
|
" does not provide interface " + iface.getSimpleName());
|
||||||
}
|
}
|
||||||
DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList), lifespan);
|
DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList));
|
||||||
obj.insert(resolution);
|
|
||||||
return obj.queryInterface(iface);
|
return obj.queryInterface(iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <I extends TraceObjectInterface> I doAddWithInterface(String path,
|
protected <I extends TraceObjectInterface> I doAddWithInterface(String path, Class<I> iface) {
|
||||||
Range<Long> lifespan, Class<I> iface, ConflictResolution resolution) {
|
return doAddWithInterface(PathUtils.parse(path), iface);
|
||||||
return doAddWithInterface(PathUtils.parse(path), lifespan, iface, resolution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <I extends TraceObjectInterface> Collection<I> getAllObjects(Class<I> iface) {
|
public <I extends TraceObjectInterface> Collection<I> getAllObjects(Class<I> iface) {
|
||||||
|
@ -523,7 +552,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
PathPredicates predicates, long snap, Class<I> iface) {
|
PathPredicates predicates, long snap, Class<I> iface) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
return seed.getSuccessors(Range.singleton(snap), predicates)
|
return seed.getSuccessors(Range.singleton(snap), predicates)
|
||||||
.map(p -> p.getLastChild(seed).queryInterface(iface))
|
.map(p -> p.getDestination(seed).queryInterface(iface))
|
||||||
.filter(i -> i != null)
|
.filter(i -> i != null)
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
@ -534,7 +563,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
TraceObjectKeyPath path, long snap, Class<I> iface) {
|
TraceObjectKeyPath path, long snap, Class<I> iface) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
return seed.getOrderedSuccessors(Range.atMost(snap), path, false)
|
return seed.getOrderedSuccessors(Range.atMost(snap), path, false)
|
||||||
.map(p -> p.getLastChild(seed).queryInterface(iface))
|
.map(p -> p.getDestination(seed).queryInterface(iface))
|
||||||
.filter(i -> i != null)
|
.filter(i -> i != null)
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
@ -553,14 +582,15 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
"breakpoint specification on the given path.");
|
"breakpoint specification on the given path.");
|
||||||
}
|
}
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
TraceObjectBreakpointLocation loc = doAddWithInterface(path, lifespan,
|
TraceObjectBreakpointLocation loc =
|
||||||
TraceObjectBreakpointLocation.class, ConflictResolution.DENY);
|
doAddWithInterface(path, TraceObjectBreakpointLocation.class);
|
||||||
loc.setName(path);
|
loc.setName(lifespan, path);
|
||||||
loc.setRange(range);
|
loc.setRange(lifespan, range);
|
||||||
// NB. Ignore threads. I'd like to deprecate that field, anyway.
|
// NB. Ignore threads. I'd like to deprecate that field, anyway.
|
||||||
loc.setKinds(kinds);
|
loc.setKinds(lifespan, kinds);
|
||||||
loc.setEnabled(enabled);
|
loc.setEnabled(lifespan, enabled);
|
||||||
loc.setComment(comment);
|
loc.setComment(lifespan, comment);
|
||||||
|
loc.getObject().insert(lifespan, ConflictResolution.DENY);
|
||||||
return loc;
|
return loc;
|
||||||
}
|
}
|
||||||
catch (DuplicateKeyException e) {
|
catch (DuplicateKeyException e) {
|
||||||
|
@ -572,11 +602,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
AddressRange range, Collection<TraceMemoryFlag> flags)
|
AddressRange range, Collection<TraceMemoryFlag> flags)
|
||||||
throws TraceOverlappedRegionException {
|
throws TraceOverlappedRegionException {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan,
|
TraceObjectMemoryRegion region =
|
||||||
TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE);
|
doAddWithInterface(path, TraceObjectMemoryRegion.class);
|
||||||
region.setName(path);
|
region.setName(lifespan, path);
|
||||||
region.setRange(range);
|
region.setRange(lifespan, range);
|
||||||
region.setFlags(flags);
|
region.setFlags(lifespan, flags);
|
||||||
|
region.getObject().insert(lifespan, ConflictResolution.TRUNCATE);
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,10 +615,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
public TraceObjectModule addModule(String path, String name, Range<Long> lifespan,
|
public TraceObjectModule addModule(String path, String name, Range<Long> lifespan,
|
||||||
AddressRange range) throws DuplicateNameException {
|
AddressRange range) throws DuplicateNameException {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
TraceObjectModule module = doAddWithInterface(path, lifespan, TraceObjectModule.class,
|
TraceObjectModule module = doAddWithInterface(path, TraceObjectModule.class);
|
||||||
ConflictResolution.DENY);
|
module.setName(lifespan, name);
|
||||||
module.setName(name);
|
module.setRange(lifespan, range);
|
||||||
module.setRange(range);
|
module.getObject().insert(lifespan, ConflictResolution.DENY);
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
catch (DuplicateKeyException e) {
|
catch (DuplicateKeyException e) {
|
||||||
|
@ -598,10 +629,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
public TraceObjectSection addSection(String path, String name, Range<Long> lifespan,
|
public TraceObjectSection addSection(String path, String name, Range<Long> lifespan,
|
||||||
AddressRange range) throws DuplicateNameException {
|
AddressRange range) throws DuplicateNameException {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
TraceObjectSection section = doAddWithInterface(path, lifespan,
|
TraceObjectSection section = doAddWithInterface(path, TraceObjectSection.class);
|
||||||
TraceObjectSection.class, ConflictResolution.DENY);
|
section.setName(lifespan, name);
|
||||||
section.setName(name);
|
section.setRange(lifespan, range);
|
||||||
section.setRange(range);
|
section.getObject().insert(lifespan, ConflictResolution.DENY);
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
catch (DuplicateKeyException e) {
|
catch (DuplicateKeyException e) {
|
||||||
|
@ -611,24 +642,41 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
|
|
||||||
public TraceObjectStack addStack(List<String> keyList, long snap) {
|
public TraceObjectStack addStack(List<String> keyList, long snap) {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStack.class,
|
TraceObjectStack stack = doAddWithInterface(keyList, TraceObjectStack.class);
|
||||||
ConflictResolution.DENY);
|
stack.getObject().insert(Range.singleton(snap), ConflictResolution.DENY);
|
||||||
|
return stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceObjectStackFrame addStackFrame(List<String> keyList, long snap) {
|
public TraceObjectStackFrame addStackFrame(List<String> keyList, long snap) {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStackFrame.class,
|
TraceObjectStackFrame frame = doAddWithInterface(keyList, TraceObjectStackFrame.class);
|
||||||
ConflictResolution.DENY);
|
frame.getObject().insert(Range.singleton(snap), ConflictResolution.DENY);
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkDuplicateThread(String path, Range<Long> lifespan)
|
||||||
|
throws DuplicateNameException {
|
||||||
|
// TODO: Change the semantics to just expand the life rather than complain of duplication
|
||||||
|
DBTraceObject exists = getObjectByCanonicalPath(TraceObjectKeyPath.parse(path));
|
||||||
|
if (exists == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exists.getLife().subRangeSet(lifespan).isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new DuplicateNameException("A thread having path '" + path +
|
||||||
|
"' already exists within an overlapping snap");
|
||||||
|
}
|
||||||
|
|
||||||
public TraceObjectThread addThread(String path, String display, Range<Long> lifespan)
|
public TraceObjectThread addThread(String path, String display, Range<Long> lifespan)
|
||||||
throws DuplicateNameException {
|
throws DuplicateNameException {
|
||||||
try (LockHold hold = trace.lockWrite()) {
|
try (LockHold hold = trace.lockWrite()) {
|
||||||
TraceObjectThread thread = doAddWithInterface(path, lifespan, TraceObjectThread.class,
|
checkDuplicateThread(path, lifespan);
|
||||||
ConflictResolution.DENY);
|
TraceObjectThread thread = doAddWithInterface(path, TraceObjectThread.class);
|
||||||
thread.setName(display);
|
thread.setName(lifespan, display);
|
||||||
|
thread.getObject().insert(lifespan, ConflictResolution.DENY);
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
catch (DuplicateKeyException e) {
|
catch (DuplicateKeyException e) {
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObject getFirstParent(TraceObject ifEmpty) {
|
public TraceObject getSource(TraceObject ifEmpty) {
|
||||||
InternalTraceObjectValue first = getFirstEntry();
|
InternalTraceObjectValue first = getFirstEntry();
|
||||||
return first == null ? ifEmpty : first.getParent();
|
return first == null ? ifEmpty : first.getParent();
|
||||||
}
|
}
|
||||||
|
@ -107,13 +107,13 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getLastValue(Object ifEmpty) {
|
public Object getDestinationValue(Object ifEmpty) {
|
||||||
InternalTraceObjectValue last = getLastEntry();
|
InternalTraceObjectValue last = getLastEntry();
|
||||||
return last == null ? ifEmpty : last.getValue();
|
return last == null ? ifEmpty : last.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObject getLastChild(TraceObject ifEmpty) {
|
public TraceObject getDestination(TraceObject ifEmpty) {
|
||||||
InternalTraceObjectValue last = getLastEntry();
|
InternalTraceObjectValue last = getLastEntry();
|
||||||
return last == null ? ifEmpty : last.getChild();
|
return last == null ? ifEmpty : last.getChild();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,10 @@ import org.apache.commons.lang3.ArrayUtils;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import db.*;
|
import db.*;
|
||||||
import ghidra.dbg.util.PathPredicates;
|
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.Visitor;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValPath;
|
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
import ghidra.util.database.*;
|
import ghidra.util.database.*;
|
||||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||||
|
@ -276,6 +275,11 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||||
return (DBTraceObject) getValue();
|
return (DBTraceObject) getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject getChildOrNull() {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<Long> getLifespan() {
|
public Range<Long> getLifespan() {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
@ -311,46 +315,9 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream<TraceObjectValPath> doGetAllPaths(Range<Long> span,
|
protected Stream<? extends DBTraceObjectValPath> doStreamVisitor(Range<Long> span,
|
||||||
DBTraceObjectValPath post) {
|
Visitor visitor) {
|
||||||
return triple.parent.doGetAllPaths(span, post.prepend(this));
|
return InternalTreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
||||||
}
|
|
||||||
|
|
||||||
protected Stream<? extends DBTraceObjectValPath> doGetAncestors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath post, PathPredicates predicates) {
|
|
||||||
return triple.parent.doGetAncestors(span, post.prepend(this), predicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<? extends DBTraceObjectValPath> doGetSuccessors(
|
|
||||||
Range<Long> span, DBTraceObjectValPath pre, PathPredicates predicates) {
|
|
||||||
DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this);
|
|
||||||
boolean includeMe = predicates.matches(path.getKeyList());
|
|
||||||
boolean descend = child != null;
|
|
||||||
if (includeMe && descend) {
|
|
||||||
return Stream.concat(Stream.of(path), child.doGetSuccessors(span, path, predicates));
|
|
||||||
}
|
|
||||||
if (includeMe) {
|
|
||||||
return Stream.of(path);
|
|
||||||
}
|
|
||||||
if (descend) {
|
|
||||||
return child.doGetSuccessors(span, path, predicates);
|
|
||||||
}
|
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
|
|
||||||
DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this);
|
|
||||||
if (predicates.matches(path.getKeyList())) {
|
|
||||||
// Singleton path, so if I match, nothing below can
|
|
||||||
return Stream.of(path);
|
|
||||||
}
|
|
||||||
if (child == null) {
|
|
||||||
return Stream.of();
|
|
||||||
}
|
|
||||||
return child.doGetOrderedSuccessors(span, path, predicates, forward);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean doIsCanonical() {
|
protected boolean doIsCanonical() {
|
||||||
|
@ -370,13 +337,6 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doDeleteSuccessors() {
|
|
||||||
if (!doIsCanonical()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
child.doDeleteTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doDelete() {
|
public void doDelete() {
|
||||||
manager.doDeleteEdge(this);
|
manager.doDeleteEdge(this);
|
||||||
|
@ -388,19 +348,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||||
if (triple.parent == null) {
|
if (triple.parent == null) {
|
||||||
throw new IllegalArgumentException("Cannot delete root value");
|
throw new IllegalArgumentException("Cannot delete root value");
|
||||||
}
|
}
|
||||||
doDelete();
|
doDeleteAndEmit();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doDeleteTree() {
|
|
||||||
doDeleteSuccessors();
|
|
||||||
doDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteTree() {
|
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
|
||||||
doDeleteTree();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,7 +358,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||||
if (triple.parent == null) {
|
if (triple.parent == null) {
|
||||||
throw new IllegalArgumentException("Cannot truncate or delete root value");
|
throw new IllegalArgumentException("Cannot truncate or delete root value");
|
||||||
}
|
}
|
||||||
return doTruncateOrDelete(span);
|
return doTruncateOrDeleteAndEmitLifeChange(span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||||
|
|
||||||
|
public enum InternalAllPathsVisitor implements SpanIntersectingVisitor {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
|
||||||
|
InternalTraceObjectValue value) {
|
||||||
|
return pre.prepend(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
|
||||||
|
if (value.getParent() == null) {
|
||||||
|
return VisitResult.EXCLUDE_FINISH;
|
||||||
|
}
|
||||||
|
if (value.getParent().isRoot()) {
|
||||||
|
// It may have other parents
|
||||||
|
return VisitResult.INCLUDE_CONTINUE;
|
||||||
|
}
|
||||||
|
return VisitResult.EXCLUDE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject continueObject(InternalTraceObjectValue value) {
|
||||||
|
return value.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
|
||||||
|
Range<Long> span, DBTraceObjectValPath path) {
|
||||||
|
return object.getParents().stream().filter(v -> !path.contains(v));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||||
|
|
||||||
|
public class InternalAncestorsVisitor implements SpanIntersectingVisitor {
|
||||||
|
|
||||||
|
protected final PathPredicates predicates;
|
||||||
|
|
||||||
|
public InternalAncestorsVisitor(PathPredicates predicates) {
|
||||||
|
this.predicates = predicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
|
||||||
|
InternalTraceObjectValue value) {
|
||||||
|
return pre == null ? DBTraceObjectValPath.of() : pre.prepend(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
|
||||||
|
return VisitResult.result(
|
||||||
|
predicates.matches(value.getParent().getCanonicalPath().getKeyList()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject continueObject(InternalTraceObjectValue value) {
|
||||||
|
return value.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
|
||||||
|
Range<Long> span, DBTraceObjectValPath path) {
|
||||||
|
if (object.isRoot()) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Can't really filter the parent values by predicates here, since the predicates are not
|
||||||
|
* matching relative paths, but canonical paths.
|
||||||
|
*/
|
||||||
|
return object.getParents().stream().filter(v -> !path.contains(v));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.PathPattern;
|
||||||
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||||
|
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||||
|
|
||||||
|
public class InternalOrderedSuccessorsVisitor implements SpanIntersectingVisitor {
|
||||||
|
|
||||||
|
protected final PathPredicates predicates;
|
||||||
|
protected final boolean forward;
|
||||||
|
|
||||||
|
public InternalOrderedSuccessorsVisitor(TraceObjectKeyPath path, boolean forward) {
|
||||||
|
this.predicates = new PathPattern(path.getKeyList());
|
||||||
|
this.forward = forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
|
||||||
|
InternalTraceObjectValue value) {
|
||||||
|
return pre == null ? DBTraceObjectValPath.of() : pre.append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
|
||||||
|
List<String> keyList = path.getKeyList();
|
||||||
|
if (predicates.matches(keyList)) {
|
||||||
|
// Singleton path, so if I match, no successor can
|
||||||
|
return VisitResult.INCLUDE_FINISH;
|
||||||
|
}
|
||||||
|
if (value.getChildOrNull() == null || predicates.successorCouldMatch(keyList, true)) {
|
||||||
|
return VisitResult.EXCLUDE_FINISH;
|
||||||
|
}
|
||||||
|
return VisitResult.EXCLUDE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject continueObject(InternalTraceObjectValue value) {
|
||||||
|
return value.getChildOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
|
||||||
|
Range<Long> span, DBTraceObjectValPath path) {
|
||||||
|
Set<String> nextKeys = predicates.getNextKeys(path.getKeyList());
|
||||||
|
if (nextKeys.isEmpty()) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
if (nextKeys.size() != 1) {
|
||||||
|
throw new IllegalArgumentException("predicates must be a singleton");
|
||||||
|
}
|
||||||
|
String next = nextKeys.iterator().next();
|
||||||
|
if (PathPattern.isWildcard(next)) {
|
||||||
|
throw new IllegalArgumentException("predicates must be a singleton");
|
||||||
|
}
|
||||||
|
return object.doGetOrderedValues(span, next, forward);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||||
|
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||||
|
|
||||||
|
public class InternalSuccessorsVisitor implements SpanIntersectingVisitor {
|
||||||
|
|
||||||
|
protected final PathPredicates predicates;
|
||||||
|
|
||||||
|
public InternalSuccessorsVisitor(PathPredicates predicates) {
|
||||||
|
this.predicates = predicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
|
||||||
|
InternalTraceObjectValue value) {
|
||||||
|
return pre == null ? DBTraceObjectValPath.of() : pre.append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
|
||||||
|
List<String> keyList = path.getKeyList();
|
||||||
|
return VisitResult.result(predicates.matches(keyList),
|
||||||
|
predicates.successorCouldMatch(keyList, true) && value.getChildOrNull() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DBTraceObject continueObject(InternalTraceObjectValue value) {
|
||||||
|
return value.getChildOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
|
||||||
|
Range<Long> span, DBTraceObjectValPath pre) {
|
||||||
|
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
|
||||||
|
if (nextKeys.isEmpty()) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<? extends DBTraceObjectValue> attrStream;
|
||||||
|
if (nextKeys.contains("")) {
|
||||||
|
attrStream = object.doGetAttributes()
|
||||||
|
.stream()
|
||||||
|
.filter(v -> DBTraceUtils.intersect(span, v.getLifespan()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attrStream = Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<? extends DBTraceObjectValue> elemStream;
|
||||||
|
if (nextKeys.contains("[]")) {
|
||||||
|
elemStream = object.doGetElements()
|
||||||
|
.stream()
|
||||||
|
.filter(v -> DBTraceUtils.intersect(span, v.getLifespan()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elemStream = Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<InternalTraceObjectValue> restStream = nextKeys.stream()
|
||||||
|
.filter(k -> !"".equals(k) && !"[]".equals(k))
|
||||||
|
.flatMap(k -> object.doGetValues(span, k).stream());
|
||||||
|
|
||||||
|
return Stream.concat(Stream.concat(attrStream, elemStream), restStream);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,13 +16,11 @@
|
||||||
package ghidra.trace.database.target;
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.apache.commons.collections4.IterableUtils;
|
import org.apache.commons.collections4.IterableUtils;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.util.PathPredicates;
|
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.database.DBTraceUtils.LifespanMapSetter;
|
import ghidra.trace.database.DBTraceUtils.LifespanMapSetter;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
|
@ -67,7 +65,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
keep = entry;
|
keep = entry;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
entry.doDelete();
|
entry.doDeleteAndEmit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -87,7 +85,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (keep != null && Objects.equals(this.value, value)) {
|
if (keep != null && Objects.equals(this.value, value)) {
|
||||||
keep.doSetLifespan(range);
|
keep.doSetLifespanAndEmit(range);
|
||||||
return keep;
|
return keep;
|
||||||
}
|
}
|
||||||
for (InternalTraceObjectValue k : kept) {
|
for (InternalTraceObjectValue k : kept) {
|
||||||
|
@ -114,7 +112,16 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
@Override
|
@Override
|
||||||
DBTraceObject getChild();
|
DBTraceObject getChild();
|
||||||
|
|
||||||
void doSetLifespan(Range<Long> range);
|
DBTraceObject getChildOrNull();
|
||||||
|
|
||||||
|
void doSetLifespan(Range<Long> lifespan);
|
||||||
|
|
||||||
|
default void doSetLifespanAndEmit(Range<Long> lifespan) {
|
||||||
|
Range<Long> oldLifespan = getLifespan();
|
||||||
|
doSetLifespan(lifespan);
|
||||||
|
getParent().emitEvents(new TraceChangeRecord<>(
|
||||||
|
TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void setLifespan(Range<Long> lifespan) {
|
default void setLifespan(Range<Long> lifespan) {
|
||||||
|
@ -124,7 +131,6 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
@Override
|
@Override
|
||||||
default void setLifespan(Range<Long> lifespan, ConflictResolution resolution) {
|
default void setLifespan(Range<Long> lifespan, ConflictResolution resolution) {
|
||||||
try (LockHold hold = getTrace().lockWrite()) {
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
Range<Long> oldLifespan = getLifespan();
|
|
||||||
if (getParent() == null) {
|
if (getParent() == null) {
|
||||||
throw new IllegalArgumentException("Cannot set lifespan of root value");
|
throw new IllegalArgumentException("Cannot set lifespan of root value");
|
||||||
}
|
}
|
||||||
|
@ -145,29 +151,37 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
||||||
return getParent().doCreateValue(range, getEntryKey(), value);
|
return getParent().doCreateValue(range, getEntryKey(), value);
|
||||||
}
|
}
|
||||||
}.set(lifespan, getValue());
|
}.set(lifespan, getValue());
|
||||||
getParent().emitEvents(new TraceChangeRecord<>(
|
|
||||||
TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<? extends DBTraceObjectValPath> doGetSuccessors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath pre, PathPredicates predicates);
|
|
||||||
|
|
||||||
Stream<? extends DBTraceObjectValPath> doGetOrderedSuccessors(Range<Long> span,
|
|
||||||
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward);
|
|
||||||
|
|
||||||
void doDelete();
|
void doDelete();
|
||||||
|
|
||||||
|
default void doDeleteAndEmit() {
|
||||||
|
DBTraceObject parent = getParent();
|
||||||
|
doDelete();
|
||||||
|
parent.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.VALUE_DELETED, null, this));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
DBTraceObject getParent();
|
DBTraceObject getParent();
|
||||||
|
|
||||||
|
default InternalTraceObjectValue doTruncateOrDeleteAndEmitLifeChange(Range<Long> span) {
|
||||||
|
if (!isCanonical()) {
|
||||||
|
return doTruncateOrDelete(span);
|
||||||
|
}
|
||||||
|
DBTraceObject child = getChildOrNull();
|
||||||
|
InternalTraceObjectValue result = doTruncateOrDelete(span);
|
||||||
|
child.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFE_CHANGED, null, child));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
default InternalTraceObjectValue doTruncateOrDelete(Range<Long> span) {
|
default InternalTraceObjectValue doTruncateOrDelete(Range<Long> span) {
|
||||||
List<Range<Long>> removed = DBTraceUtils.subtract(getLifespan(), span);
|
List<Range<Long>> removed = DBTraceUtils.subtract(getLifespan(), span);
|
||||||
if (removed.isEmpty()) {
|
if (removed.isEmpty()) {
|
||||||
doDelete();
|
doDeleteAndEmit();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
doSetLifespan(removed.get(0));
|
doSetLifespanAndEmit(removed.get(0));
|
||||||
if (removed.size() == 2) {
|
if (removed.size() == 2) {
|
||||||
return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue());
|
return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
public enum InternalTreeTraversal {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public enum VisitResult {
|
||||||
|
INCLUDE_CONTINUE,
|
||||||
|
INCLUDE_FINISH,
|
||||||
|
EXCLUDE_CONTINUE,
|
||||||
|
EXCLUDE_FINISH,
|
||||||
|
;
|
||||||
|
|
||||||
|
public static VisitResult result(boolean include, boolean cont) {
|
||||||
|
if (include) {
|
||||||
|
if (cont) {
|
||||||
|
return INCLUDE_CONTINUE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return INCLUDE_FINISH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (cont) {
|
||||||
|
return EXCLUDE_CONTINUE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return EXCLUDE_FINISH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Visitor {
|
||||||
|
Range<Long> composeSpan(Range<Long> pre, InternalTraceObjectValue value);
|
||||||
|
|
||||||
|
DBTraceObjectValPath composePath(DBTraceObjectValPath pre, InternalTraceObjectValue value);
|
||||||
|
|
||||||
|
VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path);
|
||||||
|
|
||||||
|
DBTraceObject continueObject(InternalTraceObjectValue value);
|
||||||
|
|
||||||
|
Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
|
||||||
|
Range<Long> span, DBTraceObjectValPath path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SpanIntersectingVisitor extends Visitor {
|
||||||
|
@Override
|
||||||
|
default Range<Long> composeSpan(Range<Long> pre, InternalTraceObjectValue value) {
|
||||||
|
Range<Long> valSpan = value.getLifespan();
|
||||||
|
if (!pre.isConnected(valSpan)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Range<Long> span = pre.intersection(valSpan);
|
||||||
|
if (span.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<? extends DBTraceObjectValPath> walkValue(Visitor visitor,
|
||||||
|
InternalTraceObjectValue value, Range<Long> span, DBTraceObjectValPath path) {
|
||||||
|
Range<Long> compSpan = visitor.composeSpan(span, value);
|
||||||
|
if (compSpan == null) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
DBTraceObjectValPath compPath = visitor.composePath(path, value);
|
||||||
|
if (compPath == null) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (visitor.visitValue(value, compPath)) {
|
||||||
|
case INCLUDE_FINISH:
|
||||||
|
return Stream.of(compPath);
|
||||||
|
case EXCLUDE_FINISH:
|
||||||
|
return Stream.empty();
|
||||||
|
case INCLUDE_CONTINUE: {
|
||||||
|
DBTraceObject object = visitor.continueObject(value);
|
||||||
|
return Stream.concat(Stream.of(compPath),
|
||||||
|
walkObject(visitor, object, compSpan, compPath));
|
||||||
|
}
|
||||||
|
case EXCLUDE_CONTINUE: {
|
||||||
|
DBTraceObject object = visitor.continueObject(value);
|
||||||
|
return walkObject(visitor, object, compSpan, compPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<? extends DBTraceObjectValPath> walkObject(Visitor visitor, DBTraceObject object,
|
||||||
|
Range<Long> span, DBTraceObjectValPath path) {
|
||||||
|
return visitor.continueValues(object, span, path)
|
||||||
|
.flatMap(v -> walkValue(visitor, v, span, path));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,222 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.trace.database.target;
|
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
|
||||||
|
|
||||||
import ghidra.lifecycle.Experimental;
|
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
|
||||||
import ghidra.trace.model.target.TraceObject;
|
|
||||||
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
|
||||||
|
|
||||||
@Experimental
|
|
||||||
public class LifespanCorrector {
|
|
||||||
/**
|
|
||||||
* A visitor for lifespan correction
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Implementors must implement only the upward pair, or only the downward pair
|
|
||||||
*/
|
|
||||||
public interface Visitor {
|
|
||||||
/**
|
|
||||||
* Visit an object on the upward side of traversal
|
|
||||||
*
|
|
||||||
* @param object the object
|
|
||||||
*/
|
|
||||||
default void visitObjectUpward(TraceObject object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visit an object on the downward side of traversal
|
|
||||||
*
|
|
||||||
* @param object the object
|
|
||||||
*/
|
|
||||||
default void visitObjectDownward(TraceObject object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visit a value on the upward side of traversal
|
|
||||||
*
|
|
||||||
* @param value the value, guaranteed to have a child
|
|
||||||
*/
|
|
||||||
default void visitValueUpward(TraceObjectValue value) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visit a value on the downward side of traversal
|
|
||||||
*
|
|
||||||
* @param value the value, guaranteed to have a child
|
|
||||||
*/
|
|
||||||
default void visitValueDownward(TraceObjectValue value) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Direction {
|
|
||||||
ANCESTORS {
|
|
||||||
@Override
|
|
||||||
public void visit(TraceObject seed, Visitor visitor) {
|
|
||||||
visitObjectAncestors(seed, visitor, UP | DOWN);
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
SUCCESSORS {
|
|
||||||
@Override
|
|
||||||
public void visit(TraceObject seed, Visitor visitor) {
|
|
||||||
visitObjectSuccessors(seed, visitor, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
BOTH {
|
|
||||||
@Override
|
|
||||||
public void visit(TraceObject seed, Visitor visitor) {
|
|
||||||
visitObjectAncestors(seed, visitor, DOWN);
|
|
||||||
visitObjectSuccessors(seed, visitor, false);
|
|
||||||
visitObjectAncestors(seed, visitor, UP);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static final int UP = 1;
|
|
||||||
static final int DOWN = 2;
|
|
||||||
|
|
||||||
public abstract void visit(TraceObject seed, Visitor visitor);
|
|
||||||
|
|
||||||
void visitObjectAncestors(TraceObject object, Visitor visitor, int mode) {
|
|
||||||
if ((mode & UP) == UP) {
|
|
||||||
visitor.visitObjectUpward(object);
|
|
||||||
}
|
|
||||||
if (!object.isRoot()) {
|
|
||||||
for (TraceObjectValue value : object.getParents()) {
|
|
||||||
// Yes, over time, there may be multiple canonical values
|
|
||||||
if (value.isCanonical() && !value.isDeleted()) {
|
|
||||||
visitValueAncestors(value, visitor, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((mode & DOWN) == DOWN) {
|
|
||||||
visitor.visitObjectDownward(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void visitValueAncestors(TraceObjectValue value, Visitor visitor, int mode) {
|
|
||||||
visitor.visitValueUpward(value);
|
|
||||||
visitObjectAncestors(value.getParent(), visitor, mode);
|
|
||||||
visitor.visitValueDownward(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void visitObjectSuccessors(TraceObject object, Visitor visitor, boolean includeCur) {
|
|
||||||
if (includeCur) {
|
|
||||||
visitor.visitObjectDownward(object);
|
|
||||||
}
|
|
||||||
for (TraceObjectValue value : object.getValues()) {
|
|
||||||
if (value.isCanonical() && !value.isDeleted()) {
|
|
||||||
visitValueSuccessors(value, visitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (includeCur) {
|
|
||||||
visitor.visitObjectUpward(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void visitValueSuccessors(TraceObjectValue value, Visitor visitor) {
|
|
||||||
if (!(value.getValue() instanceof TraceObject)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
visitor.visitValueDownward(value);
|
|
||||||
visitObjectSuccessors(value.getChild(), visitor, true);
|
|
||||||
visitor.visitValueUpward(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider non-canonical paths?
|
|
||||||
|
|
||||||
public enum Operation {
|
|
||||||
EXPAND {
|
|
||||||
@Override
|
|
||||||
Visitor getVisitor(ConflictResolution resolution) {
|
|
||||||
return new Visitor() {
|
|
||||||
@Override
|
|
||||||
public void visitObjectUpward(TraceObject object) {
|
|
||||||
Range<Long> span = object.getLifespan();
|
|
||||||
for (TraceObjectValue value : object.getValues()) {
|
|
||||||
span = span.span(value.getLifespan());
|
|
||||||
}
|
|
||||||
object.setLifespan(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitValueUpward(TraceObjectValue value) {
|
|
||||||
Range<Long> newLife =
|
|
||||||
value.getLifespan().span(value.getChild().getLifespan());
|
|
||||||
value.setLifespan(newLife, resolution);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SHRINK {
|
|
||||||
@Override
|
|
||||||
Visitor getVisitor(ConflictResolution resolution) {
|
|
||||||
return new Visitor() {
|
|
||||||
@Override
|
|
||||||
public void visitObjectDownward(TraceObject object) {
|
|
||||||
for (TraceObjectValue value : object.getValues()) {
|
|
||||||
if (!DBTraceUtils.intersect(object.getLifespan(),
|
|
||||||
value.getLifespan())) {
|
|
||||||
value.delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
value.setLifespan(
|
|
||||||
value.getLifespan().intersection(object.getLifespan()), resolution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitValueDownward(TraceObjectValue value) {
|
|
||||||
/**
|
|
||||||
* It'd be an odd circumstance for two canonical entries to exist for the
|
|
||||||
* same parent and child. If that happens, this will cause the child to
|
|
||||||
* become detached, since those entries cannot intersect.
|
|
||||||
*/
|
|
||||||
if (!DBTraceUtils.intersect(value.getLifespan(),
|
|
||||||
value.getChild().getLifespan())) {
|
|
||||||
value.getChild().delete();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Range<Long> newLife =
|
|
||||||
value.getLifespan().intersection(value.getChild().getLifespan());
|
|
||||||
value.getChild().setLifespan(newLife);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
abstract Visitor getVisitor(ConflictResolution resolution);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Direction direction;
|
|
||||||
private final Operation operation;
|
|
||||||
private final ConflictResolution resolution;
|
|
||||||
|
|
||||||
public LifespanCorrector(Direction direction, Operation operation,
|
|
||||||
ConflictResolution resolution) {
|
|
||||||
this.direction = direction;
|
|
||||||
this.operation = operation;
|
|
||||||
this.resolution = resolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void correctLifespans(TraceObject seed) {
|
|
||||||
direction.visit(seed, operation.getVisitor(resolution));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -100,10 +100,15 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(Range<Long> lifespan, String name) {
|
||||||
|
object.setValue(lifespan, TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
setName(computeSpan(), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +121,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCreationSnap() {
|
public long getCreationSnap() {
|
||||||
return object.getMinSnap();
|
return computeMinSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -128,7 +133,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getDestructionSnap() {
|
public long getDestructionSnap() {
|
||||||
return object.getMaxSnap();
|
return computeMaxSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -138,7 +143,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<Long> getLifespan() {
|
public Range<Long> getLifespan() {
|
||||||
return object.getLifespan();
|
return computeSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -156,7 +161,9 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() {
|
public void delete() {
|
||||||
object.deleteTree();
|
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||||
|
object.removeTree(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -60,24 +60,22 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||||
* An object was created, but not yet inserted.
|
* An object was created, but not yet inserted.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Between the {@link #CREATED} and {@link #INSERTED} events, an object is considered
|
* Between the {@link #CREATED} event and the first {@link #LIFE_CHANGED} event, an object
|
||||||
* "incomplete," because it is likely missing its attributes. Thus, a trace client must take
|
* is considered "incomplete," because it is likely missing its attributes. Thus, a trace
|
||||||
* care to ensure all attributes, especially fixed attributes, are added to the object
|
* client must take care to ensure all attributes, especially fixed attributes, are added to
|
||||||
* before it is inserted at its canonical path. Listeners may use
|
* 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
|
* {@link TraceObject#getCanonicalParent(long)} to check if an object is complete for a
|
||||||
* given snapshot.
|
* given snapshot.
|
||||||
*/
|
*/
|
||||||
public static final TraceObjectChangeType<TraceObject, Void> CREATED =
|
public static final TraceObjectChangeType<TraceObject, Void> CREATED =
|
||||||
new TraceObjectChangeType<>();
|
new TraceObjectChangeType<>();
|
||||||
/**
|
/**
|
||||||
* An object was inserted at its canonical path.
|
* An object's life changed.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* One of its canonical parents was created, deleted, or had its lifespan change.
|
||||||
*/
|
*/
|
||||||
public static final TraceObjectChangeType<TraceObject, TraceObjectValue> INSERTED =
|
public static final TraceObjectChangeType<TraceObject, Void> LIFE_CHANGED =
|
||||||
new TraceObjectChangeType<>();
|
|
||||||
/**
|
|
||||||
* An object's lifespan changed.
|
|
||||||
*/
|
|
||||||
public static final TraceObjectChangeType<TraceObject, Range<Long>> LIFESPAN_CHANGED =
|
|
||||||
new TraceObjectChangeType<>();
|
new TraceObjectChangeType<>();
|
||||||
/**
|
/**
|
||||||
* An object was deleted.
|
* An object was deleted.
|
||||||
|
@ -85,27 +83,20 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||||
public static final TraceObjectChangeType<TraceObject, Void> DELETED =
|
public static final TraceObjectChangeType<TraceObject, Void> DELETED =
|
||||||
new TraceObjectChangeType<>();
|
new TraceObjectChangeType<>();
|
||||||
/**
|
/**
|
||||||
* An object's value changed.
|
* A value entry was created.
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 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 =
|
public static final TraceObjectChangeType<TraceObjectValue, Void> VALUE_CREATED =
|
||||||
new TraceObjectChangeType<>();
|
new TraceObjectChangeType<>();
|
||||||
/**
|
/**
|
||||||
* An object's value changed in lifespan.
|
* A value entry changed in lifespan.
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This is only called for the value on which {@link TraceObjectValue#setLifespan(Range)} or
|
|
||||||
* similar is called. If other values are truncated or deleted, there is no event. Listeners
|
|
||||||
* concerned about a single snap need only check if the snap is contained in the new and old
|
|
||||||
* lifespans. Listeners concerned about the full timeline can refresh the parent object's
|
|
||||||
* values, or compute the coalescing and truncation manually.
|
|
||||||
*/
|
*/
|
||||||
public static final TraceObjectChangeType<TraceObjectValue, Range<Long>> //
|
public static final TraceObjectChangeType<TraceObjectValue, Range<Long>> //
|
||||||
VALUE_LIFESPAN_CHANGED = new TraceObjectChangeType<>();
|
VALUE_LIFESPAN_CHANGED = new TraceObjectChangeType<>();
|
||||||
|
/**
|
||||||
|
* A value entry was deleted.
|
||||||
|
*/
|
||||||
|
public static final TraceObjectChangeType<TraceObjectValue, Void> VALUE_DELETED =
|
||||||
|
new TraceObjectChangeType<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class TraceBookmarkChangeType<T, U> extends DefaultTraceChangeType<T, U> {
|
public static final class TraceBookmarkChangeType<T, U> extends DefaultTraceChangeType<T, U> {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.breakpoint;
|
package ghidra.trace.model.breakpoint;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetBreakpointLocation;
|
import ghidra.dbg.target.TargetBreakpointLocation;
|
||||||
|
@ -42,5 +44,13 @@ public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObj
|
||||||
|
|
||||||
void setLifespan(Range<Long> lifespan) throws DuplicateNameException;
|
void setLifespan(Range<Long> lifespan) throws DuplicateNameException;
|
||||||
|
|
||||||
void setRange(AddressRange range);
|
void setRange(Range<Long> lifespan, AddressRange range);
|
||||||
|
|
||||||
|
void setName(Range<Long> lifespan, String name);
|
||||||
|
|
||||||
|
void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds);
|
||||||
|
|
||||||
|
void setEnabled(Range<Long> lifespan, boolean enabled);
|
||||||
|
|
||||||
|
void setComment(Range<Long> lifespan, String comment);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,4 +43,6 @@ public interface TraceObjectBreakpointSpec extends TraceBreakpoint, TraceObjectI
|
||||||
void setLifespan(Range<Long> lifespan) throws DuplicateNameException;
|
void setLifespan(Range<Long> lifespan) throws DuplicateNameException;
|
||||||
|
|
||||||
Collection<? extends TraceObjectBreakpointLocation> getLocations();
|
Collection<? extends TraceObjectBreakpointLocation> getLocations();
|
||||||
|
|
||||||
|
void setKinds(Range<Long> lifespan, Collection<TraceBreakpointKind> kinds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetMemoryRegion;
|
import ghidra.dbg.target.TargetMemoryRegion;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.model.target.TraceObjectInterface;
|
import ghidra.trace.model.target.TraceObjectInterface;
|
||||||
import ghidra.trace.model.target.annot.TraceObjectInfo;
|
import ghidra.trace.model.target.annot.TraceObjectInfo;
|
||||||
|
|
||||||
|
@ -35,6 +36,10 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
|
||||||
public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectInterface {
|
public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectInterface {
|
||||||
String KEY_VOLATILE = "_volatile";
|
String KEY_VOLATILE = "_volatile";
|
||||||
|
|
||||||
|
void setName(Range<Long> lifespan, String name);
|
||||||
|
|
||||||
|
void setRange(Range<Long> lifespan, AddressRange range);
|
||||||
|
|
||||||
void setFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags);
|
void setFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags);
|
||||||
|
|
||||||
void addFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags);
|
void addFlags(Range<Long> lifespan, Collection<TraceMemoryFlag> flags);
|
||||||
|
|
|
@ -17,8 +17,11 @@ package ghidra.trace.model.modules;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetModule;
|
import ghidra.dbg.target.TargetModule;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.database.module.TraceObjectSection;
|
import ghidra.trace.database.module.TraceObjectSection;
|
||||||
import ghidra.trace.model.target.TraceObjectInterface;
|
import ghidra.trace.model.target.TraceObjectInterface;
|
||||||
import ghidra.trace.model.target.annot.TraceObjectInfo;
|
import ghidra.trace.model.target.annot.TraceObjectInfo;
|
||||||
|
@ -31,6 +34,11 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
|
||||||
TargetModule.RANGE_ATTRIBUTE_NAME
|
TargetModule.RANGE_ATTRIBUTE_NAME
|
||||||
})
|
})
|
||||||
public interface TraceObjectModule extends TraceModule, TraceObjectInterface {
|
public interface TraceObjectModule extends TraceModule, TraceObjectInterface {
|
||||||
|
|
||||||
|
void setName(Range<Long> lifespan, String name);
|
||||||
|
|
||||||
|
void setRange(Range<Long> lifespan, AddressRange range);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Collection<? extends TraceObjectSection> getSections();
|
Collection<? extends TraceObjectSection> getSections();
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,10 @@ package ghidra.trace.model.stack;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.lifecycle.Experimental;
|
||||||
|
import ghidra.lifecycle.Transitional;
|
||||||
import ghidra.trace.model.TraceUniqueObject;
|
import ghidra.trace.model.TraceUniqueObject;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,9 +59,13 @@ public interface TraceStack extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Set the depth of the stack by adding or deleting frames to or from the specified end
|
* Set the depth of the stack by adding or deleting frames to or from the specified end
|
||||||
*
|
*
|
||||||
* Note that pushing new frames onto a stack does not adjust the frame level of any associated
|
* <p>
|
||||||
* annotations. Some utility to adjust those annotations. Cannot just rename tables, since those
|
* Note that pushing new frames onto a stack does not adjust the frame level of any
|
||||||
* contain annotations for the given level, regardless of the particular stack.
|
* frame-associated managers or spaces, e.g., that returned by
|
||||||
|
* {@link TraceMemoryManager#getMemoryRegisterSpace(TraceThread, int, boolean)}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the experimental object mode is successful, this method should be deleted.
|
||||||
*
|
*
|
||||||
* @param depth the desired depth
|
* @param depth the desired depth
|
||||||
* @param atInner true if frames should be "pushed"
|
* @param atInner true if frames should be "pushed"
|
||||||
|
@ -78,12 +85,27 @@ public interface TraceStack extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get all (known) frames in this stack
|
* Get all (known) frames in this stack
|
||||||
*
|
*
|
||||||
|
* @param snap the snap (only relevant in the experimental objects mode. Ordinarily, the frames
|
||||||
|
* are fixed over the stack's lifetime)
|
||||||
* @return the list of frames
|
* @return the list of frames
|
||||||
*/
|
*/
|
||||||
List<TraceStackFrame> getFrames();
|
List<TraceStackFrame> getFrames(@Experimental long snap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete this stack and its frames
|
* Delete this stack and its frames
|
||||||
*/
|
*/
|
||||||
void delete();
|
void delete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this stack'sframes are fixed for its lifetime
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is a transitional method, since the experimental objects mode breaks with the normal
|
||||||
|
* stack/frame model. Essentially, this returns true if the normal model is being used, and
|
||||||
|
* false if the object-based model is being used.
|
||||||
|
*
|
||||||
|
* @return true if fixed, false if object-based (dynamic)
|
||||||
|
*/
|
||||||
|
@Transitional
|
||||||
|
boolean hasFixedFrames();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,69 @@ package ghidra.trace.model.stack;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import ghidra.lifecycle.Experimental;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A frame in a {@link TraceStack}
|
||||||
|
*/
|
||||||
public interface TraceStackFrame {
|
public interface TraceStackFrame {
|
||||||
|
/**
|
||||||
|
* Get the containing stack
|
||||||
|
*
|
||||||
|
* @return the stack
|
||||||
|
*/
|
||||||
TraceStack getStack();
|
TraceStack getStack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the frame's position in the containing stack
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 0 represents the innermost frame or top of the stack.
|
||||||
|
*
|
||||||
|
* @return the frame's level
|
||||||
|
*/
|
||||||
int getLevel();
|
int getLevel();
|
||||||
|
|
||||||
Address getProgramCounter(long snap);
|
/**
|
||||||
|
* Get the program counter at the given snap
|
||||||
|
*
|
||||||
|
* @param snap the snap (only relevant in the experimental objects mode. Ordinarily, the PC is
|
||||||
|
* fixed over the containing stack's lifetime)
|
||||||
|
* @return the program counter
|
||||||
|
*/
|
||||||
|
Address getProgramCounter(@Experimental long snap);
|
||||||
|
|
||||||
void setProgramCounter(Range<Long> span, Address pc);
|
/**
|
||||||
|
* Set the program counter over the given span
|
||||||
|
*
|
||||||
|
* @param span the span (only relevant in the experimental objects mode. Ordinarily, the PC is
|
||||||
|
* fixed over the containing stack's lifetime)
|
||||||
|
* @param pc the program counter
|
||||||
|
*/
|
||||||
|
void setProgramCounter(@Experimental Range<Long> span, Address pc);
|
||||||
|
|
||||||
String getComment();
|
/**
|
||||||
|
* Get the user comment for the frame
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* In the experimental objects mode, this actually gets the comment in the listing at the
|
||||||
|
* frame's program counter for the given snap.
|
||||||
|
*
|
||||||
|
* @param snap the snap (only relevant in the experimental objects mode)
|
||||||
|
* @return the (nullable) comment
|
||||||
|
*/
|
||||||
|
String getComment(@Experimental long snap);
|
||||||
|
|
||||||
void setComment(String comment);
|
/**
|
||||||
|
* Set the user comment for the frame
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* In the experimental objects mode, this actually sets the comment in the listing at the
|
||||||
|
* frame's program counter for the given snap.
|
||||||
|
*
|
||||||
|
* @param snap the snap (only relevant in the experimental objects mode)
|
||||||
|
* @param comment the (nullable) comment
|
||||||
|
*/
|
||||||
|
void setComment(@Experimental long snap, String comment);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,22 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.target;
|
package ghidra.trace.model.target;
|
||||||
|
|
||||||
|
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when there are "duplicate keys" and the {@link ConflictResolution#DENY} strategy is passed
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* There are said to be "duplicate keys" when two value entries having the same parent and key have
|
||||||
|
* overlapping lifespans. Such would create the possibility of a non-uniquely-defined value for a
|
||||||
|
* given path, and so it is not allowed.
|
||||||
|
*/
|
||||||
public class DuplicateKeyException extends RuntimeException {
|
public class DuplicateKeyException extends RuntimeException {
|
||||||
|
/**
|
||||||
|
* Notify of a given conflicting key
|
||||||
|
*
|
||||||
|
* @param key the key in conflict
|
||||||
|
*/
|
||||||
public DuplicateKeyException(String key) {
|
public DuplicateKeyException(String key) {
|
||||||
super(key);
|
super(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Collection;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
import com.google.common.collect.RangeSet;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
|
@ -59,33 +60,81 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
TraceObjectKeyPath getCanonicalPath();
|
TraceObjectKeyPath getCanonicalPath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts this object at its canonical path for its lifespan
|
* Get all ranges of this object's life
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Any ancestor which does not exist is created with the same lifespan as this object. Values
|
* Essentially, this is the union of the lifespans of all canonical parent values
|
||||||
* are set with the same lifespan. Only the canonical path is considered when looking for
|
|
||||||
* existing ancestry. Any whose lifespan intersects that of this object is considered
|
|
||||||
* "existing." If an existing ancestor is detached, this object will still become its successor,
|
|
||||||
* and the resulting subtree will remain detached.
|
|
||||||
*
|
*
|
||||||
|
* @return the range set for snaps at which this object is considered "inserted."
|
||||||
|
*/
|
||||||
|
RangeSet<Long> getLife();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts this object at its canonical path for the given lifespan
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Any ancestor which does not exist is created. Values' lifespans are added or expanded to
|
||||||
|
* contain the given lifespan. Only the canonical path is considered when looking for existing
|
||||||
|
* ancestry.
|
||||||
|
*
|
||||||
|
* @param the minimum lifespan of edges from the root to this object
|
||||||
* @param resolution the rule for handling duplicate keys when setting values.
|
* @param resolution the rule for handling duplicate keys when setting values.
|
||||||
*/
|
*/
|
||||||
void insert(ConflictResolution resolution);
|
void insert(Range<Long> lifespan, ConflictResolution resolution);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this object from its canonical path for the given lifespan
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Truncate the lifespans of this object's canonical parent value by the given span. If the
|
||||||
|
* parent value's lifespan is contained in the given span, the parent value will be deleted.
|
||||||
|
*
|
||||||
|
* @param span the span during which this object should be removed
|
||||||
|
*/
|
||||||
|
void remove(Range<Long> span);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this object and its successors from their canonical paths for the given span
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Truncate the lifespans of this object's canonical parent value and all canonical values
|
||||||
|
* succeeding this object. If a truncated value's lifespan is contained in the given span, the
|
||||||
|
* value will be deleted.
|
||||||
|
*
|
||||||
|
* @param span the span during which this object and its canonical successors should be removed
|
||||||
|
*/
|
||||||
|
void removeTree(Range<Long> span);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parent value along this object's canonical path for a given snapshot
|
* Get the parent value along this object's canonical path for a given snapshot
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* To be the canonical parent value at a given snapshot, three things must be true: 1) The
|
* 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
|
* parent object must have this object's path with the final 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
|
* 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.
|
* contain the given snapshot. If no value satisfies these, null is returned, and the object and
|
||||||
|
* its subtree are said to be "detached" at the given snapshot.
|
||||||
*
|
*
|
||||||
* @param snap the snapshot key
|
* @param snap the snapshot key
|
||||||
* @return the canonical parent value, or null
|
* @return the canonical parent value, or null
|
||||||
*/
|
*/
|
||||||
TraceObjectValue getCanonicalParent(long snap);
|
TraceObjectValue getCanonicalParent(long snap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parent values along this object's canonical path for a given lifespan
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* To be a canonical parent in a given lifespan, three things must be true: 1) The parent object
|
||||||
|
* must have this object's path with the final 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 intersect the
|
||||||
|
* given lifespan. If the result is empty, the object and its subtree are said to be "detatched"
|
||||||
|
* during the given lifespan.
|
||||||
|
*
|
||||||
|
* @param lifespan the lifespan to consider
|
||||||
|
* @return the stream of canonical parents
|
||||||
|
*/
|
||||||
|
Stream<? extends TraceObjectValue> getCanonicalParents(Range<Long> lifespan);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this object is the root
|
* Check if this object is the root
|
||||||
*
|
*
|
||||||
|
@ -99,7 +148,7 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* @param span the span which every value entry on each path must intersect
|
* @param span the span which every value entry on each path must intersect
|
||||||
* @return the paths
|
* @return the paths
|
||||||
*/
|
*/
|
||||||
Stream<TraceObjectValPath> getAllPaths(Range<Long> span);
|
Stream<? extends TraceObjectValPath> getAllPaths(Range<Long> span);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies a strategy for resolving duplicate keys
|
* Specifies a strategy for resolving duplicate keys
|
||||||
|
@ -116,61 +165,12 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
*/
|
*/
|
||||||
TRUNCATE,
|
TRUNCATE,
|
||||||
/**
|
/**
|
||||||
* Throw an exception if the specified lifespan would result in conflicting entries
|
* Throw {@link DuplicateKeyException} if the specified lifespan would result in conflicting
|
||||||
|
* entries
|
||||||
*/
|
*/
|
||||||
DENY;
|
DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the lifespan of this object
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* NOTE: Objects with intersecting lifespans are not checked for duplicate canonical paths.
|
|
||||||
* However, their parent value entries are checked for conflicts. Thus, at any snap, it is
|
|
||||||
* impossible for any two objects with equal canonical paths to both exist at their canonical
|
|
||||||
* locations.
|
|
||||||
*
|
|
||||||
* @param lifespan the new lifespan
|
|
||||||
*/
|
|
||||||
void setLifespan(Range<Long> lifespan);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the lifespan of this object
|
|
||||||
*
|
|
||||||
* @return the lifespan
|
|
||||||
*/
|
|
||||||
Range<Long> getLifespan();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the minimum snap of this object
|
|
||||||
*
|
|
||||||
* @see #setLifespan(Range)
|
|
||||||
* @param minSnap the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time"
|
|
||||||
*/
|
|
||||||
void setMinSnap(long minSnap);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the minimum snap of this object
|
|
||||||
*
|
|
||||||
* @return the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time"
|
|
||||||
*/
|
|
||||||
long getMinSnap();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the maximum snap of this object
|
|
||||||
*
|
|
||||||
* @see #setLifespan(Range)
|
|
||||||
* @param maxSnap the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time"
|
|
||||||
*/
|
|
||||||
void setMaxSnap(long maxSnap);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the maximum snap of this object
|
|
||||||
*
|
|
||||||
* @return the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time"
|
|
||||||
*/
|
|
||||||
long getMaxSnap();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the interface classes provided by this object, according to the schema
|
* Get all the interface classes provided by this object, according to the schema
|
||||||
*
|
*
|
||||||
|
@ -314,8 +314,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* @param lifespan the lifespan of the value
|
* @param lifespan the lifespan of the value
|
||||||
* @param key the key to set
|
* @param key the key to set
|
||||||
* @param value the new value
|
* @param value the new value
|
||||||
* @param resolution determines how to resolve conflicting keys with intersecting lifespans
|
* @param resolution determines how to resolve duplicate keys with intersecting lifespans
|
||||||
* @return the created value entry
|
* @return the created value entry
|
||||||
|
* @throws DuplicateKeyException if there are denied duplicate keys
|
||||||
*/
|
*/
|
||||||
TraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
|
TraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
|
||||||
ConflictResolution resolution);
|
ConflictResolution resolution);
|
||||||
|
@ -407,11 +408,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* <p>
|
* <p>
|
||||||
* The object may not yet be inserted at its canonical path
|
* 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
|
* @param targetIf the interface class
|
||||||
* @return the stream of objects
|
* @return the stream of objects
|
||||||
*/
|
*/
|
||||||
Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
|
Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(
|
||||||
Class<? extends TargetObject> targetIf);
|
Class<? extends TargetObject> targetIf);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -421,13 +421,18 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* The object may not yet be inserted at its canonical path
|
* The object may not yet be inserted at its canonical path
|
||||||
*
|
*
|
||||||
* @param <I> the interface type
|
* @param <I> the interface type
|
||||||
* @param span the span which the found objects must intersect
|
|
||||||
* @param ifClass the interface class
|
* @param ifClass the interface class
|
||||||
* @return the stream of interfaces
|
* @return the stream of interfaces
|
||||||
*/
|
*/
|
||||||
<I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
<I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(Class<I> ifClass);
|
||||||
Range<Long> span, Class<I> ifClass);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for successors providing the given target interface
|
||||||
|
*
|
||||||
|
* @param span the span which the found paths must intersect
|
||||||
|
* @param targetIf the target interface class
|
||||||
|
* @return the stream of found paths to values
|
||||||
|
*/
|
||||||
Stream<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
|
Stream<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
|
||||||
Class<? extends TargetObject> targetIf);
|
Class<? extends TargetObject> targetIf);
|
||||||
|
|
||||||
|
@ -446,22 +451,14 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Delete this object along with parent and child value entries referring to it
|
* Delete this object along with parent and child value entries referring to it
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Note, this does not delete the children or any successors. Use {@link #deleteTree()} to
|
* <b>Warning:</b> This will remove the object from the manager <em>entirely</em>, not just over
|
||||||
* delete an entire subtree, regardless of lifespan. It is not recommended to invoke this on the
|
* a given span. In general, this is used for cleaning and maintenance. Consider
|
||||||
* root object, since it cannot be replaced without first clearing the manager.
|
* {@link #remove(Range)} or {@link TraceObjectValue#delete()} instead. Note, this does not
|
||||||
|
* delete the child objects or any successors. It is not recommended to invoke this on the root
|
||||||
|
* object, since it cannot be replaced without first clearing the manager.
|
||||||
*/
|
*/
|
||||||
void delete();
|
void delete();
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete this object and its successors along with value entries referring to any
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* It is not recommended to invoke this on the root object. Instead, use
|
|
||||||
* {@link TraceObjectManager#clear()}. The root object cannot be replaced without first clearing
|
|
||||||
* the manager.
|
|
||||||
*/
|
|
||||||
void deleteTree();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this object has been deleted
|
* Check if this object has been deleted
|
||||||
*
|
*
|
||||||
|
@ -469,21 +466,4 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
boolean isDeleted();
|
boolean isDeleted();
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify the lifespan or delete this object, such that it no longer intersects the given span.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* If the given span and the current lifespan are already disjoint, this does nothing. If the
|
|
||||||
* given span splits the current lifespan in two, an exception is thrown. This is because the
|
|
||||||
* two resulting objects ought to be identical, but they cannot be. Instead the one object
|
|
||||||
* should remain alive, and the edge(s) pointing to it should be truncated. In other words, a
|
|
||||||
* single object cannot vanish and then later re-appear, but it can be unlinked and then later
|
|
||||||
* become relinked.
|
|
||||||
*
|
|
||||||
* @param span the span to clear
|
|
||||||
* @return this if the one object remains, null if the object is deleted.
|
|
||||||
* @throws IllegalArgumentException if the given span splits the current lifespan in two
|
|
||||||
*/
|
|
||||||
TraceObject truncateOrDelete(Range<Long> span);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,46 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.target;
|
package ghidra.trace.model.target;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
import com.google.common.collect.RangeSet;
|
||||||
|
|
||||||
|
import ghidra.lifecycle.Transitional;
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common interface for object-based implementations of other trace manager entries, e.g.,
|
||||||
|
* {@link TraceObjectThread}.
|
||||||
|
*/
|
||||||
public interface TraceObjectInterface {
|
public interface TraceObjectInterface {
|
||||||
|
/**
|
||||||
|
* Get the object backing this implementation
|
||||||
|
*
|
||||||
|
* @return the object
|
||||||
|
*/
|
||||||
TraceObject getObject();
|
TraceObject getObject();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the lifespan of this object
|
||||||
|
*
|
||||||
|
* @return the span of all lifespans
|
||||||
|
*/
|
||||||
|
@Transitional
|
||||||
|
default Range<Long> computeSpan() {
|
||||||
|
RangeSet<Long> life = getObject().getLife();
|
||||||
|
if (life.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return life.span();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transitional
|
||||||
|
default long computeMinSnap() {
|
||||||
|
return DBTraceUtils.lowerEndpoint(computeSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transitional
|
||||||
|
default long computeMaxSnap() {
|
||||||
|
return DBTraceUtils.upperEndpoint(computeSpan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,48 @@
|
||||||
package ghidra.trace.model.target;
|
package ghidra.trace.model.target;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.PathPredicates;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.dbg.util.PathUtils.PathComparator;
|
import ghidra.dbg.util.PathUtils.PathComparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An immutable path of keys leading from one object object to another
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Often, the source is the root. These are often taken as a parameter when searching for values. In
|
||||||
|
* essence, they simply wrap a list of string keys, but it provides convenience methods, sensible
|
||||||
|
* comparison, and better typing.
|
||||||
|
*/
|
||||||
public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath> {
|
public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a path from the given list of keys
|
||||||
|
*
|
||||||
|
* @param keyList the list of keys from source to destination
|
||||||
|
* @return the path
|
||||||
|
*/
|
||||||
public static TraceObjectKeyPath of(List<String> keyList) {
|
public static TraceObjectKeyPath of(List<String> keyList) {
|
||||||
return new TraceObjectKeyPath(List.copyOf(keyList));
|
return new TraceObjectKeyPath(List.copyOf(keyList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a path from the given keys
|
||||||
|
*
|
||||||
|
* @param keys the keys from source to destination
|
||||||
|
* @return the path
|
||||||
|
*/
|
||||||
public static TraceObjectKeyPath of(String... keys) {
|
public static TraceObjectKeyPath of(String... keys) {
|
||||||
return new TraceObjectKeyPath(List.of(keys));
|
return new TraceObjectKeyPath(List.of(keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a path from the given string
|
||||||
|
*
|
||||||
|
* @param path the dot-separated keys from source to destinattion
|
||||||
|
* @return the path
|
||||||
|
*/
|
||||||
public static TraceObjectKeyPath parse(String path) {
|
public static TraceObjectKeyPath parse(String path) {
|
||||||
return new TraceObjectKeyPath(PathUtils.parse(path));
|
return new TraceObjectKeyPath(PathUtils.parse(path));
|
||||||
}
|
}
|
||||||
|
@ -67,30 +95,82 @@ public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath>
|
||||||
return this.keyList.equals(that.keyList);
|
return this.keyList.equals(that.keyList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the (immutable) list of keys from source to destination
|
||||||
|
*
|
||||||
|
* @return the key list
|
||||||
|
*/
|
||||||
public List<String> getKeyList() {
|
public List<String> getKeyList() {
|
||||||
return keyList;
|
return keyList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assuming the source is the root, check if this path refers to that root
|
||||||
|
*
|
||||||
|
* @return true if the path is empty, false otherwise
|
||||||
|
*/
|
||||||
public boolean isRoot() {
|
public boolean isRoot() {
|
||||||
return keyList.isEmpty();
|
return keyList.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path by appending the given key
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* For example, if this path is "{@code Processes[2]}" and {@code name} takes the value
|
||||||
|
* "{@code Threads}", the result will be "{@code Processes[2].Threads}".
|
||||||
|
*
|
||||||
|
* @param name the new final key
|
||||||
|
* @return the resulting path
|
||||||
|
*/
|
||||||
public TraceObjectKeyPath key(String name) {
|
public TraceObjectKeyPath key(String name) {
|
||||||
return new TraceObjectKeyPath(PathUtils.extend(keyList, name));
|
return new TraceObjectKeyPath(PathUtils.extend(keyList, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the final key of this path
|
||||||
|
*
|
||||||
|
* @return the final key
|
||||||
|
*/
|
||||||
public String key() {
|
public String key() {
|
||||||
return PathUtils.getKey(keyList);
|
return PathUtils.getKey(keyList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path by appending the given element index
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* For example, if this path is "{@code Processes}" and {@code index} takes the value 2, the
|
||||||
|
* result will be "{@code Processes[2]}".
|
||||||
|
*
|
||||||
|
* @param index the new final index
|
||||||
|
* @return the resulting path
|
||||||
|
*/
|
||||||
public TraceObjectKeyPath index(long index) {
|
public TraceObjectKeyPath index(long index) {
|
||||||
return index(PathUtils.makeIndex(index));
|
return index(PathUtils.makeIndex(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path by appending the given element index
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This does the same as {@link #key(String)} but uses brackets instead. For example, if this
|
||||||
|
* path is "{@code Processes[2].Threads[0].Registers}" and {@code index} takes the value
|
||||||
|
* "{@code RAX}", the result will be "{@code Processes[2].Threads[0].Registers[RAX]"}.
|
||||||
|
*
|
||||||
|
* @param index the new final index
|
||||||
|
* @return the resulting path
|
||||||
|
*/
|
||||||
public TraceObjectKeyPath index(String index) {
|
public TraceObjectKeyPath index(String index) {
|
||||||
return new TraceObjectKeyPath(PathUtils.index(keyList, index));
|
return new TraceObjectKeyPath(PathUtils.index(keyList, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the final index of this path
|
||||||
|
*
|
||||||
|
* @return the final index
|
||||||
|
* @throws IllegalArgumentException if the final key is not an index, i.e., in brackets
|
||||||
|
*/
|
||||||
public String index() {
|
public String index() {
|
||||||
return PathUtils.getIndex(keyList);
|
return PathUtils.getIndex(keyList);
|
||||||
}
|
}
|
||||||
|
@ -100,16 +180,54 @@ public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath>
|
||||||
return PathUtils.toString(keyList);
|
return PathUtils.toString(keyList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path by removing the final key
|
||||||
|
*
|
||||||
|
* @return the resulting path, or null if this path is empty
|
||||||
|
*/
|
||||||
public TraceObjectKeyPath parent() {
|
public TraceObjectKeyPath parent() {
|
||||||
List<String> pkl = PathUtils.parent(keyList);
|
List<String> pkl = PathUtils.parent(keyList);
|
||||||
return pkl == null ? null : new TraceObjectKeyPath(pkl);
|
return pkl == null ? null : new TraceObjectKeyPath(pkl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path by appending the given list of keys
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* For example, if this path is "{@code Processes[2]}" and {@code subKeyList} takes the value
|
||||||
|
* {@code List.of("Threads", "[0]")}, the result will be "{@code Processes[2].Threads[0]}".
|
||||||
|
*
|
||||||
|
* @param subKeyList the list of keys to append
|
||||||
|
* @return the resulting path
|
||||||
|
*/
|
||||||
public TraceObjectKeyPath extend(List<String> subKeyList) {
|
public TraceObjectKeyPath extend(List<String> subKeyList) {
|
||||||
return new TraceObjectKeyPath(PathUtils.extend(keyList, subKeyList));
|
return new TraceObjectKeyPath(PathUtils.extend(keyList, subKeyList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new path by appending the given keys
|
||||||
|
*
|
||||||
|
* @see #extend(List)
|
||||||
|
* @param subKeyList the keys to append
|
||||||
|
* @return the resulting path
|
||||||
|
*/
|
||||||
public TraceObjectKeyPath extend(String... subKeyList) {
|
public TraceObjectKeyPath extend(String... subKeyList) {
|
||||||
return extend(Arrays.asList(subKeyList));
|
return extend(Arrays.asList(subKeyList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream, starting with the longer paths, paths that match the given predicates
|
||||||
|
*
|
||||||
|
* @param matcher
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Stream<TraceObjectKeyPath> streamMatchingAncestry(PathPredicates predicates) {
|
||||||
|
if (!predicates.ancestorMatches(keyList, false)) {
|
||||||
|
return Stream.of();
|
||||||
|
}
|
||||||
|
if (predicates.matches(keyList)) {
|
||||||
|
return Stream.concat(Stream.of(this), parent().streamMatchingAncestry(predicates));
|
||||||
|
}
|
||||||
|
return parent().streamMatchingAncestry(predicates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,13 +53,12 @@ public interface TraceObjectManager {
|
||||||
TraceObjectValue createRootObject(TargetObjectSchema schema);
|
TraceObjectValue createRootObject(TargetObjectSchema schema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an object with the given canonical path having the given lifespan
|
* Create (or get) an object with the given canonical path
|
||||||
*
|
*
|
||||||
* @param path the object's canonical path
|
* @param path the object's canonical path
|
||||||
* @param lifespan the initial lifespan
|
|
||||||
* @return the new object
|
* @return the new object
|
||||||
*/
|
*/
|
||||||
TraceObject createObject(TraceObjectKeyPath path, Range<Long> lifespan);
|
TraceObject createObject(TraceObjectKeyPath path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the schema of the root object
|
* Get the schema of the root object
|
||||||
|
@ -93,7 +92,7 @@ public interface TraceObjectManager {
|
||||||
* @param path the canonical path of the desired objects
|
* @param path the canonical path of the desired objects
|
||||||
* @return the collection of objects
|
* @return the collection of objects
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObject> getObjectsByCanonicalPath(TraceObjectKeyPath path);
|
TraceObject getObjectByCanonicalPath(TraceObjectKeyPath path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get objects in the database having the given path intersecting the given span
|
* Get objects in the database having the given path intersecting the given span
|
||||||
|
@ -129,6 +128,13 @@ public interface TraceObjectManager {
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObject> getAllObjects();
|
Collection<? extends TraceObject> getAllObjects();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the values (edges) in the database
|
||||||
|
*
|
||||||
|
* @return the collect of all values
|
||||||
|
*/
|
||||||
|
Collection<? extends TraceObjectValue> getAllValues();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all address-ranged values intersecting the given span and address range
|
* Get all address-ranged values intersecting the given span and address range
|
||||||
*
|
*
|
||||||
|
@ -150,6 +156,15 @@ public interface TraceObjectManager {
|
||||||
<I extends TraceObjectInterface> Stream<I> queryAllInterface(Range<Long> span,
|
<I extends TraceObjectInterface> Stream<I> queryAllInterface(Range<Long> span,
|
||||||
Class<I> ifClass);
|
Class<I> ifClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For maintenance, remove all disconnected objects
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* An object is disconnected if it is neither the child nor parent of any value for any span. In
|
||||||
|
* other words, it's unused.
|
||||||
|
*/
|
||||||
|
void cullDisconnectedObjects();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the <em>entire object model</em>, including the schema
|
* Delete the <em>entire object model</em>, including the schema
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,20 +17,100 @@ package ghidra.trace.model.target;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A path of values leading from one object to another
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Often, the source object is the root. These are often returned in streams where the search
|
||||||
|
* involves a desired "span." The path satisfies that requirement, i.e., "the path intersects the
|
||||||
|
* span" if the cumulative intersection of all values' lifespans along the path and the given span
|
||||||
|
* is non-empty. Paths may also be empty, implying the source is the destination. Empty paths
|
||||||
|
* "intersect" any given span.
|
||||||
|
*/
|
||||||
public interface TraceObjectValPath extends Comparable<TraceObjectValPath> {
|
public interface TraceObjectValPath extends Comparable<TraceObjectValPath> {
|
||||||
|
/**
|
||||||
|
* Get the values in the path, ordered from source to destination
|
||||||
|
*
|
||||||
|
* @return the list of value entries
|
||||||
|
*/
|
||||||
List<? extends TraceObjectValue> getEntryList();
|
List<? extends TraceObjectValue> getEntryList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the keys in the path, ordered from source to destination
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The returned list is suited for testing with {@link PathPredicates} or other
|
||||||
|
* path-manipulation methods.
|
||||||
|
*
|
||||||
|
* @return the list of keys
|
||||||
|
*/
|
||||||
List<String> getKeyList();
|
List<String> getKeyList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given value appears on this path
|
||||||
|
*
|
||||||
|
* @param entry the value entry to check
|
||||||
|
* @return true if it appears on the path, false otherwise
|
||||||
|
*/
|
||||||
boolean contains(TraceObjectValue entry);
|
boolean contains(TraceObjectValue entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first entry, i.e., the one adjacent to the source object
|
||||||
|
*
|
||||||
|
* @return the entry, or null if the path is empty
|
||||||
|
*/
|
||||||
TraceObjectValue getFirstEntry();
|
TraceObjectValue getFirstEntry();
|
||||||
|
|
||||||
TraceObject getFirstParent(TraceObject ifEmpty);
|
/**
|
||||||
|
* Get the source object
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This returns the parent object of the first entry of the path, unless the path is empty. If
|
||||||
|
* the path is empty, then this returns the value passed in {@code ifEmpty}, which is presumably
|
||||||
|
* the destination object.
|
||||||
|
*
|
||||||
|
* @param ifEmpty the object to return when this path is empty
|
||||||
|
* @return the source object
|
||||||
|
*/
|
||||||
|
TraceObject getSource(TraceObject ifEmpty);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last entry, i.e., the one adjacent to the destination object
|
||||||
|
*
|
||||||
|
* @return the entry, or null if the path is empty
|
||||||
|
*/
|
||||||
TraceObjectValue getLastEntry();
|
TraceObjectValue getLastEntry();
|
||||||
|
|
||||||
Object getLastValue(Object ifEmpty);
|
/**
|
||||||
|
* Get the destination value
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This returns the value of the last entry of the path, unless the path is empty. If the path
|
||||||
|
* is empty, then this returns the object passed in {@code ifEmpty}, which is presumably the
|
||||||
|
* source object. Note that values may be a primitive, so the destination is not always an
|
||||||
|
* object, i.e., {@link TraceObject}. Use {@link #getDestination(TraceObject)} to assume the
|
||||||
|
* destination is an object.
|
||||||
|
*
|
||||||
|
* @param ifEmpty the value to return when the path is empty
|
||||||
|
* @return the destination value
|
||||||
|
*/
|
||||||
|
Object getDestinationValue(Object ifEmpty);
|
||||||
|
|
||||||
TraceObject getLastChild(TraceObject ifEmpty);
|
/**
|
||||||
|
* Get the destination object
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This returns the child object of the last entry of the path, unless the path is empty. If the
|
||||||
|
* path is empty, then this returns the object passed in {@code ifEmpty}, which is presumably
|
||||||
|
* the source object. Note that values may be primitive, so the destination is not always an
|
||||||
|
* object, i.e., {@link TraceObject}. Use {@link #getDestinationValue(Object)} when it is not
|
||||||
|
* safe to assume the destination is an object.
|
||||||
|
*
|
||||||
|
* @param ifEmpty the object to return when the path is empty
|
||||||
|
* @return the destination object
|
||||||
|
* @throws ClassCastException if the destination value is not an object
|
||||||
|
*/
|
||||||
|
TraceObject getDestination(TraceObject ifEmpty);
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ public interface TraceObjectValue {
|
||||||
*
|
*
|
||||||
* @param lifespan the new lifespan
|
* @param lifespan the new lifespan
|
||||||
* @param resolution specifies how to resolve duplicate keys with intersecting lifespans
|
* @param resolution specifies how to resolve duplicate keys with intersecting lifespans
|
||||||
|
* @throws DuplicateKeyException if there are denied duplicate keys
|
||||||
*/
|
*/
|
||||||
void setLifespan(Range<Long> span, ConflictResolution resolution);
|
void setLifespan(Range<Long> span, ConflictResolution resolution);
|
||||||
|
|
||||||
|
@ -133,17 +134,9 @@ public interface TraceObjectValue {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete this entry
|
* Delete this entry
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* If this entry is part of the child object's canonical path, then the child is also deleted.
|
|
||||||
*/
|
*/
|
||||||
void delete();
|
void delete();
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete this entry and, if it is canonical, its successors
|
|
||||||
*/
|
|
||||||
void deleteTree();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this value entry has been deleted
|
* Check if this value entry has been deleted
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,7 +21,7 @@ import java.util.List;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
|
@ -66,8 +66,8 @@ public enum TraceObjectInterfaceUtils {
|
||||||
throw new DuplicateNameException(
|
throw new DuplicateNameException(
|
||||||
"Duplicate " + getShortName(traceIf) + ": " + e.getMessage());
|
"Duplicate " + getShortName(traceIf) + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
object.setLifespan(lifespan);
|
object.insert(lifespan, ConflictResolution.TRUNCATE);
|
||||||
long lower = object.getMinSnap();
|
long lower = DBTraceUtils.lowerEndpoint(lifespan);
|
||||||
for (String key : getFixedKeys(traceIf)) {
|
for (String key : getFixedKeys(traceIf)) {
|
||||||
TraceObjectValue val = object.getValue(lower, key);
|
TraceObjectValue val = object.getValue(lower, key);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.thread;
|
package ghidra.trace.model.thread;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.dbg.target.TargetThread;
|
import ghidra.dbg.target.TargetThread;
|
||||||
import ghidra.trace.model.target.TraceObjectInterface;
|
import ghidra.trace.model.target.TraceObjectInterface;
|
||||||
|
@ -29,4 +31,6 @@ import ghidra.trace.model.target.annot.TraceObjectInfo;
|
||||||
})
|
})
|
||||||
public interface TraceObjectThread extends TraceThread, TraceObjectInterface {
|
public interface TraceObjectThread extends TraceThread, TraceObjectInterface {
|
||||||
String KEY_COMMENT = "_comment";
|
String KEY_COMMENT = "_comment";
|
||||||
|
|
||||||
|
void setName(Range<Long> lifespan, String name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,10 @@ import ghidra.trace.model.thread.TraceThread;
|
||||||
* Identify the "full" address space in a trace.
|
* Identify the "full" address space in a trace.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Whenever the address space is {@code register}, then the thread and frame level become
|
* Whenever the address space is {@code register}, then the thread and frame level become necessary
|
||||||
* necessarily to uniquely identify it. This will be deprecated when either, 1) unique register
|
* to uniquely identify it. This will be deprecated when either, 1) unique register overlay spaces
|
||||||
* overlay spaces are created for each thread/frame, or 2) register values are fully transitioned to
|
* are created for each thread/frame, or 2) register values are fully transitioned to object model
|
||||||
* object model storage.
|
* storage.
|
||||||
*/
|
*/
|
||||||
public interface TraceAddressSpace {
|
public interface TraceAddressSpace {
|
||||||
AddressSpace getAddressSpace();
|
AddressSpace getAddressSpace();
|
||||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.trace.database.breakpoint;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
@ -91,7 +92,8 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat
|
||||||
@Test
|
@Test
|
||||||
public void testGetAllBreakpoints() throws Exception {
|
public void testGetAllBreakpoints() throws Exception {
|
||||||
addBreakpoints();
|
addBreakpoints();
|
||||||
assertEquals(Set.of(breakMain, breakVarA, breakVarB),
|
// breakVarA == breakVarB in object mode
|
||||||
|
assertEquals(Set.copyOf(List.of(breakMain, breakVarA, breakVarB)),
|
||||||
Set.copyOf(breakpointManager.getAllBreakpoints()));
|
Set.copyOf(breakpointManager.getAllBreakpoints()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +102,7 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat
|
||||||
addBreakpoints();
|
addBreakpoints();
|
||||||
assertEquals(Set.of(breakMain),
|
assertEquals(Set.of(breakMain),
|
||||||
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]")));
|
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]")));
|
||||||
assertEquals(Set.of(breakVarA, breakVarB),
|
assertEquals(Set.copyOf(List.of(breakVarA, breakVarB)), // Same breakpoint in object mode
|
||||||
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[1]")));
|
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[1]")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,14 +261,11 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat
|
||||||
@Test
|
@Test
|
||||||
public void testDelete() throws Exception {
|
public void testDelete() throws Exception {
|
||||||
addBreakpoints();
|
addBreakpoints();
|
||||||
assertEquals(Set.of(breakMain),
|
assertEquals(breakMain, breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"));
|
||||||
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]")));
|
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
breakMain.delete();
|
breakMain.delete();
|
||||||
assertEquals(Set.of(),
|
assertNull(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"));
|
||||||
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]")));
|
|
||||||
}
|
}
|
||||||
assertEquals(Set.of(),
|
assertNull(breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[0]"));
|
||||||
Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]")));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,8 +108,8 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
b.range(0x7f400000, 0x7f60002f), Range.closed(11L, 20L));
|
b.range(0x7f400000, 0x7f60002f), Range.closed(11L, 20L));
|
||||||
}
|
}
|
||||||
assertEquals(Set.of(mod1), new HashSet<>(moduleManager.getModulesByPath("Modules[first]")));
|
assertEquals(Set.of(mod1), new HashSet<>(moduleManager.getModulesByPath("Modules[first]")));
|
||||||
assertEquals(Set.of(mod2, mod3),
|
assertEquals(Set.copyOf(List.of(mod2, mod3)), // Same in object mode
|
||||||
new HashSet<>(moduleManager.getModulesByPath("Modules[second]")));
|
Set.copyOf(moduleManager.getModulesByPath("Modules[second]")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
stack.setDepth(5, true);
|
stack.setDepth(5, true);
|
||||||
}
|
}
|
||||||
int expectedLevel = 0;
|
int expectedLevel = 0;
|
||||||
for (TraceStackFrame frame : stack.getFrames()) {
|
for (TraceStackFrame frame : stack.getFrames(0)) {
|
||||||
assertEquals(expectedLevel++, frame.getLevel());
|
assertEquals(expectedLevel++, frame.getLevel());
|
||||||
}
|
}
|
||||||
assertEquals(5, expectedLevel);
|
assertEquals(5, expectedLevel);
|
||||||
|
@ -78,7 +78,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedLevel = 0;
|
expectedLevel = 0;
|
||||||
for (TraceStackFrame frame : stack.getFrames()) {
|
for (TraceStackFrame frame : stack.getFrames(0)) {
|
||||||
assertEquals(expectedLevel++, frame.getLevel());
|
assertEquals(expectedLevel++, frame.getLevel());
|
||||||
}
|
}
|
||||||
assertEquals(3, expectedLevel);
|
assertEquals(3, expectedLevel);
|
||||||
|
@ -88,7 +88,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedLevel = 0;
|
expectedLevel = 0;
|
||||||
for (TraceStackFrame frame : stack.getFrames()) {
|
for (TraceStackFrame frame : stack.getFrames(0)) {
|
||||||
assertEquals(expectedLevel++, frame.getLevel());
|
assertEquals(expectedLevel++, frame.getLevel());
|
||||||
}
|
}
|
||||||
assertEquals(1, expectedLevel);
|
assertEquals(1, expectedLevel);
|
||||||
|
@ -139,16 +139,21 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
|
|
||||||
TraceStack stack1 = stackManager.getStack(thread, 0, true);
|
TraceStack stack1 = stackManager.getStack(thread, 0, true);
|
||||||
stack1.setDepth(2, true);
|
stack1.setDepth(2, true);
|
||||||
(frame1a = stack1.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040100));
|
frame1a = stack1.getFrame(0, false);
|
||||||
(frame1b = stack1.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040300));
|
frame1b = stack1.getFrame(1, false);
|
||||||
|
frame1a.setProgramCounter(Range.atLeast(0L), b.addr(0x0040100));
|
||||||
|
frame1b.setProgramCounter(Range.atLeast(0L), b.addr(0x0040300));
|
||||||
|
|
||||||
TraceStack stack2 = stackManager.getStack(thread, 1, true);
|
TraceStack stack2 = stackManager.getStack(thread, 1, true);
|
||||||
stack2.setDepth(2, true);
|
stack2.setDepth(2, true);
|
||||||
(frame2a = stack2.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040200));
|
frame2a = stack2.getFrame(0, false);
|
||||||
(frame2b = stack2.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040400));
|
frame2b = stack2.getFrame(1, false);
|
||||||
|
frame2a.setProgramCounter(Range.atLeast(1L), b.addr(0x0040200));
|
||||||
|
frame2b.setProgramCounter(Range.atLeast(1L), b.addr(0x0040400));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(Set.of(frame1a, frame2a, frame1b, frame2b), toSet(stackManager
|
// stack1 == stack2, and corresponding frames, in object mode
|
||||||
|
assertEquals(Set.copyOf(List.of(frame1a, frame2a, frame1b, frame2b)), toSet(stackManager
|
||||||
.getFramesIn(b.set(b.drng(0x0040000, 0x0050000)))));
|
.getFramesIn(b.set(b.drng(0x0040000, 0x0050000)))));
|
||||||
|
|
||||||
assertEquals(Set.of(frame1a, frame1b), toSet(stackManager
|
assertEquals(Set.of(frame1a, frame1b), toSet(stackManager
|
||||||
|
@ -202,7 +207,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
stack.setDepth(2, true);
|
stack.setDepth(2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TraceStackFrame> frames = stack.getFrames();
|
List<TraceStackFrame> frames = stack.getFrames(0);
|
||||||
assertEquals(2, frames.size());
|
assertEquals(2, frames.size());
|
||||||
assertEquals(stack.getFrame(0, false), frames.get(0));
|
assertEquals(stack.getFrame(0, false), frames.get(0));
|
||||||
assertEquals(stack.getFrame(1, false), frames.get(1));
|
assertEquals(stack.getFrame(1, false), frames.get(1));
|
||||||
|
@ -220,7 +225,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
|
|
||||||
assertFalse(stack.isDeleted());
|
assertFalse(stack.isDeleted());
|
||||||
assertEquals(stack, stackManager.getStack(thread, 0, false));
|
assertEquals(stack, stackManager.getStack(thread, 0, false));
|
||||||
assertEquals(2, stack.getFrames().size());
|
assertEquals(2, stack.getFrames(0).size());
|
||||||
|
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
stack.delete();
|
stack.delete();
|
||||||
|
@ -293,10 +298,10 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
|
||||||
// NB. Object-mode sets comment at pc in listing, not on frame itself
|
// NB. Object-mode sets comment at pc in listing, not on frame itself
|
||||||
frame.setProgramCounter(Range.all(), b.addr(0x00400123));
|
frame.setProgramCounter(Range.all(), b.addr(0x00400123));
|
||||||
|
|
||||||
assertNull(frame.getComment());
|
assertNull(frame.getComment(0));
|
||||||
frame.setComment("Hello, World!");
|
frame.setComment(0, "Hello, World!");
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("Hello, World!", frame.getComment());
|
assertEquals("Hello, World!", frame.getComment(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,19 +83,19 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets");
|
TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets");
|
||||||
targetContainer = manager.createObject(pathTargets, Range.atLeast(0L));
|
targetContainer = manager.createObject(pathTargets);
|
||||||
root.setAttribute(Range.atLeast(0L), "Targets", targetContainer);
|
root.setAttribute(Range.atLeast(0L), "Targets", targetContainer);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueStore);
|
||||||
|
|
||||||
for (int i = 0; i < targetCount; i++) {
|
for (int i = 0; i < targetCount; i++) {
|
||||||
TraceObject target =
|
Range<Long> lifespan = Range.atLeast((long) i);
|
||||||
manager.createObject(pathTargets.index(i), Range.atLeast((long) i));
|
TraceObject target = manager.createObject(pathTargets.index(i));
|
||||||
target.setAttribute(target.getLifespan(), "self", target);
|
target.setAttribute(Range.all(), "self", target);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueStore);
|
||||||
targetContainer.setElement(target.getLifespan(), i, target);
|
targetContainer.setElement(lifespan, i, target);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueStore);
|
||||||
targets.add(target);
|
targets.add(target);
|
||||||
root.setAttribute(target.getLifespan(), "curTarget", target);
|
root.setAttribute(lifespan, "curTarget", target);
|
||||||
dumpStore(manager.valueStore);
|
dumpStore(manager.valueStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,25 +109,37 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
assertEquals(b.trace, manager.getTrace());
|
assertEquals(b.trace, manager.getTrace());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails because you cannot use the object manager until the schema is specified and the root
|
||||||
|
* object is created.
|
||||||
|
*/
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void testCreateObjectWithRootErr() {
|
public void testCreateObjectWithoutRootErr() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
manager.createObject(TraceObjectKeyPath.of("Test"), Range.all());
|
manager.createObject(TraceObjectKeyPath.of("Test"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails because you cannot create the root object using createObject. Instead, you must use
|
||||||
|
* createRootObject, specifying the schema.
|
||||||
|
*/
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testCreateObjectAsRootErr() {
|
public void testCreateObjectAsRootErrNoSchema() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
manager.createObject(TraceObjectKeyPath.of(), Range.all());
|
manager.createObject(TraceObjectKeyPath.of());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails because you cannot create a second root object, nor can you create any root object with
|
||||||
|
* createObject.
|
||||||
|
*/
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testCreateObjectAsRootErrRootExists() {
|
public void testCreateObjectAsRootErrRootExists() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
manager.createObject(TraceObjectKeyPath.of(), Range.all());
|
manager.createObject(TraceObjectKeyPath.of());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +150,9 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails because you cannot create a second root object.
|
||||||
|
*/
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void testCreate2ndRootErr() {
|
public void testCreate2ndRootErr() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
|
@ -163,24 +178,22 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObject obj;
|
TraceObject obj;
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
obj = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all());
|
obj = manager.createObject(TraceObjectKeyPath.of("Targets"));
|
||||||
}
|
}
|
||||||
assertEquals(TraceObjectKeyPath.of("Targets"), obj.getCanonicalPath());
|
assertEquals(TraceObjectKeyPath.of("Targets"), obj.getCanonicalPath());
|
||||||
assertEquals(Range.all(), obj.getLifespan());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetObjectsByCanonicalPath() {
|
public void testGetObjectsByCanonicalPath() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all());
|
targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of("Nothing")).isEmpty());
|
assertNull(manager.getObjectByCanonicalPath(TraceObjectKeyPath.of("Nothing")));
|
||||||
assertEquals(root,
|
assertEquals(root, manager.getObjectByCanonicalPath(TraceObjectKeyPath.of()));
|
||||||
Unique.assertOne(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of())));
|
|
||||||
assertEquals(targetContainer,
|
assertEquals(targetContainer,
|
||||||
Unique.assertOne(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of("Targets"))));
|
manager.getObjectByCanonicalPath(TraceObjectKeyPath.of("Targets")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -223,8 +236,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
public void testGetRangeValues() {
|
public void testGetRangeValues() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
targetContainer =
|
targetContainer = manager.createObject(TraceObjectKeyPath.parse("Targets"));
|
||||||
manager.createObject(TraceObjectKeyPath.parse("Targets"), Range.all());
|
|
||||||
root.setAttribute(Range.all(), "Targets", targetContainer);
|
root.setAttribute(Range.all(), "Targets", targetContainer);
|
||||||
|
|
||||||
TraceObjectValue rangeVal =
|
TraceObjectValue rangeVal =
|
||||||
|
@ -266,9 +278,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
populateModel(3);
|
populateModel(3);
|
||||||
TraceObject thread;
|
TraceObject thread;
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"),
|
thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"));
|
||||||
Range.atLeast(0L));
|
thread.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
||||||
thread.insert(ConflictResolution.DENY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(Set.of(),
|
assertEquals(Set.of(),
|
||||||
|
@ -332,7 +343,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
public void testIsRoot() {
|
public void testIsRoot() {
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all());
|
targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(root.isRoot());
|
assertTrue(root.isRoot());
|
||||||
|
@ -354,59 +365,25 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObjectValPath path;
|
TraceObjectValPath path;
|
||||||
|
|
||||||
path = paths.get(0);
|
path = paths.get(0);
|
||||||
assertEquals(object, path.getLastChild(root));
|
assertEquals(object, path.getDestination(root));
|
||||||
assertEquals(PathUtils.parse("Targets[0]"), path.getKeyList());
|
assertEquals(PathUtils.parse("Targets[0]"), path.getKeyList());
|
||||||
|
|
||||||
path = paths.get(1);
|
path = paths.get(1);
|
||||||
assertEquals(object, path.getLastChild(root));
|
assertEquals(object, path.getDestination(root));
|
||||||
assertEquals(PathUtils.parse("Targets[0].self"), path.getKeyList());
|
assertEquals(PathUtils.parse("Targets[0].self"), path.getKeyList());
|
||||||
|
|
||||||
path = paths.get(2);
|
path = paths.get(2);
|
||||||
assertEquals(object, path.getLastChild(root));
|
assertEquals(object, path.getDestination(root));
|
||||||
assertEquals(List.of("curTarget"), path.getKeyList());
|
assertEquals(List.of("curTarget"), path.getKeyList());
|
||||||
|
|
||||||
path = paths.get(3);
|
path = paths.get(3);
|
||||||
assertEquals(object, path.getLastChild(root));
|
assertEquals(object, path.getDestination(root));
|
||||||
assertEquals(PathUtils.parse("curTarget.self"), path.getKeyList());
|
assertEquals(PathUtils.parse("curTarget.self"), path.getKeyList());
|
||||||
|
|
||||||
paths = root.getAllPaths(Range.all()).collect(Collectors.toList());
|
paths = root.getAllPaths(Range.all()).collect(Collectors.toList());
|
||||||
assertEquals(1, paths.size());
|
assertEquals(1, paths.size());
|
||||||
path = paths.get(0);
|
path = paths.get(0);
|
||||||
assertEquals(root, path.getLastChild(root));
|
assertEquals(root, path.getDestination(root));
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testObjectSetLifespan() {
|
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
|
||||||
assertEquals(Range.all(), root.getLifespan());
|
|
||||||
|
|
||||||
root.setLifespan(Range.singleton(0L));
|
|
||||||
assertEquals(Range.singleton(0L), root.getLifespan());
|
|
||||||
|
|
||||||
try {
|
|
||||||
root.setMinSnap(10);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
root.setMaxSnap(-10);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
|
|
||||||
root.setMinSnap(-10);
|
|
||||||
assertEquals(Range.closed(-10L, 0L), root.getLifespan());
|
|
||||||
assertEquals(-10, root.getMinSnap());
|
|
||||||
|
|
||||||
root.setMaxSnap(10);
|
|
||||||
assertEquals(Range.closed(-10L, 10L), root.getLifespan());
|
|
||||||
assertEquals(10, root.getMaxSnap());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -415,9 +392,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
|
|
||||||
thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"),
|
thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"));
|
||||||
Range.atLeast(0L));
|
thread.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
||||||
thread.insert(ConflictResolution.DENY);
|
|
||||||
}
|
}
|
||||||
assertEquals(Set.of(), root.getInterfaces());
|
assertEquals(Set.of(), root.getInterfaces());
|
||||||
assertEquals(Set.of(TraceObjectThread.class), thread.getInterfaces());
|
assertEquals(Set.of(TraceObjectThread.class), thread.getInterfaces());
|
||||||
|
@ -429,9 +405,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
|
|
||||||
thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"),
|
thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"));
|
||||||
Range.atLeast(0L));
|
thread.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
||||||
thread.insert(ConflictResolution.DENY);
|
|
||||||
}
|
}
|
||||||
assertNull(root.queryInterface(TraceObjectThread.class));
|
assertNull(root.queryInterface(TraceObjectThread.class));
|
||||||
TraceObjectThread threadIf = thread.queryInterface(TraceObjectThread.class);
|
TraceObjectThread threadIf = thread.queryInterface(TraceObjectThread.class);
|
||||||
|
@ -539,20 +514,20 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
assertEquals(List.of(root),
|
assertEquals(List.of(root),
|
||||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), true)
|
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), true)
|
||||||
.map(p -> p.getLastChild(root))
|
.map(p -> p.getDestination(root))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
assertEquals(List.of(root),
|
assertEquals(List.of(root),
|
||||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), false)
|
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), false)
|
||||||
.map(p -> p.getLastChild(root))
|
.map(p -> p.getDestination(root))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
assertEquals(List.of(targets.get(0), targets.get(1), targets.get(2)),
|
assertEquals(List.of(targets.get(0), targets.get(1), targets.get(2)),
|
||||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), true)
|
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), true)
|
||||||
.map(p -> p.getLastChild(root))
|
.map(p -> p.getDestination(root))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
assertEquals(List.of(targets.get(2), targets.get(1), targets.get(0)),
|
assertEquals(List.of(targets.get(2), targets.get(1), targets.get(0)),
|
||||||
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), false)
|
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), false)
|
||||||
.map(p -> p.getLastChild(root))
|
.map(p -> p.getDestination(root))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,10 +831,10 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||||
|
|
||||||
root.setAttribute(root.getLifespan(), "myAttribute", 1234);
|
root.setAttribute(Range.all(), "myAttribute", 1234);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
root.setAttribute(root.getLifespan(), "[0]", 1234);
|
root.setAttribute(Range.all(), "[0]", 1234);
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
|
@ -921,50 +896,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteTree() {
|
public void testValueSetLifespanTruncatesOrDeletes() {
|
||||||
populateModel(3);
|
|
||||||
|
|
||||||
// This deletes everything except the root, since curTarget refers to successors
|
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
|
||||||
targetContainer.deleteTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(targetContainer.isDeleted());
|
|
||||||
for (TraceObject t : targets) {
|
|
||||||
assertTrue(t.isDeleted());
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(0, root.getSuccessors(Range.all(), PathPredicates.parse("curTarget")).count());
|
|
||||||
assertEquals(root, Unique.assertOne(manager.getAllObjects()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRootObjectTruncateOrDelete() {
|
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
|
||||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
|
||||||
|
|
||||||
try {
|
|
||||||
root.truncateOrDelete(Range.singleton(0L));
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
assertEquals(Range.all(), root.getLifespan());
|
|
||||||
|
|
||||||
assertEquals(root, root.truncateOrDelete(Range.atLeast(10L)));
|
|
||||||
assertEquals(Range.atMost(9L), root.getLifespan());
|
|
||||||
|
|
||||||
assertEquals(root, root.truncateOrDelete(Range.atMost(-1L)));
|
|
||||||
assertEquals(Range.closed(0L, 9L), root.getLifespan());
|
|
||||||
|
|
||||||
assertNull(root.truncateOrDelete(Range.all()));
|
|
||||||
assertTrue(root.isDeleted());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValueSetLifespan_TruncatesOrDeletes() {
|
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
TraceObjectValue rootVal =
|
TraceObjectValue rootVal =
|
||||||
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
|
@ -1040,8 +972,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
TraceObjectValue primVal = root.setValue(Range.all(), "primitive", "A string");
|
TraceObjectValue primVal = root.setValue(Range.all(), "primitive", "A string");
|
||||||
assertFalse(primVal.isCanonical());
|
assertFalse(primVal.isCanonical());
|
||||||
|
|
||||||
TraceObject child =
|
TraceObject child = manager.createObject(TraceObjectKeyPath.parse("child"));
|
||||||
manager.createObject(TraceObjectKeyPath.parse("child"), Range.all());
|
|
||||||
|
|
||||||
TraceObjectValue objVal = root.setValue(Range.all(), "child", child);
|
TraceObjectValue objVal = root.setValue(Range.all(), "child", child);
|
||||||
assertTrue(objVal.isCanonical());
|
assertTrue(objVal.isCanonical());
|
||||||
|
|
|
@ -59,6 +59,7 @@ public class DBTraceThreadManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
public void testAddThread() throws Exception {
|
public void testAddThread() throws Exception {
|
||||||
addThreads();
|
addThreads();
|
||||||
try (UndoableTransaction tid = b.startTransaction()) {
|
try (UndoableTransaction tid = b.startTransaction()) {
|
||||||
|
// TODO: Let this work by expanding the life instead
|
||||||
threadManager.createThread("Threads[1]", 1);
|
threadManager.createThread("Threads[1]", 1);
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,6 @@ import java.lang.annotation.Target;
|
||||||
* The items are intended to become part of the public API, but the interfaces are unstable, and
|
* The items are intended to become part of the public API, but the interfaces are unstable, and
|
||||||
* there's no guarantee they will ever become public.
|
* there's no guarantee they will ever become public.
|
||||||
*/
|
*/
|
||||||
@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE })
|
@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE, PARAMETER })
|
||||||
public @interface Experimental {
|
public @interface Experimental {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue