GP-1678: Create experimental object-based recorder and opinion

This commit is contained in:
Dan 2022-04-28 15:13:01 -04:00
parent 20706efea3
commit 032ae36005
109 changed files with 3184 additions and 481 deletions

View file

@ -78,6 +78,6 @@ public abstract class AbstractModelForDbgengProcessActivationTest
.collect(Collectors.toList())).trim(); .collect(Collectors.toList())).trim();
String procId = getIdFromCapture(line); String procId = getIdFromCapture(line);
assertEquals(expected.getPath(), assertEquals(expected.getPath(),
getProcessPattern().applyIndices(procId).getSingletonPath()); getProcessPattern().applyKeys(procId).getSingletonPath());
} }
} }

View file

@ -83,6 +83,6 @@ public abstract class AbstractModelForGdbFrameActivationTest
assertFalse(line.contains("\n")); assertFalse(line.contains("\n"));
assertTrue(line.startsWith("#")); assertTrue(line.startsWith("#"));
String frameLevel = line.substring(1).split("\\s+")[0]; String frameLevel = line.substring(1).split("\\s+")[0];
assertEquals(expected.getPath(), STACK_PATTERN.applyIndices(frameLevel).getSingletonPath()); assertEquals(expected.getPath(), STACK_PATTERN.applyKeys(frameLevel).getSingletonPath());
} }
} }

View file

@ -74,6 +74,6 @@ public abstract class AbstractModelForGdbInferiorActivationTest
.filter(l -> l.trim().startsWith("*")) .filter(l -> l.trim().startsWith("*"))
.collect(Collectors.toList())).trim(); .collect(Collectors.toList())).trim();
String inferiorId = line.split("\\s+")[1]; String inferiorId = line.split("\\s+")[1];
assertEquals(expected.getPath(), INF_PATTERN.applyIndices(inferiorId).getSingletonPath()); assertEquals(expected.getPath(), INF_PATTERN.applyKeys(inferiorId).getSingletonPath());
} }
} }

View file

@ -87,6 +87,6 @@ public abstract class AbstractModelForGdbThreadActivationTest
.collect(Collectors.toList())); .collect(Collectors.toList()));
String threadGid = line.split("\\s+")[2]; String threadGid = line.split("\\s+")[2];
assertEquals(expected.getPath(), assertEquals(expected.getPath(),
THREAD_PATTERN.applyIndices(threadGid, threadGid).getSingletonPath()); THREAD_PATTERN.applyKeys(threadGid, threadGid).getSingletonPath());
} }
} }

View file

@ -60,7 +60,7 @@ public abstract class AbstractModelForLldbScenarioX64RegistersTest
for (String name : toWrite.keySet()) { for (String name : toWrite.keySet()) {
for (TargetRegisterBank bank : banks.values()) { for (TargetRegisterBank bank : banks.values()) {
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
bank.getPath(), pred -> pred.applyIndices(name), false); bank.getPath(), pred -> pred.applyKeys(name), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
waitOn(bank.writeRegister(reg, toWrite.get(name))); waitOn(bank.writeRegister(reg, toWrite.get(name)));
} }

View file

@ -81,7 +81,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (Entry<String, byte[]> ent : getRegisterWrites().entrySet()) { for (Entry<String, byte[]> ent : getRegisterWrites().entrySet()) {
String regName = ent.getKey(); String regName = ent.getKey();
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
path, pred -> pred.applyIndices(regName), false); path, pred -> pred.applyKeys(regName), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
assertEquals(ent.getValue().length, (reg.getBitLength() + 7) / 8); assertEquals(ent.getValue().length, (reg.getBitLength() + 7) / 8);
} }
@ -121,7 +121,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (TargetRegisterBank bank : banks.values()) { for (TargetRegisterBank bank : banks.values()) {
for (String name : exp.keySet()) { for (String name : exp.keySet()) {
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
bank.getPath(), pred -> pred.applyIndices(name), false); bank.getPath(), pred -> pred.applyKeys(name), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
byte[] bytes = waitOn(bank.readRegister(reg)); byte[] bytes = waitOn(bank.readRegister(reg));
read.put(name, bytes); read.put(name, bytes);
@ -149,7 +149,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (TargetRegisterBank bank : banks.values()) { for (TargetRegisterBank bank : banks.values()) {
for (String name : write.keySet()) { for (String name : write.keySet()) {
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
bank.getPath(), pred -> pred.applyIndices(name), false); bank.getPath(), pred -> pred.applyKeys(name), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
waitOn(bank.writeRegister(reg, write.get(name))); waitOn(bank.writeRegister(reg, write.get(name)));

View file

@ -67,7 +67,7 @@ public abstract class DebuggerReadsMemoryTrait {
Trace trace = current.getTrace(); Trace trace = current.getTrace();
TraceRecorder recorder = current.getRecorder(); TraceRecorder recorder = current.getRecorder();
BackgroundUtils.async(tool, trace, NAME, true, true, false, BackgroundUtils.async(tool, trace, NAME, true, true, false,
(__, monitor) -> recorder.captureProcessMemory(selection, monitor, false)); (__, monitor) -> recorder.readMemoryBlocks(selection, monitor, false));
} }
@Override @Override
@ -78,7 +78,7 @@ public abstract class DebuggerReadsMemoryTrait {
} }
TraceRecorder recorder = current.getRecorder(); TraceRecorder recorder = current.getRecorder();
// TODO: Either allow partial, or provide action to intersect with accessible // TODO: Either allow partial, or provide action to intersect with accessible
if (!recorder.getAccessibleProcessMemory().contains(selection)) { if (!recorder.getAccessibleMemory().contains(selection)) {
return false; return false;
} }
return true; return true;

View file

@ -73,7 +73,7 @@ public class PCLocationTrackingSpec implements RegisterLocationTrackingSpec {
if (frame == null) { if (frame == null) {
return null; return null;
} }
return frame.getProgramCounter(); return frame.getProgramCounter(snap);
} }
@Override @Override

View file

@ -56,7 +56,7 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
} }
TraceRecorder recorder = coordinates.getRecorder(); TraceRecorder recorder = coordinates.getRecorder();
AddressSet visibleAccessible = AddressSet visibleAccessible =
recorder.getAccessibleProcessMemory().intersect(visible); recorder.getAccessibleMemory().intersect(visible);
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = AddressSetView alreadyKnown =
mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible, mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible,
@ -67,6 +67,6 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }
return recorder.captureProcessMemory(toRead, TaskMonitor.DUMMY, false); return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY, false);
} }
} }

View file

@ -58,7 +58,7 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
} }
TraceRecorder recorder = coordinates.getRecorder(); TraceRecorder recorder = coordinates.getRecorder();
AddressSet visibleAccessible = AddressSet visibleAccessible =
recorder.getAccessibleProcessMemory().intersect(visible); recorder.getAccessibleMemory().intersect(visible);
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = AddressSetView alreadyKnown =
mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible, mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible,
@ -92,6 +92,6 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }
return recorder.captureProcessMemory(toRead, TaskMonitor.DUMMY, false); return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY, false);
} }
} }

View file

@ -810,7 +810,7 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
throws Exception { throws Exception {
synchronized (this) { synchronized (this) {
monitor.checkCanceled(); monitor.checkCanceled();
this.captureTask = recorder.captureProcessMemory(new AddressSet(range), monitor, false); this.captureTask = recorder.readMemoryBlocks(new AddressSet(range), monitor, false);
} }
try { try {
captureTask.get(); // Not a fan, but whatever. captureTask.get(); // Not a fan, but whatever.

View file

@ -854,6 +854,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
public boolean isRoot(ActionContext context) {
TargetObject object = this.getObjectFromContext(context);
if (object == null) {
return false;
}
return object.isRoot();
}
public boolean isInstance(ActionContext context, Class<? extends TargetObject> clazz) { public boolean isInstance(ActionContext context, Class<? extends TargetObject> clazz) {
TargetObject object = this.getObjectFromContext(context); TargetObject object = this.getObjectFromContext(context);
if (object == null) { if (object == null) {
@ -1128,8 +1136,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
.popupMenuGroup(DebuggerResources.GROUP_TARGET, "T" + groupTargetIndex) .popupMenuGroup(DebuggerResources.GROUP_TARGET, "T" + groupTargetIndex)
.popupMenuIcon(AbstractRecordAction.ICON) .popupMenuIcon(AbstractRecordAction.ICON)
.helpLocation(new HelpLocation(plugin.getName(), "record")) .helpLocation(new HelpLocation(plugin.getName(), "record"))
.enabledWhen(ctx -> isInstance(ctx, TargetProcess.class)) .enabledWhen(ctx -> isInstance(ctx, TargetProcess.class) || isRoot(ctx))
.popupWhen(ctx -> isInstance(ctx, TargetProcess.class)) .popupWhen(ctx -> isInstance(ctx, TargetProcess.class) || isRoot(ctx))
.onAction(ctx -> performStartRecording(ctx)) .onAction(ctx -> performStartRecording(ctx))
.enabled(true) .enabled(true)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
@ -1592,7 +1600,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
} }
public void startRecording(TargetProcess targetObject, boolean prompt) { public void startRecording(TargetObject targetObject, boolean prompt) {
TraceRecorder rec = modelService.getRecorder(targetObject); TraceRecorder rec = modelService.getRecorder(targetObject);
if (rec != null) { if (rec != null) {
return; // Already being recorded return; // Already being recorded
@ -1630,6 +1638,11 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
public void performStartRecording(ActionContext context) { public void performStartRecording(ActionContext context) {
TargetObject maybeRoot = getObjectFromContext(context);
if (maybeRoot.isRoot()) {
startRecording(maybeRoot, true);
return;
}
performAction(context, false, TargetProcess.class, proc -> { performAction(context, false, TargetProcess.class, proc -> {
TargetProcess valid = DebugModelConventions.liveProcessOrNull(proc); TargetProcess valid = DebugModelConventions.liveProcessOrNull(proc);
if (valid != null) { if (valid != null) {

View file

@ -410,6 +410,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
return; return;
} }
if (currentStack == stack) { if (currentStack == stack) {
stackTableModel.fireTableDataChanged();
return; return;
} }
currentStack = stack; currentStack = stack;

View file

@ -28,41 +28,50 @@ import ghidra.util.database.UndoableTransaction;
public class StackFrameRow { public class StackFrameRow {
public static class Synthetic extends StackFrameRow { public static class Synthetic extends StackFrameRow {
private Address pc;
public Synthetic(DebuggerStackProvider provider, Address pc) { public Synthetic(DebuggerStackProvider provider, Address pc) {
super(provider, pc); super(provider);
this.pc = pc;
} }
public void updateProgramCounter(Address pc) { public void updateProgramCounter(Address pc) {
this.pc = pc; this.pc = pc;
} }
@Override
public Address getProgramCounter() {
return pc;
}
} }
private final DebuggerStackProvider provider; private final DebuggerStackProvider provider;
final TraceStackFrame frame; final TraceStackFrame frame;
private int level; private int level;
Address pc;
public StackFrameRow(DebuggerStackProvider provider, TraceStackFrame frame) { public StackFrameRow(DebuggerStackProvider provider, TraceStackFrame frame) {
this.provider = provider; this.provider = provider;
this.frame = frame; this.frame = frame;
this.level = frame.getLevel(); this.level = frame.getLevel();
this.pc = frame.getProgramCounter();
} }
private StackFrameRow(DebuggerStackProvider provider, Address pc) { private StackFrameRow(DebuggerStackProvider provider) {
this.provider = provider; this.provider = provider;
this.frame = null; this.frame = null;
this.level = 0; this.level = 0;
this.pc = pc;
} }
public int getFrameLevel() { public int getFrameLevel() {
return level; return level;
} }
public long getSnap() {
return provider.current.getSnap();
}
public Address getProgramCounter() { public Address getProgramCounter() {
return pc; return frame.getProgramCounter(getSnap());
} }
public String getComment() { public String getComment() {
@ -88,11 +97,12 @@ public class StackFrameRow {
if (curThread == null) { if (curThread == null) {
return null; return null;
} }
Address pc = getProgramCounter();
if (pc == null) { if (pc == null) {
return null; return null;
} }
TraceLocation dloc = new DefaultTraceLocation(curThread.getTrace(), TraceLocation dloc = new DefaultTraceLocation(curThread.getTrace(),
curThread, Range.singleton(provider.current.getSnap()), pc); curThread, Range.singleton(getSnap()), pc);
ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc); ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc);
if (sloc == null) { if (sloc == null) {
return null; return null;
@ -103,6 +113,5 @@ public class StackFrameRow {
protected void update() { protected void update() {
assert frame != null; // Should never update a synthetic stack assert frame != null; // Should never update a synthetic stack
level = frame.getLevel(); level = frame.getLevel();
pc = frame.getProgramCounter();
} }
} }

View file

@ -578,11 +578,18 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
} }
private ProgramLocation getDynamicLocation(ProgramLocation someLoc) { private ProgramLocation getDynamicLocation(ProgramLocation someLoc) {
if (someLoc == null) {
return null;
}
TraceProgramView view = current.getView(); TraceProgramView view = current.getView();
if (view == null) { if (view == null) {
return null; return null;
} }
if (someLoc.getProgram() instanceof TraceProgramView) { Program program = someLoc.getProgram();
if (program == null) {
return null;
}
if (program instanceof TraceProgramView) {
return someLoc; return someLoc;
} }
return mappingService.getDynamicLocationFromStatic(view, someLoc); return mappingService.getDynamicLocationFromStatic(view, someLoc);

View file

@ -101,6 +101,7 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
*/ */
public default Set<DebuggerMappingOffer> getOffers(TargetObject target, public default Set<DebuggerMappingOffer> getOffers(TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
// TODO: Remove this check?
if (!(target instanceof TargetProcess)) { if (!(target instanceof TargetProcess)) {
return Set.of(); return Set.of();
} }
@ -117,13 +118,13 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
} }
/** /**
* Produce this opinion's offers for the given environment and target process * Produce this opinion's offers for the given environment and target
* *
* @param env the environment associated with the target * @param env the environment associated with the target
* @param process the target process * @param target the target (usually a process)
* @param includeOverrides true to include override offers, i.e., those with negative confidence * @param includeOverrides true to include override offers, i.e., those with negative confidence
* @return the offers, possibly empty, but never null * @return the offers, possibly empty, but never null
*/ */
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides); boolean includeOverrides);
} }

View file

@ -15,8 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.app.plugin.core.debug.mapping;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressRange;
public interface DebuggerMemoryMapper { public interface DebuggerMemoryMapper {
/** /**
@ -33,7 +32,10 @@ public interface DebuggerMemoryMapper {
* @param traceRange the range in the view's address space * @param traceRange the range in the view's address space
* @return the "same range" in the target's address space * @return the "same range" in the target's address space
*/ */
AddressRange traceToTarget(AddressRange traceRange); default AddressRange traceToTarget(AddressRange traceRange) {
return new AddressRangeImpl(traceToTarget(traceRange.getMinAddress()),
traceToTarget(traceRange.getMaxAddress()));
}
/** /**
* Map the given address from the target process into the trace * Map the given address from the target process into the trace
@ -49,5 +51,8 @@ public interface DebuggerMemoryMapper {
* @param targetRange the range in the target's address space * @param targetRange the range in the target's address space
* @return the "same range" in the trace's address space * @return the "same range" in the trace's address space
*/ */
AddressRange targetToTrace(AddressRange targetRange); default AddressRange targetToTrace(AddressRange targetRange) {
return new AddressRangeImpl(targetToTrace(targetRange.getMinAddress()),
targetToTrace(targetRange.getMaxAddress()));
}
} }

View file

@ -45,12 +45,6 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
public Address traceToTarget(Address traceAddr) { public Address traceToTarget(Address traceAddr) {
assert isInFactory(traceAddr, traceAddressFactory); assert isInFactory(traceAddr, traceAddressFactory);
return toSameNamedSpace(traceAddr, targetAddressFactory); return toSameNamedSpace(traceAddr, targetAddressFactory);
};
@Override
public AddressRange traceToTarget(AddressRange traceRange) {
return new AddressRangeImpl(traceToTarget(traceRange.getMinAddress()),
traceToTarget(traceRange.getMaxAddress()));
} }
@Override @Override
@ -65,10 +59,4 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
assert isInFactory(targetAddr, targetAddressFactory); assert isInFactory(targetAddr, targetAddressFactory);
return toSameNamedSpace(targetAddr, traceAddressFactory); return toSameNamedSpace(targetAddr, traceAddressFactory);
} }
@Override
public AddressRange targetToTrace(AddressRange targetRange) {
return new AddressRangeImpl(targetToTrace(targetRange.getMinAddress()),
targetToTrace(targetRange.getMaxAddress()));
}
} }

View file

@ -35,12 +35,12 @@ public class DefaultDebuggerTargetTraceMapper implements DebuggerTargetTraceMapp
protected final Set<String> extraRegNames; protected final Set<String> extraRegNames;
public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID, public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID,
CompilerSpecID csId, Collection<String> extraRegNames) CompilerSpecID csID, Collection<String> extraRegNames)
throws LanguageNotFoundException, CompilerSpecNotFoundException { throws LanguageNotFoundException, CompilerSpecNotFoundException {
this.target = target; this.target = target;
LanguageService langServ = DefaultLanguageService.getLanguageService(); LanguageService langServ = DefaultLanguageService.getLanguageService();
this.language = langServ.getLanguage(langID); this.language = langServ.getLanguage(langID);
this.cSpec = language.getCompilerSpecByID(csId); this.cSpec = language.getCompilerSpecByID(csID);
this.extraRegNames = Set.copyOf(extraRegNames); this.extraRegNames = Set.copyOf(extraRegNames);
} }

View file

@ -0,0 +1,40 @@
/* ###
* 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.app.plugin.core.debug.mapping;
import java.util.Set;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.lang.*;
public class ObjectBasedDebuggerMappingOffer extends DefaultDebuggerMappingOffer {
private static final String DESCRIPTION =
"EXPERIMENTAL: Object-based recording, deferred mapping";
private static final int CONFIDENCE = -100; // TODO: Increase this when it becomes preferred
protected static final LanguageID LANGID_DATA64 = new LanguageID("DATA:BE:64:default");
protected static final CompilerSpecID CSID_PTR64 = new CompilerSpecID("pointer64");
public ObjectBasedDebuggerMappingOffer(TargetObject target) {
// TODO: Is extraRegNames relevant?
super(target, CONFIDENCE, DESCRIPTION, LANGID_DATA64, CSID_PTR64, Set.of());
}
@Override
protected DebuggerTargetTraceMapper createMapper()
throws LanguageNotFoundException, CompilerSpecNotFoundException {
return new ObjectBasedDebuggerTargetTraceMapper(target, langID, csID, extraRegNames);
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.app.plugin.core.debug.mapping;
import java.util.Set;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetObject;
public class ObjectBasedDebuggerMappingOpinion implements DebuggerMappingOpinion {
@Override
public Set<DebuggerMappingOffer> getOffers(TargetObject target, boolean includeOverrides) {
// TODO: Remove this check
if (!includeOverrides) {
return Set.of();
}
// TODO: Do I want to require it to record the whole model?
// If not, I need to figure out how to locate object dependencies and still record them.
if (!target.isRoot()) {
return Set.of();
}
return Set.of(new ObjectBasedDebuggerMappingOffer(target));
}
@Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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.app.plugin.core.debug.mapping;
import java.util.HashMap;
import java.util.Map;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.DuplicateNameException;
public class ObjectBasedDebuggerMemoryMapper implements DebuggerMemoryMapper {
protected final Trace trace;
protected final AddressSpace base;
protected final Map<Integer, AddressSpace> targetToTraceSpaces = new HashMap<>();
protected final Map<Integer, AddressSpace> traceToTargetSpaces = new HashMap<>();
public ObjectBasedDebuggerMemoryMapper(Trace trace) {
this.trace = trace;
this.base = trace.getBaseAddressFactory().getDefaultAddressSpace();
}
@Override
public Address traceToTarget(Address traceAddr) {
AddressSpace traceSpace = traceAddr.getAddressSpace();
int traceIdHash = System.identityHashCode(traceSpace);
AddressSpace targetSpace;
synchronized (traceToTargetSpaces) {
targetSpace = traceToTargetSpaces.get(traceIdHash);
}
/**
* Can only be null if space is the default space or some non-physical space. In that case,
* the target hasn't defined a space with that name, so no mapping.
*/
if (targetSpace == null) {
return null;
}
return targetSpace.getAddress(traceAddr.getOffset());
}
@Override
public Address targetToTrace(Address targetAddr) {
AddressSpace targetSpace = targetAddr.getAddressSpace();
int targetIdHash = System.identityHashCode(targetSpace);
AddressSpace traceSpace;
synchronized (traceToTargetSpaces) {
traceSpace = targetToTraceSpaces.get(targetIdHash);
if (traceSpace == null) {
traceSpace = createSpace(targetSpace.getName());
targetToTraceSpaces.put(targetIdHash, traceSpace);
traceToTargetSpaces.put(System.identityHashCode(traceSpace),
targetSpace);
}
}
return traceSpace.getAddress(targetAddr.getOffset());
}
protected AddressSpace createSpace(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space for mapping", true)) {
AddressFactory factory = trace.getBaseAddressFactory();
AddressSpace space = factory.getAddressSpace(name);
if (space == null) {
return trace.getMemoryManager().createOverlayAddressSpace(name, base);
}
// Let the default space suffice for its own name
// NB. if overlay already exists, we've already issued a warning
if (space == base || space.isOverlaySpace()) {
return space;
}
// Otherwise, do not allow non-physical spaces to be used by accident.
return trace.getMemoryManager().createOverlayAddressSpace('_' + name, base);
}
catch (DuplicateNameException e) {
throw new AssertionError(e);
}
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.app.plugin.core.debug.mapping;
import java.util.Collection;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin;
import ghidra.app.plugin.core.debug.service.model.record.ObjectBasedTraceRecorder;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
public class ObjectBasedDebuggerTargetTraceMapper extends DefaultDebuggerTargetTraceMapper {
protected ObjectBasedDebuggerMemoryMapper memoryMapper;
public ObjectBasedDebuggerTargetTraceMapper(TargetObject target, LanguageID langID,
CompilerSpecID csID, Collection<String> extraRegNames)
throws LanguageNotFoundException, CompilerSpecNotFoundException {
super(target, langID, csID, extraRegNames);
}
@Override
protected DebuggerMemoryMapper createMemoryMapper(TargetMemory memory) {
// TODO: Validate regions to not overlap?
// Could probably do that in unit testing of model instead
return memoryMapper;
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
throw new UnsupportedOperationException();
}
@Override
public TraceRecorder startRecording(DebuggerModelServicePlugin service, Trace trace) {
this.memoryMapper = new ObjectBasedDebuggerMemoryMapper(trace);
return new ObjectBasedTraceRecorder(service, trace, target, this);
}
}

View file

@ -19,7 +19,8 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.*; import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
@ -54,7 +55,7 @@ public class OverridesDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!includeOverrides) { if (!includeOverrides) {
return Set.of(); return Set.of();
@ -65,7 +66,7 @@ public class OverridesDebuggerMappingOpinion implements DebuggerMappingOpinion {
// ALL THE SPECS!!! // ALL THE SPECS!!!
new LanguageCompilerSpecQuery(null, null, null, null, null)) new LanguageCompilerSpecQuery(null, null, null, null, null))
.stream() .stream()
.map(lcsp -> offerForLanguageAndCSpec(process, endian, lcsp)) .map(lcsp -> offerForLanguageAndCSpec(target, endian, lcsp))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }

View file

@ -49,24 +49,28 @@ public class FridaArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
protected static class FridaAarch64MacosOffer extends DefaultDebuggerMappingOffer { protected static class FridaAarch64MacosOffer extends DefaultDebuggerMappingOffer {
public FridaAarch64MacosOffer(TargetProcess process) { public FridaAarch64MacosOffer(TargetProcess process) {
super(process, 50, "AARCH64/Frida on macos", LANG_ID_AARCH64,COMP_ID_DEFAULT, super(process, 50, "AARCH64/Frida on macos", LANG_ID_AARCH64, COMP_ID_DEFAULT,
Set.of("cpsr")); Set.of("cpsr"));
} }
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includesOverrides) { boolean includesOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("frida")) { if (!env.getDebugger().toLowerCase().contains("frida")) {
return Set.of(); return Set.of();
} }
String arch = env.getArchitecture(); String arch = env.getArchitecture();
boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm"); boolean is64Bit =
arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm");
String os = env.getOperatingSystem(); String os = env.getOperatingSystem();
if (os.contains("macos")) { if (os.contains("macos")) {
if (is64Bit) { if (is64Bit) {
Msg.info(this, "Using os=" + os + " arch=" + arch); Msg.info(this, "Using os=" + os + " arch=" + arch);
return Set.of(new FridaAarch64MacosOffer(process)); return Set.of(new FridaAarch64MacosOffer((TargetProcess) target));
} }
} }
return Set.of(); return Set.of();

View file

@ -49,14 +49,17 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
protected static class LldbAarch64MacosOffer extends DefaultDebuggerMappingOffer { protected static class LldbAarch64MacosOffer extends DefaultDebuggerMappingOffer {
public LldbAarch64MacosOffer(TargetProcess process) { public LldbAarch64MacosOffer(TargetProcess process) {
super(process, 50, "AARCH64/LLDB on macos", LANG_ID_AARCH64,COMP_ID_DEFAULT, super(process, 50, "AARCH64/LLDB on macos", LANG_ID_AARCH64, COMP_ID_DEFAULT,
Set.of("cpsr")); Set.of("cpsr"));
} }
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includesOverrides) { boolean includesOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("lldb")) { if (!env.getDebugger().toLowerCase().contains("lldb")) {
return Set.of(); return Set.of();
} }
@ -66,7 +69,7 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
if (os.contains("macos")) { if (os.contains("macos")) {
if (is64Bit) { if (is64Bit) {
Msg.info(this, "Using os=" + os + " arch=" + arch); Msg.info(this, "Using os=" + os + " arch=" + arch);
return Set.of(new LldbAarch64MacosOffer(process)); return Set.of(new LldbAarch64MacosOffer((TargetProcess) target));
} }
} }
return Set.of(); return Set.of();

View file

@ -75,15 +75,18 @@ public class DbgengX64DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (env == null || !env.getDebugger().toLowerCase().contains("dbg")) { if (env == null || !env.getDebugger().toLowerCase().contains("dbg")) {
return Set.of(); return Set.of();
} }
boolean is64Bit = boolean is64Bit =
env.getArchitecture().contains("x86_64") || env.getArchitecture().contains("x64_32"); env.getArchitecture().contains("x86_64") || env.getArchitecture().contains("x64_32");
if (is64Bit) { if (is64Bit) {
return Set.of(new DbgI386X86_64WindowsOffer(process)); return Set.of(new DbgI386X86_64WindowsOffer((TargetProcess) target));
} }
return null; return null;
} }

View file

@ -96,7 +96,7 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject {
try { try {
// This is on its own task thread, so whatever. // This is on its own task thread, so whatever.
// Just don't hang it indefinitely. // Just don't hang it indefinitely.
recorder.captureProcessMemory(set, TaskMonitor.DUMMY, false) recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, false)
.get(1000, TimeUnit.MILLISECONDS); .get(1000, TimeUnit.MILLISECONDS);
} }
catch (InterruptedException | ExecutionException | TimeoutException e) { catch (InterruptedException | ExecutionException | TimeoutException e) {

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.frida;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -71,16 +70,22 @@ public class FridaX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("frida")) { if (!env.getDebugger().toLowerCase().contains("frida")) {
return Set.of(); return Set.of();
} }
String arch = env.getArchitecture(); String arch = env.getArchitecture();
boolean is32Bit = arch.contains("ia32") ||arch.contains("x86-32") || arch.contains("i386") || boolean is32Bit =
arch.contains("x86_32"); arch.contains("ia32") || arch.contains("x86-32") || arch.contains("i386") ||
boolean is64Bit = arch.contains("x64") ||arch.contains("x86-64") || arch.contains("x64-32") || arch.contains("x86_32");
arch.contains("x86_64") || arch.contains("x64_32") || arch.contains("i686"); boolean is64Bit =
arch.contains("x64") || arch.contains("x86-64") || arch.contains("x64-32") ||
arch.contains("x86_64") || arch.contains("x64_32") || arch.contains("i686");
String os = env.getOperatingSystem(); String os = env.getOperatingSystem();
if (os.contains("darwin")) { if (os.contains("darwin")) {
if (is64Bit) { if (is64Bit) {

View file

@ -21,7 +21,8 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.*; import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
@ -81,7 +82,7 @@ public class DefaultGdbDebuggerMappingOpinion implements DebuggerMappingOpinion
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!isGdb(env)) { if (!isGdb(env)) {
return Set.of(); return Set.of();
@ -90,7 +91,7 @@ public class DefaultGdbDebuggerMappingOpinion implements DebuggerMappingOpinion
String arch = env.getArchitecture(); String arch = env.getArchitecture();
return getCompilerSpecsForGnu(arch, endian).stream() return getCompilerSpecsForGnu(arch, endian).stream()
.flatMap(lcsp -> offersForLanguageAndCSpec(process, arch, endian, lcsp).stream()) .flatMap(lcsp -> offersForLanguageAndCSpec(target, arch, endian, lcsp).stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.gdb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@ -39,8 +38,11 @@ public class GdbM68kDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }
@ -54,7 +56,7 @@ public class GdbM68kDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
String arch = env.getArchitecture(); String arch = env.getArchitecture();
if (arch.startsWith("m68k")) { if (arch.startsWith("m68k")) {
return Set.of(new GdbM68kBELinux32DefOffer(process)); return Set.of(new GdbM68kBELinux32DefOffer((TargetProcess) target));
} }
return Set.of(); return Set.of();
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.gdb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@ -79,8 +78,12 @@ public class GdbMipsDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.gdb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@ -65,8 +64,12 @@ public class GdbPowerPCDebuggerMappingOpinion implements DebuggerMappingOpinion
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }

View file

@ -100,8 +100,12 @@ public class GdbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }

View file

@ -64,8 +64,11 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().contains("Java Debug Interface")) { if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of(); return Set.of();
} }
@ -73,6 +76,6 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
return Set.of(); return Set.of();
} }
// NOTE: Not worried about JRE version // NOTE: Not worried about JRE version
return Set.of(new DalvikDebuggerMappingOffer(process)); return Set.of(new DalvikDebuggerMappingOffer((TargetProcess) target));
} }
} }

View file

@ -64,8 +64,11 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().contains("Java Debug Interface")) { if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of(); return Set.of();
} }
@ -73,6 +76,6 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
return Set.of(); return Set.of();
} }
// NOTE: Not worried about JRE version // NOTE: Not worried about JRE version
return Set.of(new JavaDebuggerMappingOffer(process)); return Set.of(new JavaDebuggerMappingOffer((TargetProcess) target));
} }
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.lldb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -71,8 +70,12 @@ public class LldbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("lldb")) { if (!env.getDebugger().toLowerCase().contains("lldb")) {
return Set.of(); return Set.of();
} }

View file

@ -250,6 +250,11 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
Msg.info(this, "Ignoring " + breakpoint + Msg.info(this, "Ignoring " + breakpoint +
" changed until service has finished loading its trace"); " changed until service has finished loading its trace");
} }
catch (NoSuchElementException e) {
// TODO: This catch clause should not be necessary.
Msg.error(this,
"!!!! Object-based breakpoint emitted event without a spec: " + breakpoint);
}
} }
private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull, private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull,

View file

@ -69,7 +69,7 @@ public class ReadsTargetMemoryPcodeExecutorState
if (!isLive()) { if (!isLive()) {
return false; return false;
} }
waitTimeout(recorder.captureProcessMemory(unknown, TaskMonitor.DUMMY, false)); waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true; return true;
} }

View file

@ -21,11 +21,11 @@ import java.util.concurrent.CompletableFuture;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder; import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder;
import ghidra.async.AsyncUtils; import ghidra.app.plugin.core.debug.service.model.record.RecorderUtils;
import ghidra.async.TypeSpec;
import ghidra.dbg.target.TargetMemory; import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion; import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.program.model.address.*; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.*; import ghidra.trace.model.memory.*;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -35,20 +35,7 @@ import ghidra.util.task.TaskMonitor;
public class DefaultMemoryRecorder implements ManagedMemoryRecorder { public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
// For large memory captures // For large memory captures
private static final int BLOCK_SIZE = 4096; private static final int BLOCK_BITS = 12; // 4096 bytes
private static final long BLOCK_MASK = -1L << 12;
protected static AddressSetView expandToBlocks(AddressSetView asv) {
AddressSet result = new AddressSet();
// Not terribly efficient, but this is one range most of the time
for (AddressRange range : asv) {
AddressSpace space = range.getAddressSpace();
Address min = space.getAddress(range.getMinAddress().getOffset() & BLOCK_MASK);
Address max = space.getAddress(range.getMaxAddress().getOffset() | ~BLOCK_MASK);
result.add(new AddressRangeImpl(min, max));
}
return result;
}
private final DefaultTraceRecorder recorder; private final DefaultTraceRecorder recorder;
private final Trace trace; private final Trace trace;
@ -62,45 +49,7 @@ public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set, public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set,
TaskMonitor monitor, boolean toMap) { TaskMonitor monitor, boolean toMap) {
// TODO: Figure out how to display/select per-thread memory. return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor, toMap);
// Probably need a thread parameter passed in then?
// NOTE: That thread memory will already be chained to process memory. Good.
// NOTE: I don't intend to warn about the number of requests.
// They're delivered in serial, and there's a cancel button that works
int total = 0;
AddressSetView expSet = expandToBlocks(set)
.intersect(trace.getMemoryManager().getRegionsAddressSet(recorder.getSnap()));
for (AddressRange r : expSet) {
total += Long.divideUnsigned(r.getLength() + BLOCK_SIZE - 1, BLOCK_SIZE);
}
monitor.initialize(total);
monitor.setMessage("Capturing memory");
// TODO: Read blocks in parallel? Probably NO. Tends to overload the agent.
NavigableMap<Address, byte[]> result = toMap ? new TreeMap<>() : null;
return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> {
AddressRangeChunker blocks = new AddressRangeChunker(r, BLOCK_SIZE);
AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (vBlk, inner) -> {
// The listener in the recorder will copy to the Trace.
monitor.incrementProgress(1);
AddressRange tBlk = recorder.getMemoryMapper().traceToTarget(vBlk);
recorder.getProcessMemory()
.readMemory(tBlk.getMinAddress(), (int) tBlk.getLength())
.thenAccept(data -> {
if (toMap) {
result.put(tBlk.getMinAddress(), data);
}
})
.exceptionally(e -> {
Msg.error(this, "Error reading block " + tBlk + ": " + e);
// NOTE: Above may double log, since recorder listens for errors, too
return null; // Continue looping on errors
})
.thenApply(__ -> !monitor.isCancelled())
.handle(inner::repeatWhile);
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
}).thenApply(__ -> result);
} }
@Override @Override

View file

@ -86,7 +86,7 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) { public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true); TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
traceFrame.setProgramCounter(pc); traceFrame.setProgramCounter(null, pc); // Not object-based, so span=null
} }
public void recordFrame(TargetStackFrame frame) { public void recordFrame(TargetStackFrame frame) {

View file

@ -22,6 +22,8 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.*; import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.app.plugin.core.debug.service.model.record.DataTypeRecorder;
import ghidra.app.plugin.core.debug.service.model.record.SymbolRecorder;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener; import ghidra.app.services.TraceRecorderListener;
import ghidra.async.AsyncLazyValue; import ghidra.async.AsyncLazyValue;
@ -64,11 +66,11 @@ public class DefaultTraceRecorder implements TraceRecorder {
TraceObjectManager objectManager; TraceObjectManager objectManager;
DefaultBreakpointRecorder breakpointRecorder; DefaultBreakpointRecorder breakpointRecorder;
DefaultDataTypeRecorder datatypeRecorder; DataTypeRecorder datatypeRecorder;
DefaultMemoryRecorder memoryRecorder; DefaultMemoryRecorder memoryRecorder;
DefaultModuleRecorder moduleRecorder; DefaultModuleRecorder moduleRecorder;
DefaultProcessRecorder processRecorder; DefaultProcessRecorder processRecorder;
DefaultSymbolRecorder symbolRecorder; SymbolRecorder symbolRecorder;
DefaultTimeRecorder timeRecorder; DefaultTimeRecorder timeRecorder;
//protected final PermanentTransactionExecutor seqTx; //protected final PermanentTransactionExecutor seqTx;
@ -94,10 +96,10 @@ public class DefaultTraceRecorder implements TraceRecorder {
this.processRecorder = new DefaultProcessRecorder(this); this.processRecorder = new DefaultProcessRecorder(this);
this.breakpointRecorder = new DefaultBreakpointRecorder(this); this.breakpointRecorder = new DefaultBreakpointRecorder(this);
this.datatypeRecorder = new DefaultDataTypeRecorder(this); this.datatypeRecorder = new DataTypeRecorder(this);
this.memoryRecorder = new DefaultMemoryRecorder(this); this.memoryRecorder = new DefaultMemoryRecorder(this);
this.moduleRecorder = new DefaultModuleRecorder(this); this.moduleRecorder = new DefaultModuleRecorder(this);
this.symbolRecorder = new DefaultSymbolRecorder(this); this.symbolRecorder = new SymbolRecorder(this);
this.timeRecorder = new DefaultTimeRecorder(this); this.timeRecorder = new DefaultTimeRecorder(this);
this.objectManager = new TraceObjectManager(target, mapper, this); this.objectManager = new TraceObjectManager(target, mapper, this);
} }
@ -266,7 +268,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
/*---------------- CAPTURE METHODS -------------------*/ /*---------------- CAPTURE METHODS -------------------*/
@Override @Override
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set, public CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean toMap) { TaskMonitor monitor, boolean toMap) {
if (set.isEmpty()) { if (set.isEmpty()) {
return CompletableFuture.completedFuture(new TreeMap<>()); return CompletableFuture.completedFuture(new TreeMap<>());
@ -499,12 +501,6 @@ public class DefaultTraceRecorder implements TraceRecorder {
/*---------------- LISTENER METHODS -------------------*/ /*---------------- LISTENER METHODS -------------------*/
// UNUSED?
@Override
public TraceEventListener getListenerForRecord() {
return objectManager.getEventListener();
}
public ListenerSet<TraceRecorderListener> getListeners() { public ListenerSet<TraceRecorderListener> getListeners() {
return objectManager.getListeners(); return objectManager.getListeners();
} }
@ -526,17 +522,17 @@ public class DefaultTraceRecorder implements TraceRecorder {
} }
@Override @Override
public AddressSetView getAccessibleProcessMemory() { public AddressSetView getAccessibleMemory() {
return processRecorder.getAccessibleProcessMemory(); return processRecorder.getAccessibleProcessMemory();
} }
@Override @Override
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) { public CompletableFuture<byte[]> readMemory(Address start, int length) {
return processRecorder.readProcessMemory(start, length); return processRecorder.readProcessMemory(start, length);
} }
@Override @Override
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) { public CompletableFuture<Void> writeMemory(Address start, byte[] data) {
return processRecorder.writeProcessMemory(start, data); return processRecorder.writeProcessMemory(start, data);
} }
@ -566,6 +562,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
return true; return true;
} }
// UNUSED?
@Override @Override
public CompletableFuture<Void> flushTransactions() { public CompletableFuture<Void> flushTransactions() {
return parTx.flush(); return parTx.flush();

View file

@ -24,20 +24,22 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer; import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer; import ghidra.app.plugin.core.debug.utils.TransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx; import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx;
import ghidra.framework.model.DomainObjectException;
import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.ClosedException;
public class PermanentTransactionExecutor { public class PermanentTransactionExecutor {
private final TransactionCoalescer txc; private final TransactionCoalescer txc;
private final Executor[] threads; private final ExecutorService[] threads;
private final UndoableDomainObject obj; private final UndoableDomainObject obj;
public PermanentTransactionExecutor(UndoableDomainObject obj, String name, int threadCount, public PermanentTransactionExecutor(UndoableDomainObject obj, String name, int threadCount,
int delayMs) { int delayMs) {
this.obj = obj; this.obj = obj;
txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs); txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs);
this.threads = new Executor[threadCount]; this.threads = new ExecutorService[threadCount];
for (int i = 0; i < threadCount; i++) { for (int i = 0; i < threadCount; i++) {
ThreadFactory factory = new BasicThreadFactory.Builder() ThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern(name + "thread-" + i + "-%d") .namingPattern(name + "thread-" + i + "-%d")
@ -46,6 +48,12 @@ public class PermanentTransactionExecutor {
} }
} }
public void shutdownNow() {
for (ExecutorService t : threads) {
t.shutdownNow();
}
}
/** /**
* This hash is borrowed from {@link HashMap}, except for the power-of-two masking, since I * This hash is borrowed from {@link HashMap}, except for the power-of-two masking, since I
* don't want to force the thread count to be a power of two (though it probably is). In the * don't want to force the thread count to be a power of two (though it probably is). In the
@ -70,6 +78,12 @@ public class PermanentTransactionExecutor {
try (CoalescedTx tx = txc.start(description)) { try (CoalescedTx tx = txc.start(description)) {
runnable.run(); runnable.run();
} }
catch (DomainObjectException e) {
if (e.getCause() instanceof ClosedException) {
Msg.info(this, obj + " is closed. Shutting down transaction executor.");
shutdownNow();
}
}
}, selectThread(sel)).exceptionally(e -> { }, selectThread(sel)).exceptionally(e -> {
Msg.error(this, "Trouble recording " + description, e); Msg.error(this, "Trouble recording " + description, e);
return null; return null;

View file

@ -20,7 +20,7 @@ import ghidra.util.database.UndoableTransaction;
public class RecorderPermanentTransaction implements AutoCloseable { public class RecorderPermanentTransaction implements AutoCloseable {
static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) { public static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) {
UndoableTransaction tid = UndoableTransaction.start(obj, description, true); UndoableTransaction tid = UndoableTransaction.start(obj, description, true);
return new RecorderPermanentTransaction(obj, tid); return new RecorderPermanentTransaction(obj, tid);
} }

View file

@ -79,13 +79,6 @@ public class RecorderSimpleMemory implements AbstractRecorderMemory {
} }
} }
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
@Override @Override
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred, public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) { DebuggerMemoryMapper memMapper) {

View file

@ -32,6 +32,13 @@ public interface AbstractRecorderMemory {
public CompletableFuture<Void> writeMemory(Address address, byte[] data); public CompletableFuture<Void> writeMemory(Address address, byte[] data);
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred, public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper); DebuggerMemoryMapper memMapper);

View file

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.model; package ghidra.app.plugin.core.debug.service.model.record;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
@ -27,13 +29,13 @@ import ghidra.program.model.data.*;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DefaultDataTypeRecorder { public class DataTypeRecorder {
//private DefaultTraceRecorder recorder; //private TraceRecorder recorder;
private Trace trace; private final Trace trace;
private final TargetDataTypeConverter typeConverter; private final TargetDataTypeConverter typeConverter;
public DefaultDataTypeRecorder(DefaultTraceRecorder recorder) { public DataTypeRecorder(TraceRecorder recorder) {
//this.recorder = recorder; //this.recorder = recorder;
this.trace = recorder.getTrace(); this.trace = recorder.getTrace();
this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager()); this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager());

View file

@ -0,0 +1,65 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.plugin.core.debug.register.RegisterTypeInfo;
import ghidra.dbg.target.TargetRegister;
import ghidra.program.model.lang.Register;
public class EmptyDebuggerRegisterMapper implements DebuggerRegisterMapper {
@Override
public TargetRegister getTargetRegister(String name) {
return null;
}
@Override
public Register getTraceRegister(String name) {
return null;
}
@Override
public TargetRegister traceToTarget(Register register) {
return null;
}
@Override
public Register targetToTrace(TargetRegister tReg) {
return null;
}
@Override
public RegisterTypeInfo getDefaultTypeInfo(Register lReg) {
return null;
}
@Override
public Set<Register> getRegistersOnTarget() {
return null;
}
@Override
public void targetRegisterAdded(TargetRegister register) {
throw new UnsupportedOperationException();
}
@Override
public void targetRegisterRemoved(TargetRegister register) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,179 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.program.model.address.*;
import ghidra.trace.model.memory.*;
import ghidra.util.Msg;
import utilities.util.IDKeyed;
class MemoryRecorder {
protected final ObjectBasedTraceRecorder recorder;
protected final TraceMemoryManager memoryManager;
protected final Map<IDKeyed<AddressSpace>, TargetMemory> memoriesByTargetSpace =
new HashMap<>();
protected final Map<IDKeyed<TargetMemoryRegion>, AddressRange> regions = new HashMap<>();
protected MemoryRecorder(ObjectBasedTraceRecorder recorder) {
this.recorder = recorder;
this.memoryManager = recorder.trace.getMemoryManager();
}
private TargetMemory getMemoryForSpace(AddressSpace space) {
return memoriesByTargetSpace.get(new IDKeyed<>(space));
}
private void addMemoryForSpace(AddressSpace targetSpace, TargetMemory memory) {
TargetMemory exists =
memoriesByTargetSpace.put(new IDKeyed<>(targetSpace), memory);
if (exists != null && exists != memory) {
Msg.warn(this,
"Address space duplicated between memories: " + exists + " and " + memory);
}
}
protected void addRegionMemory(TargetMemoryRegion region, TargetMemory memory) {
addMemoryForSpace(region.getRange().getMinAddress().getAddressSpace(), memory);
}
protected void adjustRegionRange(TargetMemoryRegion region, AddressRange range) {
synchronized (regions) {
AddressRange tRange = recorder.memoryMapper.targetToTrace(range);
if (tRange == null) {
regions.remove(new IDKeyed<>(region));
}
else {
regions.put(new IDKeyed<>(region), tRange);
}
}
}
protected void removeMemory(TargetMemory memory) {
while (memoriesByTargetSpace.values().remove(memory))
;
}
protected void removeRegion(TargetMemoryRegion region) {
synchronized (regions) {
regions.remove(new IDKeyed<>(region));
}
}
protected CompletableFuture<byte[]> read(Address start, int length) {
Address tStart = recorder.memoryMapper.traceToTarget(start);
if (tStart == null) {
return CompletableFuture.completedFuture(new byte[] {});
}
TargetMemory memory = getMemoryForSpace(tStart.getAddressSpace());
if (memory == null) {
return CompletableFuture.completedFuture(new byte[] {});
}
return memory.readMemory(tStart, length);
}
protected CompletableFuture<Void> write(Address start, byte[] data) {
Address tStart = recorder.memoryMapper.traceToTarget(start);
if (tStart == null) {
throw new IllegalArgumentException(
"Address space " + start.getAddressSpace() + " not defined on the target");
}
TargetMemory memory = getMemoryForSpace(tStart.getAddressSpace());
if (memory == null) {
throw new IllegalArgumentException(
"Address space " + tStart.getAddressSpace() +
" cannot be found in target memory");
}
return memory.writeMemory(tStart, data);
}
protected void invalidate(TargetMemory memory, long snap) {
Set<AddressSpace> targetSpaces = memoriesByTargetSpace.entrySet()
.stream()
.filter(e -> e.getValue() == memory)
.map(e -> e.getKey().obj)
.collect(Collectors.toSet());
for (AddressSpace targetSpace : targetSpaces) {
Address traceMin = recorder.memoryMapper.targetToTrace(targetSpace.getMinAddress());
Address traceMax = traceMin.getAddressSpace().getMaxAddress();
memoryManager.setState(snap, traceMin, traceMax, TraceMemoryState.UNKNOWN);
}
}
protected void recordMemory(long snap, Address start, byte[] data) {
memoryManager.putBytes(snap, start, ByteBuffer.wrap(data));
}
public void recordError(long snap, Address tMin, DebuggerMemoryAccessException e) {
// TODO: Bookmark to describe error?
memoryManager.setState(snap, tMin, TraceMemoryState.ERROR);
}
protected boolean isAccessible(TraceMemoryRegion r) {
// TODO: Perhaps a bit aggressive, but haven't really been checking anyway.
return true;
}
protected Collector<AddressRange, AddressSet, AddressSet> toAddressSet() {
return new Collector<>() {
@Override
public Supplier<AddressSet> supplier() {
return AddressSet::new;
}
@Override
public BiConsumer<AddressSet, AddressRange> accumulator() {
return AddressSet::add;
}
@Override
public BinaryOperator<AddressSet> combiner() {
return (s1, s2) -> {
s1.add(s2);
return s1;
};
}
@Override
public Function<AddressSet, AddressSet> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
};
}
public AddressSetView getAccessible() {
synchronized (regions) {
return regions.values()
.stream()
.collect(toAddressSet());
}
}
}

View file

@ -0,0 +1,641 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin;
import ghidra.app.plugin.core.debug.service.model.PermanentTransactionExecutor;
import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener;
import ghidra.async.AsyncUtils;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.error.DebuggerModelAccessException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.module.TraceObjectSection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.stack.TraceObjectStackFrame;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.task.TaskMonitor;
public class ObjectBasedTraceRecorder implements TraceRecorder {
protected static final int POOL_SIZE = Math.min(16, Runtime.getRuntime().availableProcessors());
protected static final int DELAY_MS = 100;
protected static final int BLOCK_BITS = 12;
protected final Trace trace;
protected final TargetObject target;
protected final ObjectBasedDebuggerTargetTraceMapper mapper;
protected final DebuggerMemoryMapper memoryMapper;
protected final DebuggerRegisterMapper emptyRegisterMapper = new EmptyDebuggerRegisterMapper();
protected final TimeRecorder timeRecorder;
protected final ObjectRecorder objectRecorder;
protected final MemoryRecorder memoryRecorder;
protected final DataTypeRecorder dataTypeRecorder;
protected final SymbolRecorder symbolRecorder;
// upstream listeners
protected final ListenerForRecord listenerForRecord;
protected final ListenerSet<TraceRecorderListener> listeners =
new ListenerSet<>(TraceRecorderListener.class);
// TODO: I don't like this here. Should ask the model, not the recorder.
protected TargetObject curFocus;
protected boolean valid = true;
protected class ListenerForRecord extends AnnotatedDebuggerAttributeListener {
private final PermanentTransactionExecutor tx =
new PermanentTransactionExecutor(trace, "OBTraceRecorder: ", POOL_SIZE, DELAY_MS);
private boolean ignoreInvalidation = false;
// TODO: Do I need DebuggerCallbackReorderer?
public ListenerForRecord() {
super(MethodHandles.lookup());
}
@Override
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
String description, List<Object> parameters) {
if (!valid) {
return;
}
if (type == TargetEventType.RUNNING) {
/**
* Do not permit the current snapshot to be invalidated on account of the target
* running. When the STOP occurs, a new (completely UNKNOWN) snapshot is generated.
*/
ignoreInvalidation = true;
return;
/**
* TODO: Perhaps some configuration for this later. It's kind of interesting to
* record the RUNNING event time, but it gets pedantic when these exist between
* steps.
*/
}
/**
* Snapshot creation should not be offloaded to parallel executor or we may confuse
* event snaps.
*/
TraceObjectThread traceEventThread =
objectRecorder.getTraceInterface(eventThread, TraceObjectThread.class);
timeRecorder.createSnapshot(description, traceEventThread, null);
ignoreInvalidation = false;
// NB. Need not worry about CREATED, LOADED, etc. Just recording objects.
}
// NB. ignore executionStateChanged, since recording whole model
@Override
public void invalidateCacheRequested(TargetObject object) {
if (!valid) {
return;
}
if (ignoreInvalidation) {
return;
}
if (object instanceof TargetMemory) {
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Memory invalidated: " + path, () -> {
memoryRecorder.invalidate((TargetMemory) object, snap);
}, path);
}
}
// NB. ignore registersUpdated. All object-based, now.
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = memory.getJoinedPath(".");
Address tAddress = getMemoryMapper().targetToTrace(address);
tx.execute("Memory observed: " + path, () -> {
memoryRecorder.recordMemory(snap, tAddress, data);
}, path);
}
@Override
public void memoryReadError(TargetObject memory, AddressRange range,
DebuggerMemoryAccessException e) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = memory.getJoinedPath(".");
Address tMin = getMemoryMapper().targetToTrace(range.getMinAddress());
tx.execute("Memory read error: " + path, () -> {
memoryRecorder.recordError(snap, tMin, e);
}, path);
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
protected void focusChanged(TargetObject scope, TargetObject focused) {
if (!valid) {
return;
}
// NB. Don't care about ancestry. Focus should be model-wide anyway.
curFocus = focused;
}
@Override
public void created(TargetObject object) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
// Don't offload, because we need a consistent map
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Object created: " + path, true)) {
objectRecorder.recordCreated(snap, object);
}
}
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Object invalidated: " + path, () -> {
objectRecorder.recordInvalidated(snap, object);
}, path);
if (object == target) {
stopRecording();
return;
}
if (object instanceof TargetMemory) {
memoryRecorder.removeMemory((TargetMemory) object);
}
if (object instanceof TargetMemoryRegion) {
memoryRecorder.removeRegion((TargetMemoryRegion) object);
}
}
@Override
public void attributesChanged(TargetObject object, Collection<String> removed,
Map<String, ?> added) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Object attributes changed: " + path, () -> {
objectRecorder.recordAttributes(snap, object, removed, added);
}, path);
super.attributesChanged(object, removed, added);
}
@AttributeCallback(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME)
public void rangeChanged(TargetObject object, AddressRange range) {
if (!valid) {
return;
}
if (!(object instanceof TargetMemoryRegion)) {
return;
}
memoryRecorder.adjustRegionRange((TargetMemoryRegion) object, range);
}
@AttributeCallback(TargetMemoryRegion.MEMORY_ATTRIBUTE_NAME)
public void memoryChanged(TargetObject object, TargetMemory memory) {
if (!valid) {
return;
}
if (!(object instanceof TargetMemoryRegion)) {
return;
}
memoryRecorder.addRegionMemory((TargetMemoryRegion) object, memory);
}
@Override
public void elementsChanged(TargetObject object, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
if (!valid) {
return;
}
long snap = timeRecorder.getSnap();
String path = object.getJoinedPath(".");
tx.execute("Object elements changed: " + path, () -> {
objectRecorder.recordElements(snap, object, removed, added);
}, path);
}
}
public ObjectBasedTraceRecorder(DebuggerModelServicePlugin service, Trace trace,
TargetObject target, ObjectBasedDebuggerTargetTraceMapper mapper) {
trace.addConsumer(this);
this.trace = trace;
this.target = target;
this.mapper = mapper;
// TODO: Don't depend on memory in interface.
// TODO: offerMemory not async
memoryMapper = mapper.offerMemory(null).getNow(null);
timeRecorder = new TimeRecorder(this);
objectRecorder = new ObjectRecorder(this);
memoryRecorder = new MemoryRecorder(this);
dataTypeRecorder = new DataTypeRecorder(this);
symbolRecorder = new SymbolRecorder(this);
listenerForRecord = new ListenerForRecord();
}
@Override
public CompletableFuture<Void> init() {
// TODO: Make this method synchronous?
timeRecorder.createSnapshot("Started recording " + target.getModel(), null, null);
target.getModel().addModelListener(listenerForRecord, true);
return AsyncUtils.NIL;
}
@Override
public TargetObject getTarget() {
return target;
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public long getSnap() {
return timeRecorder.getSnap();
}
@Override
public TraceSnapshot forceSnapshot() {
return timeRecorder.forceSnapshot();
}
@Override
public boolean isRecording() {
return valid;
}
@Override
public void stopRecording() {
invalidate();
fireRecordingStopped();
}
protected void invalidate() {
target.getModel().removeModelListener(listenerForRecord);
synchronized (this) {
if (!valid) {
return;
}
valid = false;
}
trace.release(this);
}
@Override
public void addListener(TraceRecorderListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(TraceRecorderListener listener) {
listeners.remove(listener);
}
@Override
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
return objectRecorder.getTargetInterface(bpt, TraceObjectBreakpointLocation.class,
TargetBreakpointLocation.class);
}
@Override
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
return objectRecorder.getTraceInterface(bpt, TraceObjectBreakpointLocation.class);
}
@Override
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
return objectRecorder.getTargetInterface(region, TraceObjectMemoryRegion.class,
TargetMemoryRegion.class);
}
@Override
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
return objectRecorder.getTraceInterface(region, TraceObjectMemoryRegion.class);
}
@Override
public TargetModule getTargetModule(TraceModule module) {
return objectRecorder.getTargetInterface(module, TraceObjectModule.class,
TargetModule.class);
}
@Override
public TraceModule getTraceModule(TargetModule module) {
return objectRecorder.getTraceInterface(module, TraceObjectModule.class);
}
@Override
public TargetSection getTargetSection(TraceSection section) {
return objectRecorder.getTargetInterface(section, TraceObjectSection.class,
TargetSection.class);
}
@Override
public TraceSection getTraceSection(TargetSection section) {
return objectRecorder.getTraceInterface(section, TraceObjectSection.class);
}
@Override
public TargetThread getTargetThread(TraceThread thread) {
return objectRecorder.getTargetInterface(thread, TraceObjectThread.class,
TargetThread.class);
}
@Override
public TargetExecutionState getTargetThreadState(TargetThread thread) {
return thread.getTypedAttributeNowByName(TargetExecutionStateful.STATE_ATTRIBUTE_NAME,
TargetExecutionState.class, TargetExecutionState.INACTIVE);
}
@Override
public TargetExecutionState getTargetThreadState(TraceThread thread) {
return getTargetThreadState(getTargetThread(thread));
}
@Override
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
return objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class);
}
@Override
public TraceThread getTraceThread(TargetThread thread) {
return objectRecorder.getTraceInterface(thread, TraceObjectThread.class);
}
@Override
public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
TraceObject traceObject = objectRecorder.toTrace(successor);
return traceObject.queryCanonicalAncestorsInterface(Range.singleton(getSnap()),
TraceObjectThread.class).findFirst().orElse(null);
}
@Override
public TraceStackFrame getTraceStackFrame(TargetStackFrame frame) {
return objectRecorder.getTraceInterface(frame, TraceObjectStackFrame.class);
}
@Override
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
TraceObject traceObject = objectRecorder.toTrace(successor);
return traceObject.queryCanonicalAncestorsInterface(Range.singleton(getSnap()),
TraceObjectStackFrame.class).findFirst().orElse(null);
}
@Override
public TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel) {
return objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetStackFrame.class);
}
@Override
public Set<TargetThread> getLiveTargetThreads() {
return trace.getObjectManager()
.getRootObject()
.querySuccessorsInterface(Range.singleton(getSnap()), TraceObjectThread.class)
.map(t -> objectRecorder.getTargetInterface(t.getObject(), TargetThread.class))
.collect(Collectors.toSet());
}
@Override
public DebuggerRegisterMapper getRegisterMapper(TraceThread thread) {
return emptyRegisterMapper;
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return memoryMapper;
}
@Override
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
// TODO: This seems a little aggressive, but the accessbility thing is already out of hand
return true;
}
@Override
public boolean isRegisterBankAccessible(TraceThread thread, int frameLevel) {
// TODO: This seems a little aggressive, but the accessbility thing is already out of hand
return true;
}
@Override
public AddressSetView getAccessibleMemory() {
return memoryRecorder.getAccessible();
}
@Override
public CompletableFuture<Map<Register, RegisterValue>> captureThreadRegisters(
TraceThread thread, int frameLevel, Set<Register> registers) {
return CompletableFuture.completedFuture(Map.of());
}
@Override
public CompletableFuture<Void> writeThreadRegisters(TraceThread thread, int frameLevel,
Map<Register, RegisterValue> values) {
throw new UnsupportedOperationException();
}
@Override
public CompletableFuture<byte[]> readMemory(Address start, int length) {
return memoryRecorder.read(start, length);
}
@Override
public CompletableFuture<Void> writeMemory(Address start, byte[] data) {
return memoryRecorder.write(start, data);
}
@Override
public CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(
AddressSetView set, TaskMonitor monitor, boolean returnResult) {
return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor,
returnResult);
}
@Override
public CompletableFuture<Void> captureDataTypes(TraceModule module, TaskMonitor monitor) {
return dataTypeRecorder.captureDataTypes(getTargetModule(module), monitor);
}
@Override
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor) {
return dataTypeRecorder.captureDataTypes(namespace, monitor);
}
@Override
public CompletableFuture<Void> captureSymbols(TraceModule module, TaskMonitor monitor) {
return symbolRecorder.captureSymbols(getTargetModule(module), monitor);
}
@Override
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
TaskMonitor monitor) {
return symbolRecorder.captureSymbols(namespace, monitor);
}
@Override
public List<TargetBreakpointSpecContainer> collectBreakpointContainers(TargetThread thread) {
if (thread == null) {
return objectRecorder.collectTargetSuccessors(target,
TargetBreakpointSpecContainer.class);
}
return objectRecorder.collectTargetSuccessors(thread, TargetBreakpointSpecContainer.class);
}
private class BreakpointConvention {
private final TraceObjectThread thread;
private final TraceObject process;
private BreakpointConvention(TraceObjectThread thread) {
this.thread = thread;
TraceObject object = thread.getObject();
this.process = object
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
.map(p -> p.getFirstParent(object))
.findFirst()
.orElse(null);
}
private boolean appliesTo(TraceObjectBreakpointLocation loc) {
TraceObject object = loc.getObject();
if (object.queryAncestorsInterface(Range.singleton(getSnap()), TraceObjectThread.class)
.anyMatch(t -> t == thread)) {
return true;
}
if (process == null) {
return false;
}
return object
.queryAncestorsTargetInterface(Range.singleton(getSnap()), TargetProcess.class)
.map(p -> p.getFirstParent(object))
.anyMatch(p -> p == process);
}
}
@Override
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
if (thread == null) {
return objectRecorder.collectTargetSuccessors(target, TargetBreakpointLocation.class);
}
BreakpointConvention convention = new BreakpointConvention(
objectRecorder.getTraceInterface(thread, TraceObjectThread.class));
return trace.getObjectManager()
.queryAllInterface(Range.singleton(getSnap()), TraceObjectBreakpointLocation.class)
.filter(convention::appliesTo)
.map(tl -> objectRecorder.getTargetInterface(tl.getObject(),
TargetBreakpointLocation.class))
.collect(Collectors.toList());
}
@Override
public Set<TraceBreakpointKind> getSupportedBreakpointKinds() {
return objectRecorder.collectTargetSuccessors(target, TargetBreakpointSpecContainer.class)
.stream()
.flatMap(c -> c.getSupportedBreakpointKinds().stream())
.map(k -> TraceRecorder.targetToTraceBreakpointKind(k))
.collect(Collectors.toSet());
}
@Override
public boolean isSupportsFocus() {
return objectRecorder.isSupportsFocus;
}
@Override
public TargetObject getFocus() {
return curFocus;
}
@Override
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
for (TargetFocusScope scope : objectRecorder.collectTargetSuccessors(target,
TargetFocusScope.class)) {
if (PathUtils.isAncestor(scope.getPath(), focus.getPath())) {
return scope.requestFocus(focus).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not focus " + focus + ": " + ex.getMessage();
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, msg, ex);
}
return false;
});
}
}
Msg.info(this, "Could not find suitable focus scope for " + focus);
return CompletableFuture.completedFuture(false);
}
// UNUSED?
@Override
public CompletableFuture<Void> flushTransactions() {
return listenerForRecord.tx.flush();
}
protected void fireSnapAdvanced(long key) {
listeners.fire.snapAdvanced(this, key);
}
protected void fireRecordingStopped() {
listeners.fire.recordingStopped(this);
}
// TODO: Deprecate/remove the other callbacks: registerBankMapped, *accessibilityChanged
}

View file

@ -0,0 +1,290 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import com.google.common.collect.Range;
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetFocusScope;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.TraceUniqueObject;
import ghidra.trace.model.target.*;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import utilities.util.IDKeyed;
class ObjectRecorder {
protected final ObjectBasedTraceRecorder recorder;
protected final TraceObjectManager objectManager;
protected final boolean isSupportsFocus;
private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap =
new DualHashBidiMap<>();
protected ObjectRecorder(ObjectBasedTraceRecorder recorder) {
this.recorder = recorder;
this.objectManager = recorder.trace.getObjectManager();
TargetObjectSchema schema = recorder.target.getSchema();
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
try (UndoableTransaction tid =
UndoableTransaction.start(recorder.trace, "Create root", true)) {
objectManager.createRootObject(schema);
}
}
protected TraceObject toTrace(TargetObject targetObject) {
IDKeyed<TraceObject> traceObject = objectMap.get(new IDKeyed<>(targetObject));
return traceObject == null ? null : traceObject.obj;
}
protected TargetObject toTarget(TraceObject traceObject) {
IDKeyed<TargetObject> targetObject = objectMap.getKey(new IDKeyed<>(traceObject));
return targetObject == null ? null : targetObject.obj;
}
protected void recordCreated(long snap, TargetObject object) {
TraceObject traceObject;
if (object.isRoot()) {
// Already have the root object
traceObject = objectManager.getRootObject();
}
else {
traceObject = objectManager
.createObject(TraceObjectKeyPath.of(object.getPath()), Range.atLeast(snap));
}
synchronized (objectMap) {
objectMap.put(new IDKeyed<>(object), new IDKeyed<>(traceObject));
}
}
protected void recordInvalidated(long snap, TargetObject object) {
if (object.isRoot()) {
return;
}
IDKeyed<TraceObject> traceObject;
synchronized (objectMap) {
traceObject = objectMap.remove(new IDKeyed<>(object));
}
if (traceObject == null) {
Msg.error(this, "Unknown object was invalidated: " + object);
return;
}
traceObject.obj.truncateOrDelete(Range.atLeast(snap));
}
protected String encodeEnum(Enum<?> e) {
return e.name();
}
protected String encodeEnumSet(Set<? extends Enum<?>> s) {
return s.stream()
.sorted(Comparator.comparing(Enum::ordinal))
.map(Enum::name)
.collect(Collectors.joining(","));
}
protected Object mapAttribute(Object attribute) {
if (attribute instanceof TargetObject) {
TraceObject traceObject = toTrace((TargetObject) attribute);
if (traceObject == null) {
Msg.error(this, "Unknown object appeared as an attribute: " + attribute);
}
return traceObject;
}
if (attribute instanceof Address) {
Address traceAddress = recorder.memoryMapper.targetToTrace((Address) attribute);
if (traceAddress == null) {
Msg.error(this, "Unmappable address appeared as an attribute: " + attribute);
}
return traceAddress;
}
if (attribute instanceof AddressRange) {
AddressRange traceRange = recorder.memoryMapper.targetToTrace((AddressRange) attribute);
if (traceRange == null) {
Msg.error(this, "Unmappable range appeared as an attribute: " + attribute);
}
return traceRange;
}
if (attribute instanceof TargetAttachKind) {
return encodeEnum((TargetAttachKind) attribute);
}
if (attribute instanceof TargetAttachKindSet) {
return encodeEnumSet((TargetAttachKindSet) attribute);
}
if (attribute instanceof TargetBreakpointKind) {
return encodeEnum((TargetBreakpointKind) attribute);
}
if (attribute instanceof TargetBreakpointKindSet) {
return encodeEnumSet((TargetBreakpointKindSet) attribute);
}
if (attribute instanceof TargetExecutionState) {
return encodeEnum((TargetExecutionState) attribute);
}
if (attribute instanceof TargetParameterMap) {
return "[parameter map not recorded]";
}
if (attribute instanceof TargetStepKind) {
return encodeEnum((TargetStepKind) attribute);
}
if (attribute instanceof TargetStepKindSet) {
return encodeEnumSet((TargetStepKindSet) attribute);
}
return attribute;
}
protected void recordAttributes(long snap, TargetObject object, Collection<String> removed,
Map<String, ?> added) {
TraceObject traceObject;
Map<String, Object> traceAdded = new HashMap<>();
synchronized (objectMap) {
traceObject = toTrace(object);
if (traceObject == null) {
Msg.error(this, "Unknown object had attributes changed: " + object);
return;
}
for (Map.Entry<String, ?> entry : added.entrySet()) {
Object value = mapAttribute(entry.getValue());
if (value == null) {
continue;
}
traceAdded.put(entry.getKey(), value);
}
}
for (Map.Entry<String, Object> entry : traceAdded.entrySet()) {
traceObject.setAttribute(Range.atLeast(snap), entry.getKey(), entry.getValue());
}
}
protected TraceObject mapElement(TargetObject element) {
TraceObject traceObject = toTrace(element);
if (traceObject == null) {
Msg.error(this, "Unknown object appeared as an element: " + element);
return null;
}
return traceObject;
}
protected void recordElements(long snap, TargetObject object, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
TraceObject traceObject;
Map<String, Object> traceAdded = new HashMap<>();
synchronized (objectMap) {
traceObject = toTrace(object);
if (traceObject == null) {
Msg.error(this, "Unknown object had attributes changed: " + object);
return;
}
for (Map.Entry<String, ? extends TargetObject> entry : added.entrySet()) {
Object value = mapElement(entry.getValue());
if (value == null) {
continue;
}
traceAdded.put(entry.getKey(), value);
}
}
for (Map.Entry<String, Object> entry : traceAdded.entrySet()) {
traceObject.setElement(Range.atLeast(snap), entry.getKey(), entry.getValue());
}
}
protected <T extends TargetObject, I extends TraceObjectInterface> T getTargetInterface(
TraceUniqueObject traceUnique, Class<I> traceObjectIf, Class<T> targetObjectIf) {
if (!traceObjectIf.isAssignableFrom(traceUnique.getClass())) {
return null;
}
TraceObject traceObject = traceObjectIf.cast(traceUnique).getObject();
return getTargetInterface(traceObject, targetObjectIf);
}
protected <T extends TargetObject> T getTargetInterface(TraceObject traceObject,
Class<T> targetObjectIf) {
TargetObject targetObject = toTarget(traceObject);
return targetObject == null ? null : targetObject.as(targetObjectIf);
}
protected <I extends TraceObjectInterface> I getTraceInterface(TargetObject targetObject,
Class<I> traceObjectIf) {
TraceObject traceObject = toTrace(targetObject);
return traceObject == null ? null : traceObject.queryInterface(traceObjectIf);
}
protected <T extends TargetObject> T getTargetFrameInterface(TraceThread thread, int frameLevel,
Class<T> targetObjectIf) {
TraceObject object = ((TraceObjectThread) thread).getObject();
PathMatcher matcher = object.getTargetSchema().searchFor(targetObjectIf, false);
PathPattern pattern = matcher.getSingletonPattern();
if (pattern == null) {
return null;
}
PathPredicates applied;
if (pattern.countWildcards() == 0) {
if (frameLevel != 0) {
return null;
}
applied = pattern;
}
else if (pattern.countWildcards() == 1) {
applied = pattern.applyIntKeys(frameLevel);
}
else {
return null;
}
TraceObjectValPath found = object
.getSuccessors(Range.singleton(recorder.getSnap()), applied)
.findAny()
.orElse(null);
if (found == null) {
return null;
}
TraceObject last = found.getLastChild(null);
if (last == null) {
return null;
}
return getTargetInterface(last, targetObjectIf);
}
protected <T extends TargetObject> List<T> collectTargetSuccessors(TargetObject targetSeed,
Class<T> targetIf) {
// TODO: Should this really go through the database?
TraceObject seed = toTrace(targetSeed);
if (seed == null) {
return List.of();
}
return seed.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()), targetIf)
.map(p -> toTarget(p.getLastChild(seed)).as(targetIf))
.collect(Collectors.toList());
}
}

View file

@ -0,0 +1,86 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
public enum RecorderUtils {
INSTANCE;
public AddressSetView quantize(int blockBits, AddressSetView set) {
if (blockBits == 1) {
return set;
}
long blockMask = -1L << blockBits;
AddressSet result = new AddressSet();
// Not terribly efficient, but this is one range most of the time
for (AddressRange range : set) {
AddressSpace space = range.getAddressSpace();
Address min = space.getAddress(range.getMinAddress().getOffset() & blockMask);
Address max = space.getAddress(range.getMaxAddress().getOffset() | ~blockMask);
result.add(new AddressRangeImpl(min, max));
}
return result;
}
public CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(
TraceRecorder recorder, int blockBits, AddressSetView set, TaskMonitor monitor,
boolean returnResult) {
// NOTE: I don't intend to warn about the number of requests.
// They're delivered in serial, and there's a cancel button that works
int blockSize = 1 << blockBits;
int total = 0;
AddressSetView expSet = quantize(blockBits, set)
.intersect(recorder.getTrace()
.getMemoryManager()
.getRegionsAddressSet(recorder.getSnap()));
for (AddressRange r : expSet) {
total += Long.divideUnsigned(r.getLength() + blockSize - 1, blockSize);
}
monitor.initialize(total);
monitor.setMessage("Reading memory");
// TODO: Read blocks in parallel? Probably NO. Tends to overload the connector.
NavigableMap<Address, byte[]> result = returnResult ? new TreeMap<>() : null;
return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> {
AddressRangeChunker blocks = new AddressRangeChunker(r, blockSize);
AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (blk, inner) -> {
// The listener in the recorder will copy to the Trace.
monitor.incrementProgress(1);
CompletableFuture<byte[]> future =
recorder.readMemory(blk.getMinAddress(), (int) blk.getLength());
future.thenAccept(data -> {
if (returnResult) {
result.put(blk.getMinAddress(), data);
}
}).exceptionally(e -> {
Msg.error(this, "Could not read " + blk + ": " + e);
return null; // Continue looping on errors
}).thenApply(__ -> !monitor.isCancelled()).handle(inner::repeatWhile);
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
}).thenApply(__ -> result);
}
}

View file

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.model; package ghidra.app.plugin.core.debug.service.model.record;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
@ -30,12 +32,12 @@ import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException; import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DefaultSymbolRecorder { public class SymbolRecorder {
private DefaultTraceRecorder recorder; private final TraceRecorder recorder;
private Trace trace; private final Trace trace;
public DefaultSymbolRecorder(DefaultTraceRecorder recorder) { public SymbolRecorder(TraceRecorder recorder) {
this.recorder = recorder; this.recorder = recorder;
this.trace = recorder.getTrace(); this.trace = recorder.getTrace();
} }

View file

@ -0,0 +1,66 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
class TimeRecorder {
protected final ObjectBasedTraceRecorder recorder;
protected TraceSnapshot snapshot = null;
protected TimeRecorder(ObjectBasedTraceRecorder recorder) {
this.recorder = recorder;
}
protected TraceSnapshot getSnapshot() {
return snapshot;
}
protected long getSnap() {
return snapshot.getKey();
}
protected synchronized TraceSnapshot doCreateSnapshot(String description,
TraceThread eventThread) {
snapshot = recorder.trace.getTimeManager().createSnapshot(description);
snapshot.setEventThread(eventThread);
return snapshot;
}
protected TraceSnapshot createSnapshot(String description, TraceThread eventThread,
RecorderPermanentTransaction tid) {
TraceSnapshot snapshot;
if (tid != null) {
snapshot = doCreateSnapshot(description, eventThread);
}
else {
try (RecorderPermanentTransaction tid2 =
RecorderPermanentTransaction.start(recorder.trace, description)) {
snapshot = doCreateSnapshot(description, eventThread);
}
}
recorder.fireSnapAdvanced(snapshot.getKey());
return snapshot;
}
protected TraceSnapshot forceSnapshot() {
return createSnapshot("User-forced snapshot", null, null);
}
}

View file

@ -83,7 +83,7 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
this.viewport = trace.getProgramView().getViewport(); this.viewport = trace.getProgramView().getViewport();
this.pc = trace.getBaseLanguage().getProgramCounter(); this.pc = trace.getBaseLanguage().getProgramCounter();
this.pcRange = TraceRegisterUtils.rangeForRegister(pc); this.pcRange = pc == null ? null : TraceRegisterUtils.rangeForRegister(pc);
ClassSearcher.addChangeListener(injectsChangeListener); ClassSearcher.addChangeListener(injectsChangeListener);
updateInjects(); updateInjects();
@ -140,9 +140,9 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
} }
} }
private void stackChanged(TraceStack stack) { private void stackChanged(TraceStack stack, long zero, long snap) {
queueRunnable(() -> { queueRunnable(() -> {
disassembleStackPcVals(stack, stack.getSnap(), null); disassembleStackPcVals(stack, snap, null);
}); });
} }
@ -186,7 +186,7 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
if (space.getFrameLevel() != 0) { if (space.getFrameLevel() != 0) {
return; return;
} }
if (!range.getRange().intersects(pcRange)) { if (pcRange == null || !range.getRange().intersects(pcRange)) {
return; return;
} }
TraceThread thread = space.getThread(); TraceThread thread = space.getThread();
@ -198,18 +198,18 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
}); });
} }
protected void disassembleStackPcVals(TraceStack stack, long memSnap, AddressRange range) { protected void disassembleStackPcVals(TraceStack stack, long snap, AddressRange range) {
TraceStackFrame frame = stack.getFrame(0, false); TraceStackFrame frame = stack.getFrame(0, false);
if (frame == null) { if (frame == null) {
return; return;
} }
Address pcVal = frame.getProgramCounter(); Address pcVal = frame.getProgramCounter(snap);
if (pcVal == null) { if (pcVal == null) {
return; return;
} }
if (range == null || range.contains(pcVal)) { if (range == null || range.contains(pcVal)) {
// NOTE: If non-0 frames are ever used, level should be passed in for injects // NOTE: If non-0 frames are ever used, level should be passed in for injects
disassemble(pcVal, stack.getThread(), memSnap); disassemble(pcVal, stack.getThread(), snap);
} }
} }

View file

@ -21,11 +21,9 @@ import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper; import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper; import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.plugin.core.debug.service.model.TraceEventListener;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.lifecycle.Internal;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
@ -251,11 +249,11 @@ public interface TraceRecorder {
boolean isRegisterBankAccessible(TraceThread thread, int frameLevel); boolean isRegisterBankAccessible(TraceThread thread, int frameLevel);
/** /**
* Get the set of accessible process memory, as viewed in the trace * Get the set of accessible target memory, as viewed in the trace
* *
* @return the computed set * @return the computed set
*/ */
AddressSetView getAccessibleProcessMemory(); AddressSetView getAccessibleMemory();
/** /**
* Capture a target thread's registers. * Capture a target thread's registers.
@ -293,50 +291,49 @@ public interface TraceRecorder {
Map<Register, RegisterValue> values); Map<Register, RegisterValue> values);
/** /**
* Read (and capture) a range of process memory * Read (and capture) a range of target memory
*
* <p>
* This will not quantize the blocks; whereas
* {@link #captureProcessMemory(AddressSetView, TaskMonitor)} will.
* *
* @param start the address to start at, as viewed in the trace * @param start the address to start at, as viewed in the trace
* @param length the number of bytes to read * @param length the number of bytes to read
* @return a future which completes with the read bytes * @return a future which completes with the read bytes
*/ */
CompletableFuture<byte[]> readProcessMemory(Address start, int length); CompletableFuture<byte[]> readMemory(Address start, int length);
/** /**
* Write (and capture) a range of process memory * Write (and capture) a range of target memory
* *
* @param start the address to start at, as viewed in the trace * @param start the address to start at, as viewed in the trace
* @param data the data to write * @param data the data to write
* @return a future which completes when the entire write is complete * @return a future which completes when the entire write is complete
*/ */
CompletableFuture<Void> writeProcessMemory(Address start, byte[] data); CompletableFuture<Void> writeMemory(Address start, byte[] data);
/** /**
* Capture a portion of the target's memory. * Read (and capture) several blocks of target memory
* *
* <p> * <p>
* Though this function returns immediately, the given monitor will be updated in the background * The given address set is quantized to the minimal set of blocks covering the requested set.
* as the task progresses. Thus, the caller should ensure the monitor is visible until the * To capture a precise range, use {@link #readMemory(Address, int)} instead. Though this
* returned future completes. * function returns immediately, the given monitor will be updated in the background as the task
* progresses. Thus, the caller should ensure the monitor is visible until the returned future
* completes.
* *
* <p> * <p>
* This task is relatively error tolerant. If a block or region cannot be captured -- a common * This task is relatively error tolerant. If a block or region cannot be captured -- a common
* occurrence -- the error is logged, but the future may still complete successfully. For large * occurrence -- the error is logged, but the task may still complete "successfully." For large
* captures, it is recommended to set {@code toMap} to false. The recorder will place the bytes * captures, it is recommended to set {@code returnResult} to false. The recorder will capture
* into the trace where they can be retrieved later. For small captures, and where bypassing the * the bytes into the trace where they can be retrieved later. For small captures, and where
* database may offer some advantage, set {@code toMap} to true, and the captured bytes will be * bypassing the database may offer some advantage, set {@code returnResult} to true, and the
* returned in an interval map. Connected intervals may or may not be joined. * captured bytes will be returned in an interval map. Connected intervals may or may not be
* joined.
* *
* @param selection the addresses to capture, as viewed in the trace * @param set the addresses to capture, as viewed in the trace
* @param monitor a monitor for displaying task steps * @param monitor a monitor for displaying task steps
* @param toMap true to return results in a map, false to complete with null * @param returnResult true to complete with results, false to complete with null
* @return a future which completes with the capture results * @return a future which completes when the task finishes
*/ */
CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView selection, CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean toMap); TaskMonitor monitor, boolean returnResult);
/** /**
* Write a variable (memory or register) of the given thread or the process * Write a variable (memory or register) of the given thread or the process
@ -345,7 +342,7 @@ public interface TraceRecorder {
* This is a convenience for writing target memory or registers, based on address. If the given * This is a convenience for writing target memory or registers, based on address. If the given
* address represents a register, this will attempt to map it to a register and write it in the * address represents a register, this will attempt to map it to a register and write it in the
* given thread and frame. If the address is in memory, it will simply delegate to * given thread and frame. If the address is in memory, it will simply delegate to
* {@link #writeProcessMemory(Address, byte[])}. * {@link #writeMemory(Address, byte[])}.
* *
* @param thread the thread. Ignored (may be null) if address is in memory * @param thread the thread. Ignored (may be null) if address is in memory
* @param frameLevel the frame, usually 0. Ignored if address is in memory * @param frameLevel the frame, usually 0. Ignored if address is in memory
@ -356,7 +353,7 @@ public interface TraceRecorder {
default CompletableFuture<Void> writeVariable(TraceThread thread, int frameLevel, default CompletableFuture<Void> writeVariable(TraceThread thread, int frameLevel,
Address address, byte[] data) { Address address, byte[] data) {
if (address.isMemoryAddress()) { if (address.isMemoryAddress()) {
return writeProcessMemory(address, data); return writeMemory(address, data);
} }
if (address.isRegisterAddress()) { if (address.isRegisterAddress()) {
Language lang = getTrace().getBaseLanguage(); Language lang = getTrace().getBaseLanguage();
@ -550,22 +547,6 @@ public interface TraceRecorder {
*/ */
CompletableFuture<Boolean> requestFocus(TargetObject focus); CompletableFuture<Boolean> requestFocus(TargetObject focus);
/**
* Get the internal listener on the model used by the recorder
*
* <p>
* This allows external "hints" to be given to the recorder by manually injecting objects into
* its listener.
*
* <p>
* TODO: This is a bit of a stop-gap until we have a better way of drawing the recorder's
* attention to certain object, or otherwise controlling what it records.
*
* @return the listener
*/
@Internal
TraceEventListener getListenerForRecord();
/** /**
* Wait for pending transactions finish execution. * Wait for pending transactions finish execution.
* *

View file

@ -76,7 +76,7 @@ public class TraceRecorderAsyncPcodeExecutorState
Address addr = space.getAddress(truncateOffset(space, offset)); Address addr = space.getAddress(truncateOffset(space, offset));
AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1)); AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1));
CompletableFuture<NavigableMap<Address, byte[]>> future = CompletableFuture<NavigableMap<Address, byte[]>> future =
recorder.captureProcessMemory(set, TaskMonitor.DUMMY, true); recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true);
return future.thenApply(map -> { return future.thenApply(map -> {
return knitFromResults(map, addr, size); return knitFromResults(map, addr, size);
}); });

View file

@ -106,11 +106,11 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator {
TraceStackFrame frame; TraceStackFrame frame;
frame = stack.getFrame(0, false); frame = stack.getFrame(0, false);
frame.setProgramCounter(tb.addr(0x00404321)); frame.setProgramCounter(Range.all(), tb.addr(0x00404321));
frame = stack.getFrame(1, false); frame = stack.getFrame(1, false);
frame.setProgramCounter(tb.addr(0x00401234)); frame.setProgramCounter(Range.all(), tb.addr(0x00401234));
frame = stack.getFrame(2, false); frame = stack.getFrame(2, false);
frame.setProgramCounter(tb.addr(0x00401001)); frame.setProgramCounter(Range.all(), tb.addr(0x00401001));
} }
root.createFile("trace", tb.trace, TaskMonitor.DUMMY); root.createFile("trace", tb.trace, TaskMonitor.DUMMY);
root.createFile("echo", program, TaskMonitor.DUMMY); root.createFile("echo", program, TaskMonitor.DUMMY);

View file

@ -454,8 +454,13 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
protected static void performEnabledAction(ActionContextProvider provider, protected static void performEnabledAction(ActionContextProvider provider,
DockingActionIf action, boolean wait) { DockingActionIf action, boolean wait) {
ActionContext context = provider.getActionContext(null); ActionContext context = waitForValue(() -> {
waitForCondition(() -> action.isEnabledForContext(context)); ActionContext ctx = provider.getActionContext(null);
if (!action.isEnabledForContext(ctx)) {
return null;
}
return ctx;
});
performAction(action, context, wait); performAction(action, context, wait);
} }

View file

@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.gui.listing; package ghidra.app.plugin.core.debug.gui.listing;
import static ghidra.lifecycle.Unfinished.*; import static ghidra.lifecycle.Unfinished.TODO;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.Color; import java.awt.Color;
@ -1254,8 +1254,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
thread = tb.getOrAddThread("Thread 1", 0); thread = tb.getOrAddThread("Thread 1", 0);
DBTraceStackManager sm = tb.trace.getStackManager(); DBTraceStackManager sm = tb.trace.getStackManager();
TraceStack stack = sm.getStack(thread, 0, true); TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00401234));
stack.getFrame(1, true).setProgramCounter(tb.addr(0x00404321)); stack.getFrame(1, true).setProgramCounter(Range.all(), tb.addr(0x00404321));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
traceManager.activateThread(thread); traceManager.activateThread(thread);
@ -1346,7 +1346,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
thread = tb.getOrAddThread("Thread 1", 0); thread = tb.getOrAddThread("Thread 1", 0);
TraceStack stack = sm.getStack(thread, 0, true); TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00401234));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
traceManager.activateThread(thread); traceManager.activateThread(thread);
@ -1356,7 +1356,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceStack stack = sm.getStack(thread, 0, true); TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00404321)); stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00404321));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);

View file

@ -969,8 +969,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
thread = tb.getOrAddThread("Thread 1", 0); thread = tb.getOrAddThread("Thread 1", 0);
DBTraceStackManager sm = tb.trace.getStackManager(); DBTraceStackManager sm = tb.trace.getStackManager();
TraceStack stack = sm.getStack(thread, 0, true); TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00401234));
stack.getFrame(1, true).setProgramCounter(tb.addr(0x00404321)); stack.getFrame(1, true).setProgramCounter(Range.all(), tb.addr(0x00404321));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
traceManager.activateThread(thread); traceManager.activateThread(thread);
@ -1061,7 +1061,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
thread = tb.getOrAddThread("Thread 1", 0); thread = tb.getOrAddThread("Thread 1", 0);
TraceStack stack = sm.getStack(thread, 0, true); TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00401234));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
traceManager.activateThread(thread); traceManager.activateThread(thread);
@ -1071,7 +1071,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceStack stack = sm.getStack(thread, 0, true); TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00404321)); stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00404321));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);

View file

@ -94,11 +94,11 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
stack.setDepth(2, false); stack.setDepth(2, false);
TraceStackFrame frame = stack.getFrame(0, false); TraceStackFrame frame = stack.getFrame(0, false);
frame.setProgramCounter(tb.addr(0x00400100)); frame.setProgramCounter(Range.all(), tb.addr(0x00400100));
frame.setComment("Hello"); frame.setComment("Hello");
frame = stack.getFrame(1, false); frame = stack.getFrame(1, false);
frame.setProgramCounter(tb.addr(0x00400200)); frame.setProgramCounter(Range.all(), tb.addr(0x00400200));
frame.setComment("World"); frame.setComment("World");
} }
} }

View file

@ -19,7 +19,7 @@ import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetProcess; import ghidra.dbg.target.TargetObject;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@ -27,12 +27,12 @@ public class TestKnownArchDebuggerMappingOpinion implements DebuggerMappingOpini
public static final String ARCH = "test-known-arch"; public static final String ARCH = "test-known-arch";
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!ARCH.equals(env.getArchitecture())) { if (!ARCH.equals(env.getArchitecture())) {
return Set.of(); return Set.of();
} }
return Set.of(new DefaultDebuggerMappingOffer(process, 100, "Offer for test-known-arch", return Set.of(new DefaultDebuggerMappingOffer(target, 100, "Offer for test-known-arch",
new LanguageID(DebuggerModelServiceTest.LANGID_TOYBE64), new CompilerSpecID("default"), new LanguageID(DebuggerModelServiceTest.LANGID_TOYBE64), new CompilerSpecID("default"),
Set.of())); Set.of()));
} }

View file

@ -0,0 +1,565 @@
/* ###
* 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.app.plugin.core.debug.service.model.record;
import static org.hamcrest.Matchers.isOneOf;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import org.junit.Test;
import com.google.common.collect.Range;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.model.*;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.*;
import ghidra.trace.model.thread.*;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.task.TaskMonitor;
public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
DebuggerMappingOpinion opinion = new ObjectBasedDebuggerMappingOpinion();
TraceRecorder recorder;
TraceBreakpointManager breaks;
TraceObjectManager objects;
TraceMemoryManager memory;
TraceModuleManager modules;
TraceThreadManager threads;
TraceTimeManager time;
protected void startRecording() throws Exception {
createTestModel();
TargetObject target = mb.testModel.session;
DebuggerTargetTraceMapper mapper = Unique.assertOne(opinion.getOffers(target, true)).take();
recorder = modelService.recordTarget(mb.testModel.session, mapper, ActionSource.MANUAL);
useTrace(recorder.getTrace());
breaks = tb.trace.getBreakpointManager();
objects = tb.trace.getObjectManager();
memory = tb.trace.getMemoryManager();
modules = tb.trace.getModuleManager();
threads = tb.trace.getThreadManager();
time = tb.trace.getTimeManager();
}
protected void dumpValues(TraceObject obj) {
System.err.println("Values of " + obj);
for (TraceObjectValue val : obj.getValues()) {
System.err.println(" " + val.getEntryKey() + " = " + val.getValue());
}
}
protected void dumpValues(TraceObjectInterface obj) {
dumpValues(obj.getObject());
}
protected void dumpObjects() {
System.err.println("All objects:");
for (TraceObject object : objects.getAllObjects()) {
System.err.println(" " + object);
}
}
@Test
public void testRecordBaseSession() throws Throwable {
startRecording();
waitForPass(noExc(() -> {
waitOn(recorder.flushTransactions());
assertEquals(5, objects.getAllObjects().size());
}));
}
@Test
public void testCloseModelStopsRecording() throws Throwable {
startRecording();
waitForPass(() -> assertTrue(recorder.isRecording()));
waitOn(mb.testModel.close());
waitForPass(() -> assertFalse(recorder.isRecording()));
}
@Test
public void testRecordEvents() throws Throwable {
startRecording();
waitForPass(() -> assertEquals(0, recorder.getSnap()));
mb.testModel.fire()
.event(mb.testModel.session, null, TargetEventType.RUNNING, "Test RUNNING",
List.of());
mb.testModel.fire()
.event(mb.testModel.session, null, TargetEventType.STOPPED, "Test STOPPED",
List.of());
waitForPass(() -> {
assertEquals(1, recorder.getSnap());
TraceSnapshot snapshot = time.getSnapshot(1, false);
assertEquals("Test STOPPED", snapshot.getDescription());
});
mb.createTestProcessesAndThreads();
mb.testModel.fire()
.event(mb.testModel.session, mb.testThread1, TargetEventType.STEP_COMPLETED,
"Test STEP", List.of());
waitForPass(() -> {
assertEquals(2, recorder.getSnap());
TraceSnapshot snapshot = time.getSnapshot(2, false);
assertEquals("Test STEP", snapshot.getDescription());
TraceThread thread = recorder.getTraceThread(mb.testThread1);
assertEquals(thread, snapshot.getEventThread());
});
}
@Test
public void testRecordThreads() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
waitForPass(noExc(() -> {
waitOn(recorder.flushTransactions());
assertEquals(4, threads.getAllThreads().size());
TraceThread thread = recorder.getTraceThread(mb.testThread1);
assertEquals(mb.testThread1, recorder.getTargetThread(thread));
assertEquals(TargetExecutionState.STOPPED,
recorder.getTargetThreadState(mb.testThread1));
assertEquals(TargetExecutionState.STOPPED, recorder.getTargetThreadState(thread));
}));
assertEquals(Set.of(mb.testThread1, mb.testThread2, mb.testThread3, mb.testThread4),
recorder.getLiveTargetThreads());
}
@Test
public void testRecordThreadNameReuse() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
TraceThread thread1a = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
recorder.forceSnapshot();
mb.testProcess1.threads.removeThreads(mb.testThread1);
waitForPass(() -> assertEquals(Range.singleton(0L), thread1a.getLifespan()));
assertNull(recorder.getTraceThread(mb.testThread1));
recorder.forceSnapshot();
mb.testThread1 = mb.testProcess1.addThread(1);
TraceThread thread1b = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
assertNotSame(thread1a, thread1b);
}
@Test
public void testRecordMemoryRegion() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
TestTargetMemoryRegion text =
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "wrx");
waitForPass(() -> {
TraceMemoryRegion region = Unique.assertOne(memory.getAllRegions());
assertEquals("[exe:.text]", region.getName());
assertEquals("Processes[1].Memory[exe:.text]", region.getPath());
assertEquals(tb.range(0x00400000, 0x00400fff), region.getRange());
assertEquals(
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE, TraceMemoryFlag.EXECUTE),
region.getFlags());
assertEquals(region, recorder.getTraceMemoryRegion(text));
assertEquals(text, recorder.getTargetMemoryRegion(region));
});
}
@Test
public void testRecordMemoryAddressSet() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rx");
waitForPass(() -> {
assertEquals(tb.set(tb.range(0x00400000, 0x00400fff)), recorder.getAccessibleMemory());
});
TestTargetMemoryRegion data =
mb.testProcess1.memory.addRegion("exe:.data", mb.rng(0x00401000, 0x00401fff), "wr");
waitForPass(() -> {
assertEquals(tb.set(tb.range(0x00400000, 0x00401fff)), recorder.getAccessibleMemory());
});
mb.testProcess1.memory.removeRegion(data);
waitForPass(() -> {
assertEquals(tb.set(tb.range(0x00400000, 0x00400fff)), recorder.getAccessibleMemory());
});
mb.testModel.removeProcess(mb.testProcess1);
waitForPass(() -> {
assertEquals(tb.set(), recorder.getAccessibleMemory());
});
}
@Test
public void testRecordMemoryBytes() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "wrx");
mb.testProcess1.memory.writeMemory(mb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
byte[] data = new byte[9];
waitForPass(() -> {
memory.getBytes(recorder.getSnap(), tb.addr(0x00400123), ByteBuffer.wrap(data));
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9), data);
});
}
protected void flushAndWait() throws Throwable {
waitOn(mb.testModel.flushEvents());
waitOn(recorder.flushTransactions());
waitForDomainObject(tb.trace);
}
@Test
public void testRecordMemoryInvalidateCacheRequested() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rwx");
waitOn(mb.testProcess1.memory.writeMemory(mb.addr(0x00400123),
mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9)));
flushAndWait();
assertEquals(TraceMemoryState.KNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
mb.testModel.fire().invalidateCacheRequested(mb.testProcess1.memory);
flushAndWait();
assertEquals(TraceMemoryState.UNKNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
waitOn(mb.testProcess1.memory.writeMemory(mb.addr(0x00400123),
mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9)));
flushAndWait();
assertEquals(TraceMemoryState.KNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
mb.testModel.fire()
.event(mb.testModel.session, null, TargetEventType.RUNNING, "Test RUNNING",
List.of());
mb.testModel.fire().invalidateCacheRequested(mb.testProcess1.memory);
flushAndWait();
assertEquals(TraceMemoryState.KNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
mb.testModel.fire()
.event(mb.testModel.session, null, TargetEventType.STOPPED, "Test STOPPED",
List.of());
flushAndWait();
assertEquals(TraceMemoryState.UNKNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
}
@Test
public void testReadMemory() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rwx");
mb.testProcess1.memory.setMemory(tb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
flushAndWait();
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
isOneOf(null, TraceMemoryState.UNKNOWN));
byte[] data = new byte[10];
waitOn(recorder.readMemory(tb.addr(0x00400123), 10));
flushAndWait();
assertEquals(TraceMemoryState.KNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
memory.getBytes(recorder.getSnap(), tb.addr(0x00400123), ByteBuffer.wrap(data));
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), data);
}
protected Map.Entry<TraceAddressSnapRange, TraceMemoryState> stateEntry(long min, long max,
TraceMemoryState state) {
return Map.entry(new ImmutableTraceAddressSnapRange(tb.range(min, max), recorder.getSnap()),
state);
}
@Test
public void testReadMemoryBlocks() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rx");
mb.testProcess1.memory.addRegion("exe:.data", mb.rng(0x00600000, 0x00602fff), "rw");
mb.testProcess1.memory.setMemory(tb.addr(0x00400123), mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
flushAndWait();
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
isOneOf(null, TraceMemoryState.UNKNOWN));
byte[] data = new byte[10];
assertNull(waitOn(recorder.readMemoryBlocks(
tb.set(tb.range(0x00400123, 0x00400123), tb.range(0x00600ffe, 0x00601000)),
TaskMonitor.DUMMY, false)));
flushAndWait();
assertEquals(Set.of(
stateEntry(0x00400000, 0x00400fff, TraceMemoryState.KNOWN),
stateEntry(0x00600000, 0x00601fff, TraceMemoryState.KNOWN)),
Set.copyOf(memory.getStates(recorder.getSnap(), tb.range(0x00400000, 0x00602fff))));
memory.getBytes(recorder.getSnap(), tb.addr(0x00400123), ByteBuffer.wrap(data));
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), data);
}
@Test
public void testWriteMemory() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rwx");
flushAndWait();
assertThat(memory.getState(recorder.getSnap(), tb.addr(0x00400123)),
isOneOf(null, TraceMemoryState.UNKNOWN));
byte[] data = new byte[10];
waitOn(recorder.writeMemory(tb.addr(0x00400123), tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9)));
flushAndWait();
assertEquals(TraceMemoryState.KNOWN,
memory.getState(recorder.getSnap(), tb.addr(0x00400123)));
memory.getBytes(recorder.getSnap(), tb.addr(0x00400123), ByteBuffer.wrap(data));
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), data);
}
@Test
public void testRecordMemoryError() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "rx");
flushAndWait();
assertEquals(Set.of(), Set.copyOf(memory.getStates(recorder.getSnap(), tb.range(0, -1))));
mb.testModel.fire()
.memoryReadError(mb.testProcess1.memory, mb.rng(0x00400123, 0x00400321),
new DebuggerMemoryAccessException("Test error"));
flushAndWait();
// NB, only the first byte of the reported range is marked.
assertEquals(Set.of(stateEntry(0x00400123, 0x00400123, TraceMemoryState.ERROR)),
Set.copyOf(memory.getStates(recorder.getSnap(), tb.range(0, -1))));
}
@Test
public void testRecordMemoryBytes2Processes() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
Address tgtAddr1 = mb.addr(0x00400123);
Address tgtAddr3 = mb.testModel.ram3.getAddress(0x00400123);
mb.testProcess1.memory.addRegion("exe:.text", mb.rng(0x00400000, 0x00400fff), "wrx");
mb.testProcess3.memory.addRegion("exe:.text", new AddressRangeImpl(tgtAddr3, 0x1000),
"rwx");
mb.testProcess1.memory.writeMemory(tgtAddr1, mb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9));
mb.testProcess3.memory.writeMemory(tgtAddr3, mb.arr(11, 12, 13, 14, 15));
Address trcAddr1 = recorder.getMemoryMapper().targetToTrace(tgtAddr1);
Address trcAddr3 = recorder.getMemoryMapper().targetToTrace(tgtAddr3);
byte[] data = new byte[9];
waitForPass(() -> {
memory.getBytes(recorder.getSnap(), trcAddr1, ByteBuffer.wrap(data));
assertArrayEquals(tb.arr(1, 2, 3, 4, 5, 6, 7, 8, 9), data);
memory.getBytes(recorder.getSnap(), trcAddr3, ByteBuffer.wrap(data));
assertArrayEquals(tb.arr(11, 12, 13, 14, 15, 0, 0, 0, 0), data);
});
}
@Test
public void testRecordFocus() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
flushAndWait();
// TODO: Is focus really a concern of the recorder?
assertTrue(recorder.isSupportsFocus());
assertNull(recorder.getFocus());
waitOn(recorder.requestFocus(mb.testThread1));
flushAndWait();
assertEquals(mb.testThread1, recorder.getFocus());
}
@Test
public void testRecordBreakpoint() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
flushAndWait();
assertEquals(Set.of(TraceBreakpointKind.values()),
Set.copyOf(recorder.getSupportedBreakpointKinds()));
waitOn(mb.testProcess1.breaks.placeBreakpoint(mb.rng(0x00400123, 0x00400126),
Set.of(TargetBreakpointKind.SW_EXECUTE)));
flushAndWait();
TraceBreakpoint loc = Unique.assertOne(breaks.getAllBreakpoints());
assertEquals(tb.range(0x00400123, 0x00400126), loc.getRange());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), loc.getKinds());
assertEquals(List.of(),
recorder.collectBreakpointContainers(mb.testThread1));
assertEquals(List.of(mb.testProcess1.breaks, mb.testProcess3.breaks),
recorder.collectBreakpointContainers(null));
TargetBreakpointLocation targetLoc = recorder.getTargetBreakpoint(loc);
assertEquals(loc, recorder.getTraceBreakpoint(targetLoc));
// This must *not* show as applicable to thread1
waitOn(mb.testProcess3.breaks.placeBreakpoint(mb.testModel.ram3.getAddress(0x00400321),
Set.of(TargetBreakpointKind.SW_EXECUTE)));
flushAndWait();
assertEquals(2, breaks.getAllBreakpoints().size());
assertEquals(List.of(targetLoc), recorder.collectBreakpoints(mb.testThread1));
}
@Test
public void testRecordModuleAndSection() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
TestTargetModule targetExe =
mb.testProcess1.modules.addModule("exe", mb.rng(0x00400000, 0x00602fff));
TestTargetSection targetText =
targetExe.addSection(".text", mb.rng(0x00400000, 0x00400fff));
flushAndWait();
TraceModule exe = Unique.assertOne(modules.getAllModules());
assertEquals(exe, recorder.getTraceModule(targetExe));
assertEquals(targetExe, recorder.getTargetModule(exe));
assertEquals("exe", exe.getName());
assertEquals("Processes[1].Modules[exe]", exe.getPath());
assertEquals(tb.range(0x00400000, 0x00602fff), exe.getRange());
TraceSection text = Unique.assertOne(modules.getAllSections());
assertEquals(text, recorder.getTraceSection(targetText));
assertEquals(targetText, recorder.getTargetSection(text));
assertEquals("[.text]", text.getName());
assertEquals("Processes[1].Modules[exe].Sections[.text]", text.getPath());
assertEquals(tb.range(0x00400000, 0x00400fff), text.getRange());
assertEquals(exe, text.getModule());
assertEquals(Set.of(text), Set.copyOf(exe.getSections()));
}
@Test
public void testRecordRegisters() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
// TODO: Adjust schema to reflect merging of container and bank
// TODO: Other bank placements. Will need different schemas, though :/
mb.createTestThreadRegisterBanks();
TestTargetRegisterValue targetPC = new TestTargetRegisterValue(mb.testBank1, "pc", true,
BigInteger.valueOf(0x00400123), 8);
TestTargetRegisterValue targetR0 =
new TestTargetRegisterValue(mb.testBank1, "r0", false, BigInteger.ZERO, 8);
mb.testBank1.setElements(Set.of(targetPC, targetR0), "Test registers");
flushAndWait();
TraceObjectThread thread = (TraceObjectThread) recorder.getTraceThread(mb.testThread1);
assertNotNull(thread);
assertEquals(mb.testBank1, recorder.getTargetRegisterBank(thread, 0));
assertEquals(thread, recorder.getTraceThreadForSuccessor(mb.testBank1));
TraceObject traceBank = thread.getObject()
.querySuccessorsTargetInterface(Range.singleton(recorder.getSnap()),
TargetRegisterBank.class)
.map(p -> p.getLastChild(thread.getObject()))
.findAny()
.orElseThrow();
TraceObject pc = traceBank.getElement(recorder.getSnap(), "pc").getChild();
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0x40, 0x01, 0x023),
(byte[]) pc.getAttribute(recorder.getSnap(), TargetObject.VALUE_ATTRIBUTE_NAME)
.getValue());
TraceObject r0 = traceBank.getElement(recorder.getSnap(), "r0").getChild();
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0, 0),
(byte[]) r0.getAttribute(recorder.getSnap(), TargetObject.VALUE_ATTRIBUTE_NAME)
.getValue());
// TODO: Test interpretation, once mapping scheme is worked out
// TODO: How to annotate values with types, etc?
// TODO: Perhaps byte-array values are allocated in memory-like byte store?
// TODO: Brings endianness into the picture :/
}
@Test
public void testRecordStack() throws Throwable {
startRecording();
mb.createTestProcessesAndThreads();
TestTargetStack targetStack = mb.testThread1.addStack();
// TODO: These "push" semantics don't seem to work as designed....
TestTargetStackFrame targetFrame0 = targetStack.pushFrameNoBank(mb.addr(0x00400123));
TestTargetStackFrame targetFrame1 = targetStack.pushFrameNoBank(mb.addr(0x00400321));
flushAndWait();
TraceObjectThread thread = (TraceObjectThread) recorder.getTraceThread(mb.testThread1);
assertNotNull(thread);
assertEquals(targetFrame0, recorder.getTargetStackFrame(thread, 0));
assertEquals(targetFrame1, recorder.getTargetStackFrame(thread, 1));
assertEquals(thread, recorder.getTraceThreadForSuccessor(targetFrame0));
assertEquals(thread, recorder.getTraceThreadForSuccessor(targetFrame1));
TraceStackFrame frame0 = recorder.getTraceStackFrame(targetFrame0);
TraceStackFrame frame1 = recorder.getTraceStackFrame(targetFrame1);
// TODO: This can use a bank, once we test the frame-has-bank schema
assertEquals(frame0, recorder.getTraceStackFrameForSuccessor(targetFrame0));
assertEquals(frame1, recorder.getTraceStackFrameForSuccessor(targetFrame1));
assertEquals(0, frame0.getLevel());
assertEquals(1, frame1.getLevel());
assertEquals(tb.addr(0x00400123), frame0.getProgramCounter(Long.MAX_VALUE));
assertEquals(tb.addr(0x00400321), frame1.getProgramCounter(Long.MAX_VALUE));
}
}

View file

@ -69,7 +69,17 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
} }
protected void objectInvalidated(TargetObject object) { protected void objectInvalidated(TargetObject object) {
creationLog.remove(object.getPath()); synchronized (lock) {
creationLog.remove(object.getPath());
CompletableFuture.runAsync(() -> {
synchronized (cbLock) {
cbCreationLog.remove(object.getPath());
}
}, clientExecutor).exceptionally(ex -> {
Msg.error(this, "Error updated objectInvalidated before callback");
return null;
});
}
} }
protected void addModelRoot(SpiTargetObject root) { protected void addModelRoot(SpiTargetObject root) {

View file

@ -45,6 +45,10 @@ import ghidra.dbg.target.schema.TargetAttributeType;
@DebuggerTargetObjectIface("BreakpointSpec") @DebuggerTargetObjectIface("BreakpointSpec")
public interface TargetBreakpointSpec extends TargetObject, /*@Transitional*/ TargetTogglable { public interface TargetBreakpointSpec extends TargetObject, /*@Transitional*/ TargetTogglable {
/**
* The kinds of breakpoints understood by Ghidra. Note these must match with those in
* {@code TraceBreakpointKind}.
*/
public enum TargetBreakpointKind { public enum TargetBreakpointKind {
/** /**
* A read breakpoint, likely implemented in hardware * A read breakpoint, likely implemented in hardware

View file

@ -194,6 +194,12 @@ public interface TargetMethod extends TargetObject {
public static TargetParameterMap copyOf(Map<String, ParameterDescription<?>> map) { public static TargetParameterMap copyOf(Map<String, ParameterDescription<?>> map) {
return new ImmutableTargetParameterMap(map); return new ImmutableTargetParameterMap(map);
} }
@SafeVarargs
public static TargetParameterMap ofEntries(
Entry<String, ParameterDescription<?>>... entries) {
return new ImmutableTargetParameterMap(Map.ofEntries(entries));
}
} }
/** /**

View file

@ -17,6 +17,7 @@ package ghidra.dbg.util;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.*;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
@ -278,24 +279,36 @@ public interface PathPredicates {
} }
/** /**
* Substitute wildcards from left to right for the given list of indices * Substitute wildcards from left to right for the given list of keys
* *
* <p> * <p>
* Takes each pattern and substitutes its wildcards for the given indices, starting from the * Takes each pattern and substitutes its wildcards for the given indices, starting from the
* left and working right. This object is unmodified, and the result is returned. * left and working right. This object is unmodified, and the result is returned.
* *
* <p> * <p>
* If there are fewer wildcards in a pattern than given, only the left-most indices are taken. * If there are fewer wildcards in a pattern than given, only the left-most keys are taken. If
* If there are fewer indices than wildcards in a pattern, then the right-most wildcards are * there are fewer keys than wildcards in a pattern, then the right-most wildcards are left in
* left in the resulting pattern. Note while rare, attribute wildcards are substituted, too. * the resulting pattern.
* *
* @param indices the indices to substitute * @param keys the keys to substitute
* @return the pattern or matcher with the applied substitutions * @return the pattern or matcher with the applied substitutions
*/ */
PathPredicates applyKeys(List<String> indices); PathPredicates applyKeys(List<String> keys);
default PathPredicates applyIndices(String... indices) { default PathPredicates applyKeys(Stream<String> keys) {
return applyKeys(List.of(indices)); return applyKeys(keys.collect(Collectors.toList()));
}
default PathPredicates applyKeys(String... keys) {
return applyKeys(List.of(keys));
}
default PathPredicates applyIntKeys(int radix, List<Integer> keys) {
return applyKeys(keys.stream().map(k -> Integer.toString(k, radix)));
}
default PathPredicates applyIntKeys(int... keys) {
return applyKeys(IntStream.of(keys).mapToObj(k -> Integer.toString(k)));
} }
/** /**

View file

@ -20,8 +20,11 @@ import ghidra.dbg.agent.SpiTargetObject;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
public class EmptyDebuggerObjectModel extends AbstractDebuggerObjectModel { public class EmptyDebuggerObjectModel extends AbstractDebuggerObjectModel {
protected final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0); public final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0);
protected final AddressFactory factory = new DefaultAddressFactory(new AddressSpace[] { ram }); public final AddressSpace ram3 =
new GenericAddressSpace("ram3", 64, AddressSpace.TYPE_RAM, 0);
protected final AddressFactory factory =
new DefaultAddressFactory(new AddressSpace[] { ram, ram3 });
@Override @Override
public AddressFactory getAddressFactory() { public AddressFactory getAddressFactory() {

View file

@ -73,7 +73,7 @@ public class TestDebuggerModelBuilder {
testThread1 = testProcess1.addThread(1); testThread1 = testProcess1.addThread(1);
testThread2 = testProcess1.addThread(2); testThread2 = testProcess1.addThread(2);
testProcess3 = testModel.addProcess(3); testProcess3 = testModel.addProcess(3, testModel.ram3);
testThread3 = testProcess3.addThread(3); testThread3 = testProcess3.addThread(3);
testThread4 = testProcess3.addThread(4); testThread4 = testProcess3.addThread(4);
} }

View file

@ -20,9 +20,11 @@ import java.util.concurrent.*;
import org.jdom.JDOMException; import org.jdom.JDOMException;
import ghidra.dbg.DebuggerModelListener;
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.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.program.model.address.AddressSpace;
// TODO: Refactor with other Fake and Test model stuff. // TODO: Refactor with other Fake and Test model stuff.
public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel { public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
@ -89,7 +91,15 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
} }
public TestTargetProcess addProcess(int pid) { public TestTargetProcess addProcess(int pid) {
return session.addProcess(pid); return addProcess(pid, ram);
}
public TestTargetProcess addProcess(int pid, AddressSpace space) {
return session.addProcess(pid, space);
}
public void removeProcess(TestTargetProcess process) {
session.removeProcess(process);
} }
public <T> CompletableFuture<T> future(T t) { public <T> CompletableFuture<T> future(T t) {
@ -110,4 +120,8 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
invalidateCachesCount = 0; invalidateCachesCount = 0;
return result; return result;
} }
public DebuggerModelListener fire() {
return listeners.fire;
}
} }

View file

@ -32,16 +32,24 @@ public class TestMimickJavaLauncher
public TestMimickJavaLauncher(TestTargetObject parent) { public TestMimickJavaLauncher(TestTargetObject parent) {
super(parent, "Java Launcher", "Launcher"); super(parent, "Java Launcher", "Launcher");
setAttributes(List.of(), Map.of(TargetMethod.PARAMETERS_ATTRIBUTE_NAME, Map.of("Suspend", setAttributes(
ParameterDescription.create(Boolean.class, "Suspend", false, true, "Suspend", ""), List.of(), Map.of(TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetParameterMap.ofEntries(
"Quote", ParameterDescription.create(String.class, "Quote", false, "\"", "Quote", ""), Map.entry("Suspend",
"Launcher", ParameterDescription.create(Boolean.class, "Suspend", false, true, "Suspend",
ParameterDescription.create(String.class, "Launcher", false, "java", "Launcher", ""), "")),
"Options", Map.entry("Quote",
ParameterDescription.create(String.class, "Options", false, "", "Options", ""), "Main", ParameterDescription.create(String.class, "Quote", false, "\"", "Quote", "")),
ParameterDescription.create(String.class, "Main", false, "hw.HelloWorld", "Main", ""), Map.entry("Launcher",
"Home", ParameterDescription.create(String.class, "Home", false, ParameterDescription.create(String.class, "Launcher", false, "java", "Launcher",
"/opt/java-11-amazon-corretto", "Home", ""))), "")),
Map.entry("Options",
ParameterDescription.create(String.class, "Options", false, "", "Options", "")),
Map.entry("Main",
ParameterDescription.create(String.class, "Main", false, "hw.HelloWorld",
"Main", "")),
Map.entry("Home",
ParameterDescription.create(String.class, "Home", false,
"/opt/java-11-amazon-corretto", "Home", "")))),
"Initialized"); "Initialized");
} }
@ -52,6 +60,6 @@ public class TestMimickJavaLauncher
@Override @Override
public CompletableFuture<Void> launch(Map<String, ?> args) { public CompletableFuture<Void> launch(Map<String, ?> args) {
return AsyncUtils.NIL; // TODO: Queue and allow to test complete it? return AsyncUtils.NIL; // TODO: Queue and allow test to complete it?
} }
} }

View file

@ -25,8 +25,8 @@ import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
// TODO: Test some other breakpoint conventions: // TODO: Test some other breakpoint conventions:
// A1) 1-1 spec-effective, where spec is effective is breakpoint (DONE) // A1) 1-1 spec-loc, where spec is loc is breakpoint (DONE)
// A2) 1-n spec-effective, where effective are children of spec // A2) 1-n spec-loc, where locs are children of spec
// B1) container per process (DONE) // B1) container per process (DONE)
// B2) container per session // B2) container per session

View file

@ -24,24 +24,25 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetAccessConditioned; import ghidra.dbg.target.TargetAccessConditioned;
import ghidra.dbg.target.TargetMemory; import ghidra.dbg.target.TargetMemory;
import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressRange;
public class TestTargetMemory public class TestTargetMemory
extends DefaultTestTargetObject<TestTargetMemoryRegion, TestTargetProcess> extends DefaultTestTargetObject<TestTargetMemoryRegion, TestTargetProcess>
implements TargetMemory, TargetAccessConditioned { implements TargetMemory, TargetAccessConditioned {
protected final SemisparseByteArray memory = new SemisparseByteArray(); protected final SemisparseByteArray memory = new SemisparseByteArray();
protected final AddressSpace space;
public TestTargetMemory(TestTargetProcess parent) { public TestTargetMemory(TestTargetProcess parent, AddressSpace space) {
super(parent, "Memory", "Memory"); super(parent, "Memory", "Memory");
this.space = space;
changeAttributes(List.of(), Map.of( changeAttributes(List.of(), Map.of(
ACCESSIBLE_ATTRIBUTE_NAME, true // ACCESSIBLE_ATTRIBUTE_NAME, true //
), "Initialized"); ), "Initialized");
} }
public void getMemory(Address address, byte[] data) { public void getMemory(Address address, byte[] data) {
assertEquals(getModel().ram, address.getAddressSpace()); assertEquals(space, address.getAddressSpace());
memory.getData(address.getOffset(), data); memory.getData(address.getOffset(), data);
} }
@ -57,7 +58,7 @@ public class TestTargetMemory
} }
public void setMemory(Address address, byte[] data) { public void setMemory(Address address, byte[] data) {
assertEquals(getModel().ram, address.getAddressSpace()); assertEquals(space, address.getAddressSpace());
memory.putData(address.getOffset(), data); memory.putData(address.getOffset(), data);
} }
@ -77,6 +78,11 @@ public class TestTargetMemory
return region; return region;
} }
public void removeRegion(TestTargetMemoryRegion region) {
changeElements(List.of(region.getIndex()), List.of(),
"Remove test region: " + region.getRange());
}
public boolean setAccessible(boolean accessible) { public boolean setAccessible(boolean accessible) {
boolean old = isAccessible(); boolean old = isAccessible();
changeAttributes(List.of(), Map.ofEntries( changeAttributes(List.of(), Map.ofEntries(

View file

@ -22,6 +22,7 @@ import ghidra.dbg.target.TargetAggregate;
import ghidra.dbg.target.TargetProcess; import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSpace;
public class TestTargetProcess extends public class TestTargetProcess extends
DefaultTestTargetObject<TestTargetProcessContainer, TestTargetObject> DefaultTestTargetObject<TestTargetProcessContainer, TestTargetObject>
@ -32,10 +33,10 @@ public class TestTargetProcess extends
public final TestTargetRegisterContainer regs; public final TestTargetRegisterContainer regs;
public final TestTargetThreadContainer threads; public final TestTargetThreadContainer threads;
public TestTargetProcess(DefaultTestTargetObject<?, ?> parent, int pid) { public TestTargetProcess(DefaultTestTargetObject<?, ?> parent, int pid, AddressSpace space) {
super(parent, PathUtils.makeKey(PathUtils.makeIndex(pid)), "Process"); super(parent, PathUtils.makeKey(PathUtils.makeIndex(pid)), "Process");
breaks = new TestTargetBreakpointContainer(this); breaks = new TestTargetBreakpointContainer(this);
memory = new TestTargetMemory(this); memory = new TestTargetMemory(this, space);
modules = new TestTargetModuleContainer(this); modules = new TestTargetModuleContainer(this);
regs = new TestTargetRegisterContainer(this); regs = new TestTargetRegisterContainer(this);
threads = new TestTargetThreadContainer(this); threads = new TestTargetThreadContainer(this);

View file

@ -18,15 +18,22 @@ package ghidra.dbg.model;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import ghidra.program.model.address.AddressSpace;
public class TestTargetProcessContainer public class TestTargetProcessContainer
extends DefaultTestTargetObject<TestTargetProcess, TestTargetSession> { extends DefaultTestTargetObject<TestTargetProcess, TestTargetSession> {
public TestTargetProcessContainer(TestTargetSession parent) { public TestTargetProcessContainer(TestTargetSession parent) {
super(parent, "Processes", "Processes"); super(parent, "Processes", "Processes");
} }
public TestTargetProcess addProcess(int pid) { public TestTargetProcess addProcess(int pid, AddressSpace space) {
TestTargetProcess proc = new TestTargetProcess(this, pid); TestTargetProcess proc = new TestTargetProcess(this, pid, space);
changeElements(List.of(), List.of(proc), Map.of(), "Test Process Added"); changeElements(List.of(), List.of(proc), Map.of(), "Test Process Added");
return proc; return proc;
} }
public void removeProcess(TestTargetProcess process) {
changeElements(List.of(process.getIndex()), List.of(),
"Test Process Removed: " + process.getPid());
}
} }

View file

@ -0,0 +1,65 @@
/* ###
* 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.model;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.util.PathUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
public class TestTargetRegisterValue
extends DefaultTestTargetObject<TestTargetObject, AbstractTestTargetRegisterBank<?>>
implements TargetRegister {
public static TestTargetRegisterValue fromRegisterValue(
AbstractTestTargetRegisterBank<?> parent, RegisterValue rv) {
Register register = rv.getRegister();
return new TestTargetRegisterValue(parent, PathUtils.makeKey(register.getName()),
register.isProgramCounter(), rv.getUnsignedValue(), register.getBitLength() + 7 / 8);
}
protected final int byteLength;
protected final boolean isPC;
public TestTargetRegisterValue(AbstractTestTargetRegisterBank<?> parent, String name,
boolean isPC, BigInteger value, int byteLength) {
this(parent, name, isPC, Utils.bigIntegerToBytes(value, byteLength, true));
}
public TestTargetRegisterValue(AbstractTestTargetRegisterBank<?> parent, String name,
boolean isPC, byte[] value) {
super(parent, name, "Register");
this.byteLength = value.length;
this.isPC = isPC;
changeAttributes(List.of(), Map.of(
CONTAINER_ATTRIBUTE_NAME, parent,
LENGTH_ATTRIBUTE_NAME, byteLength,
VALUE_ATTRIBUTE_NAME, value //
), "Initialized");
}
public void setValue(BigInteger value) {
changeAttributes(List.of(), Map.of(
VALUE_ATTRIBUTE_NAME, Utils.bigIntegerToBytes(value, byteLength, true) //
), "Set value");
}
}

View file

@ -17,10 +17,12 @@ package ghidra.dbg.model;
import java.util.List; import java.util.List;
import ghidra.dbg.target.TargetSectionContainer;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
public class TestTargetSectionContainer public class TestTargetSectionContainer
extends DefaultTestTargetObject<TestTargetSection, TestTargetModule> { extends DefaultTestTargetObject<TestTargetSection, TestTargetModule>
implements TargetSectionContainer {
public TestTargetSectionContainer(TestTargetModule parent) { public TestTargetSectionContainer(TestTargetModule parent) {
super(parent, "Sections", "SectionContainer"); super(parent, "Sections", "SectionContainer");

View file

@ -25,6 +25,7 @@ import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema; import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.program.model.address.AddressSpace;
public class TestTargetSession extends DefaultTargetModelRoot public class TestTargetSession extends DefaultTargetModelRoot
implements TestTargetObject, TargetFocusScope, TargetEventScope, TargetLauncher { implements TestTargetObject, TargetFocusScope, TargetEventScope, TargetLauncher {
@ -51,8 +52,12 @@ public class TestTargetSession extends DefaultTargetModelRoot
"Initialized"); "Initialized");
} }
public TestTargetProcess addProcess(int pid) { public TestTargetProcess addProcess(int pid, AddressSpace space) {
return processes.addProcess(pid); return processes.addProcess(pid, space);
}
public void removeProcess(TestTargetProcess process) {
processes.removeProcess(process);
} }
@Override @Override

View file

@ -38,6 +38,10 @@ public class TestTargetStack extends DefaultTestTargetObject<TestTargetStackFram
return frame; return frame;
} }
public TestTargetStackFrameNoRegisterBank pushFrameNoBank(Address pc) {
return pushFrame(new TestTargetStackFrameNoRegisterBank(this, elements.size(), pc));
}
/** /**
* Push a new frame onto the stack where the register bank is a child attribute * Push a new frame onto the stack where the register bank is a child attribute
* *

View file

@ -0,0 +1,51 @@
/* ###
* 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.model;
import java.util.List;
import java.util.Map;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
public class TestTargetStackFrameNoRegisterBank extends
DefaultTestTargetObject<TestTargetObject, TestTargetStack> implements TestTargetStackFrame {
protected Address pc;
public TestTargetStackFrameNoRegisterBank(TestTargetStack parent, int level, Address pc) {
super(parent, PathUtils.makeKey(PathUtils.makeIndex(level)), "Frame");
changeAttributes(List.of(), Map.of(
PC_ATTRIBUTE_NAME, this.pc = pc //
), "Initialized");
}
@Override
public void setFromFrame(TestTargetStackFrame frame) {
TestTargetStackFrameNoRegisterBank that = (TestTargetStackFrameNoRegisterBank) frame;
this.pc = that.pc;
changeAttributes(List.of(), Map.of(
PC_ATTRIBUTE_NAME, this.pc //
), "Copied frame");
}
public void setPC(Address pc) {
this.pc = pc;
changeAttributes(List.of(), Map.of(
PC_ATTRIBUTE_NAME, this.pc //
), "PC Updated");
}
}

View file

@ -34,7 +34,7 @@ public class TestTargetThreadContainer
return thread; return thread;
} }
public void removeThreads(TestTargetThread[] threads) { public void removeThreads(TestTargetThread... threads) {
List<String> indices = List<String> indices =
Stream.of(threads).map(TargetObject::getIndex).collect(Collectors.toList()); Stream.of(threads).map(TargetObject::getIndex).collect(Collectors.toList());
changeElements(indices, List.of(), Map.of(), "Test Threads Removed"); changeElements(indices, List.of(), Map.of(), "Test Threads Removed");

View file

@ -211,7 +211,7 @@ public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestU
PathPredicates matcher = model.getRootSchema() PathPredicates matcher = model.getRootSchema()
.getSuccessorSchema(seedPath) .getSuccessorSchema(seedPath)
.searchFor(cls, seedPath, true) .searchFor(cls, seedPath, true)
.applyIndices(index); .applyKeys(index);
if (matcher.isEmpty()) { if (matcher.isEmpty()) {
return null; return null;
} }

View file

@ -10,17 +10,16 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" /> <attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" /> <attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" /> <attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
<attribute name="Environment" schema="Environment" /> <attribute name="Environment" schema="Environment" />
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" /> <attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
<attribute name="Interpreter" schema="Interpreter" /> <attribute name="Interpreter" schema="Interpreter" />
<attribute name="JavaMimic" schema="Launcher" /> <attribute name="JavaMimic" schema="Launcher" />
<attribute schema="ANY" /> <attribute schema="ANY" />
</schema> </schema>
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER"> <schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
@ -29,7 +28,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -46,7 +44,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -60,7 +57,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -77,7 +73,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -92,7 +87,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -102,12 +96,12 @@
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" /> <attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="Registers" schema="RegisterContainer" required="yes" fixed="yes" /> <attribute name="Registers" schema="RegisterContainer" required="yes" fixed="yes" />
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" /> <attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Devices" schema="OBJECT" /> <attribute name="Devices" schema="OBJECT" />
<attribute name="Environment" schema="OBJECT" /> <attribute name="Environment" schema="OBJECT" />
<attribute name="Handle" schema="OBJECT" /> <attribute name="Handle" schema="OBJECT" />
<attribute name="Id" schema="OBJECT" /> <attribute name="Id" schema="OBJECT" />
<attribute name="Io" schema="OBJECT" /> <attribute name="Io" schema="OBJECT" />
<attribute name="Name" schema="OBJECT" /> <attribute name="Name" schema="OBJECT" />
<attribute schema="ANY" /> <attribute schema="ANY" />
</schema> </schema>
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER"> <schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
@ -116,7 +110,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -132,7 +125,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -146,7 +138,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -164,7 +155,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -188,7 +178,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -198,9 +187,9 @@
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" /> <attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" /> <attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
<attribute name="RegisterBank" schema="RegisterBank" fixed="yes" /> <attribute name="RegisterBank" schema="RegisterBank" fixed="yes" />
<attribute name="Environment" schema="OBJECT" /> <attribute name="Environment" schema="OBJECT" />
<attribute name="Id" schema="OBJECT" /> <attribute name="Id" schema="OBJECT" />
<attribute name="Name" schema="OBJECT" /> <attribute name="Name" schema="OBJECT" />
<attribute name="_arch" schema="STRING" /> <attribute name="_arch" schema="STRING" />
<attribute schema="ANY" /> <attribute schema="ANY" />
</schema> </schema>
@ -212,7 +201,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -223,6 +211,33 @@
<attribute name="Len" schema="OBJECT" /> <attribute name="Len" schema="OBJECT" />
<attribute name="Name" schema="OBJECT" /> <attribute name="Name" schema="OBJECT" />
<attribute name="Size" schema="OBJECT" /> <attribute name="Size" schema="OBJECT" />
<attribute name="Sections" schema="SectionContainer" />
<attribute schema="ANY" />
</schema>
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Section" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
<interface name="Section" />
<element schema="VOID" />
<attribute name="_module" schema="Module" hidden="yes" />
<attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" /> <attribute schema="ANY" />
</schema> </schema>
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER"> <schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
@ -233,7 +248,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -246,7 +260,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -260,7 +273,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -275,7 +287,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -287,7 +298,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -310,7 +320,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -328,7 +337,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -342,18 +350,17 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" /> <attribute name="_order" schema="INT" hidden="yes" />
<attribute name="FrameNumber" schema="OBJECT" /> <attribute name="FrameNumber" schema="OBJECT" />
<attribute name="FrameOffset" schema="OBJECT" /> <attribute name="FrameOffset" schema="OBJECT" />
<attribute name="FuncTableEntry" schema="OBJECT" /> <attribute name="FuncTableEntry" schema="OBJECT" />
<attribute name="InstructionOffset" schema="OBJECT" /> <attribute name="InstructionOffset" schema="OBJECT" />
<attribute name="ReturnOffset" schema="OBJECT" /> <attribute name="ReturnOffset" schema="OBJECT" />
<attribute name="StackOffset" schema="OBJECT" /> <attribute name="StackOffset" schema="OBJECT" />
<attribute name="Virtual" schema="OBJECT" /> <attribute name="Virtual" schema="OBJECT" />
<attribute schema="ANY" /> <attribute schema="ANY" />
</schema> </schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER"> <schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
@ -365,7 +372,6 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ADDRESS" hidden="yes" /> <attribute name="_value" schema="ADDRESS" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
@ -380,13 +386,10 @@
<attribute name="_modified" schema="BOOL" hidden="yes" /> <attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" /> <attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_update_mode" schema="UPDATE_MODE" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" /> <attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" required="yes" hidden="yes" /> <attribute name="_value" schema="ANY" required="yes" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" /> <attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" /> <attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" /> <attribute schema="VOID" />
</schema> </schema>
<schema name="UPDATE_MODE" elementResync="NEVER" attributeResync="NEVER">
</schema>
</context> </context>

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.pcode.exec.trace; package ghidra.pcode.exec.trace;
import com.google.common.collect.Range;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.AbstractPcodeEmulator; import ghidra.pcode.emu.AbstractPcodeEmulator;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
@ -96,7 +98,8 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
ls.writeCacheDown(trace, destSnap, traceThread, 0); ls.writeCacheDown(trace, destSnap, traceThread, 0);
if (synthesizeStacks) { if (synthesizeStacks) {
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true); TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
stack.getFrame(0, true).setProgramCounter(emuThread.getCounter()); stack.getFrame(0, true)
.setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter());
} }
} }
} }

View file

@ -268,7 +268,9 @@ 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.queryAncestorsInterface(getLifespan(), TraceObjectBreakpointSpec.class) return object
.queryCanonicalAncestorsInterface(getLifespan(),
TraceObjectBreakpointSpec.class)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.trace.database.breakpoint; package ghidra.trace.database.breakpoint;
import java.util.*; import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.common.collect.Range; import com.google.common.collect.Range;
@ -31,6 +32,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceBreakpointChangeType; import ghidra.trace.model.Trace.TraceBreakpointChangeType;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
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;
@ -46,7 +48,7 @@ public class DBTraceObjectBreakpointSpec
implements TraceObjectBreakpointSpec, DBTraceObjectInterface { implements TraceObjectBreakpointSpec, DBTraceObjectInterface {
private final DBTraceObject object; private final DBTraceObject object;
private Set<TraceBreakpointKind> kinds; private TraceBreakpointKindSet kinds;
public DBTraceObjectBreakpointSpec(DBTraceObject object) { public DBTraceObjectBreakpointSpec(DBTraceObject object) {
this.object = object; this.object = object;
@ -147,28 +149,25 @@ public class DBTraceObjectBreakpointSpec
// 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(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
kinds.stream().map(k -> k.name()).collect(Collectors.joining(","))); TraceBreakpointKindSet.encode(kinds));
this.kinds = Set.copyOf(kinds); this.kinds = TraceBreakpointKindSet.copyOf(kinds);
} }
} }
@Override @Override
public Set<TraceBreakpointKind> getKinds() { public Set<TraceBreakpointKind> getKinds() {
Set<TraceBreakpointKind> result = new HashSet<>();
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, String.class, null); TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, String.class, null);
if (kindsStr == null) { if (kindsStr == null) {
return kinds; return kinds;
} }
for (String name : kindsStr.split(",")) { try {
try { return kinds = TraceBreakpointKindSet.decode(kindsStr, true);
result.add(TraceBreakpointKind.valueOf(name)); }
} catch (IllegalArgumentException e) {
catch (IllegalArgumentException e) { Msg.warn(this, "Unrecognized breakpoint kind(s) in trace database: " + e);
Msg.warn(this, "Could not decode breakpoint kind from trace database: " + name); return kinds = TraceBreakpointKindSet.decode(kindsStr, false);
}
} }
return kinds = result;
} }
@Override @Override
@ -210,13 +209,17 @@ public class DBTraceObjectBreakpointSpec
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
TraceChangeRecord<TraceObjectValue, Object> cast = TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec); TraceObjectChangeType.VALUE_CHANGED.cast(rec);
String key = cast.getAffectedObject().getEntryKey(); TraceObjectValue affected = cast.getAffectedObject();
String key = affected.getEntryKey();
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) || boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key); TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key);
if (!applies) { if (!applies) {
return null; return null;
} }
assert cast.getAffectedObject().getParent() == object; assert affected.getParent() == object;
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return null; // Incomplete object
}
for (TraceObjectBreakpointLocation loc : getLocations()) { for (TraceObjectBreakpointLocation loc : getLocations()) {
DBTraceObjectBreakpointLocation dbLoc = (DBTraceObjectBreakpointLocation) loc; DBTraceObjectBreakpointLocation dbLoc = (DBTraceObjectBreakpointLocation) loc;
TraceAddressSpace space = dbLoc.getTraceAddressSpace(); TraceAddressSpace space = dbLoc.getTraceAddressSpace();

View file

@ -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.*; import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils; 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;
@ -71,6 +72,27 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
protected TraceChangeType<TraceMemoryRegion, Void> getDeletedType() { protected TraceChangeType<TraceMemoryRegion, Void> getDeletedType() {
return TraceMemoryRegionChangeType.DELETED; return TraceMemoryRegionChangeType.DELETED;
} }
@Override
protected void emitExtraAdded() {
updateViewsAdded();
}
@Override
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
updateViewsLifespanChanged(oldLifespan, newLifespan);
}
@Override
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
Object newValue) {
updateViewsValueChanged(lifespan, key, oldValue, newValue);
}
@Override
protected void emitExtraDeleted() {
updateViewsDeleted();
}
} }
private final DBTraceObject object; private final DBTraceObject object;
@ -96,7 +118,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
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); object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
object.getTrace().updateViewsChangeRegionBlockName(this);
} }
} }
@ -116,7 +137,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
} }
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object, TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
newLifespan); newLifespan);
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
} }
} }
@ -157,7 +177,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
return; return;
} }
object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange); object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
object.getTrace().updateViewsChangeRegionBlockRange(this, oldRange, newRange);
} }
} }
@ -229,7 +248,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
Boolean val = flags.contains(flag) ? true : null; Boolean val = flags.contains(flag) ? true : null;
object.setValue(lifespan, keyForFlag(flag), val); object.setValue(lifespan, keyForFlag(flag), val);
} }
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
} }
} }
@ -239,7 +257,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
for (TraceMemoryFlag flag : flags) { for (TraceMemoryFlag flag : flags) {
object.setValue(lifespan, keyForFlag(flag), true); object.setValue(lifespan, keyForFlag(flag), true);
} }
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
} }
} }
@ -249,7 +266,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
for (TraceMemoryFlag flag : flags) { for (TraceMemoryFlag flag : flags) {
object.setValue(lifespan, keyForFlag(flag), null); object.setValue(lifespan, keyForFlag(flag), null);
} }
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
} }
} }
@ -296,7 +312,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
public void delete() { public void delete() {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.deleteTree(); object.deleteTree();
object.getTrace().updateViewsDeleteRegionBlock(this);
} }
} }
@ -309,4 +324,35 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) { public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
return translator.translate(rec); return translator.translate(rec);
} }
protected void updateViewsAdded() {
object.getTrace().updateViewsAddRegionBlock(this);
}
protected void updateViewsLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
}
protected void updateViewsValueChanged(Range<Long> lifespan, String key, Object oldValue,
Object newValue) {
DBTrace trace = object.getTrace();
switch (key) {
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockRange(this,
(AddressRange) oldValue, (AddressRange) newValue);
return;
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockName(this);
return;
case TargetMemoryRegion.READABLE_ATTRIBUTE_NAME:
case TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME:
case TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockFlags(this, lifespan);
return;
}
}
protected void updateViewsDeleted() {
object.getTrace().updateViewsDeleteRegionBlock(this);
}
} }

View file

@ -23,13 +23,15 @@ import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.trace.database.target.DBTraceObject; import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface;
import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.memory.TraceObjectRegister; import ghidra.trace.model.memory.TraceObjectRegister;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.*;
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.util.TraceChangeRecord;
public class DBTraceObjectRegister implements TraceObjectRegister { public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObjectInterface {
private final DBTraceObject object; private final DBTraceObject object;
public DBTraceObjectRegister(DBTraceObject object) { public DBTraceObjectRegister(DBTraceObject object) {
@ -102,4 +104,10 @@ public class DBTraceObjectRegister implements TraceObjectRegister {
return TraceMemoryState.values()[TraceObjectInterfaceUtils.getValue(object, snap, KEY_STATE, return TraceMemoryState.values()[TraceObjectInterfaceUtils.getValue(object, snap, KEY_STATE,
Integer.class, TraceMemoryState.UNKNOWN.ordinal())]; Integer.class, TraceMemoryState.UNKNOWN.ordinal())];
} }
@Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
// TODO: Once we decide how to map registers....
return null;
}
} }

View file

@ -107,14 +107,14 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
@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); object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
} }
} }
@Override @Override
public String getName() { public String getName() {
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(), return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
} }
@Override @Override

View file

@ -24,7 +24,8 @@ import com.google.common.collect.Range;
import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.*; import ghidra.dbg.util.*;
import ghidra.trace.database.target.*; import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface;
import ghidra.trace.model.Trace.TraceStackChangeType; import ghidra.trace.model.Trace.TraceStackChangeType;
import ghidra.trace.model.stack.*; import ghidra.trace.model.stack.*;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
@ -52,7 +53,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
} }
@Override @Override
protected TraceChangeType<TraceStack, Void> getChangedType() { protected TraceChangeType<TraceStack, ?> getChangedType() {
return TraceStackChangeType.CHANGED; return TraceStackChangeType.CHANGED;
} }
@ -106,7 +107,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
PathMatcher matcher = object.getTargetSchema().searchFor(TargetStackFrame.class, true); PathMatcher matcher = object.getTargetSchema().searchFor(TargetStackFrame.class, true);
List<String> relKeyList = List<String> relKeyList =
matcher.applyIndices(PathUtils.makeIndex(level)).getSingletonPath(); matcher.applyKeys(PathUtils.makeIndex(level)).getSingletonPath();
if (relKeyList == null) { if (relKeyList == null) {
throw new IllegalStateException("Could not determine where to create new frame"); throw new IllegalStateException("Could not determine where to create new frame");
} }
@ -117,8 +118,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
} }
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) { protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
// TODO: All attributes or just those known to StackFrame? // TODO: All attributes within a given span, intersected to that span?
to.setProgramCounter(from.getProgramCounter()); to.setProgramCounter(to.getObject().getLifespan(),
from.getProgramCounter(from.getObject().getMaxSnap()));
} }
protected void shiftFrameAttributes(int from, int to, int count, protected void shiftFrameAttributes(int from, int to, int count,
@ -141,12 +143,13 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) { 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(null); frame.setProgramCounter(frame.getObject().getLifespan(), null);
} }
} }
@Override @Override
public void setDepth(int depth, boolean atInner) { public void setDepth(int depth, boolean atInner) {
// 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().collect(Collectors.toCollection(ArrayList::new));
@ -179,7 +182,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
protected TraceStackFrame doGetFrame(int level) { protected TraceStackFrame doGetFrame(int level) {
TargetObjectSchema schema = object.getTargetSchema(); TargetObjectSchema schema = object.getTargetSchema();
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true); PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
matcher = matcher.applyIndices(PathUtils.makeIndex(level)); matcher = matcher.applyKeys(PathUtils.makeIndex(level));
return object.getSuccessors(object.getLifespan(), matcher) return object.getSuccessors(object.getLifespan(), matcher)
.findAny() .findAny()
.map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class)) .map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class))

View file

@ -17,6 +17,8 @@ package ghidra.trace.database.stack;
import java.util.List; import java.util.List;
import com.google.common.collect.Range;
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;
@ -72,23 +74,25 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
} }
@Override @Override
public Address getProgramCounter() { public Address getProgramCounter(long snap) {
return TraceObjectInterfaceUtils.getValue(object, object.getMaxSnap(), return TraceObjectInterfaceUtils.getValue(object, snap,
TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null); TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null);
} }
@Override @Override
public void setProgramCounter(Address pc) { public void setProgramCounter(Range<Long> span, Address pc) {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
if (pc == Address.NO_ADDRESS) { if (pc == Address.NO_ADDRESS) {
pc = null; pc = null;
} }
object.setValue(object.getLifespan(), TargetStackFrame.PC_ATTRIBUTE_NAME, pc); object.setValue(object.getLifespan().intersection(span),
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
} }
} }
@Override @Override
public String getComment() { public String getComment() {
// 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
@ -101,7 +105,7 @@ 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(); Address pc = getProgramCounter(object.getMaxSnap());
return pc == null ? null return pc == null ? null
: object.getTrace() : object.getTrace()
.getCommentAdapter() .getCommentAdapter()
@ -111,11 +115,12 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
@Override @Override
public void setComment(String comment) { public void setComment(String comment) {
// TODO: Do I need to add a span argument?
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
object.getTrace() object.getTrace()
.getCommentAdapter() .getCommentAdapter()
.setComment(object.getLifespan(), getProgramCounter(), CodeUnit.EOL_COMMENT, .setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()),
comment); CodeUnit.EOL_COMMENT, comment);
} }
} }
@ -124,23 +129,38 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
return object; return object;
} }
protected boolean isPcChange(TraceChangeRecord<?, ?> rec) { protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
TraceChangeRecord<TraceObjectValue, Object> cast = TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec); TraceObjectChangeType.VALUE_CHANGED.cast(rec);
return TargetStackFrame.PC_ATTRIBUTE_NAME.equals(cast.getAffectedObject().getEntryKey()); TraceObjectValue affected = cast.getAffectedObject();
assert affected.getParent() == object;
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
return false;
}
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return false;
}
return true;
}
protected long snapFor(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap();
}
return object.getMinSnap();
} }
@Override @Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) { public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType() || if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() ||
rec.getEventType() == TraceObjectChangeType.DELETED.getType() || rec.getEventType() == TraceObjectChangeType.DELETED.getType() ||
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() && rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() &&
isPcChange(rec)) { changeApplies(rec)) {
// NB. Affected object may be the wrapped object, or the value entry
TraceAddressSpace space = TraceAddressSpace space =
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME); spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME);
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, getStack(), null, TraceObjectStack stack = getStack();
null); return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack,
0L, snapFor(rec));
} }
return null; return null;
} }

View file

@ -212,8 +212,8 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
} }
doUpdateFrameKeys(); doUpdateFrameKeys();
} }
manager.trace manager.trace.setChanged(
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this)); new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this, 0L, getSnap()));
} }
@Override @Override

View file

@ -18,6 +18,8 @@ package ghidra.trace.database.stack;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import com.google.common.collect.Range;
import db.DBRecord; import db.DBRecord;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -98,12 +100,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject
} }
@Override @Override
public Address getProgramCounter() { public Address getProgramCounter(long snap) {
return pc; return pc;
} }
@Override @Override
public void setProgramCounter(Address pc) { public void setProgramCounter(Range<Long> span, Address pc) {
//System.err.println("setPC(threadKey=" + stack.getThread().getKey() + ",snap=" + //System.err.println("setPC(threadKey=" + stack.getThread().getKey() + ",snap=" +
// stack.getSnap() + ",level=" + level + ",pc=" + pc + ");"); // stack.getSnap() + ",level=" + level + ",pc=" + pc + ");");
manager.trace.assertValidAddress(pc); manager.trace.assertValidAddress(pc);
@ -115,7 +117,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
update(PC_COLUMN); update(PC_COLUMN);
} }
manager.trace.setChanged( manager.trace.setChanged(
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack)); new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
stack.getSnap()));
} }
@Override @Override
@ -130,7 +133,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
update(COMMENT_COLUMN); update(COMMENT_COLUMN);
} }
manager.trace.setChanged( manager.trace.setChanged(
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack)); new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
stack.getSnap()));
} }
@Internal @Internal

View file

@ -205,6 +205,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap); 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;
@ -247,6 +254,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
} }
@Override
public TraceObjectValue getCanonicalParent(long snap) {
// TODO: If this is invoked often, perhaps keep as field
try (LockHold hold = manager.trace.lockRead()) {
if (isRoot()) {
return manager.valueStore.getObjectAt(0);
}
String canonicalKey = path.key();
TraceObjectKeyPath canonicalTail = path.parent();
return manager.valuesByChild.getLazily(this)
.stream()
.filter(v -> canonicalKey.equals(v.getEntryKey()))
.filter(v -> v.getLifespan().contains(snap))
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()))
.findAny()
.orElse(null);
}
}
@Override @Override
public boolean isRoot() { public boolean isRoot() {
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
@ -275,10 +301,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
public void setLifespan(Range<Long> lifespan) { public void setLifespan(Range<Long> lifespan) {
// TODO: Could derive fixed attributes from schema and set their lifespans, too.... // TODO: Could derive fixed attributes from schema and set their lifespans, too....
try (LockHold hold = manager.trace.lockWrite()) { try (LockHold hold = manager.trace.lockWrite()) {
Range<Long> oldLifespan = getLifespan(); doSetLifespanAndEmit(lifespan);
doSetLifespan(lifespan);
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
oldLifespan, lifespan));
} }
} }
@ -335,7 +358,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override @Override
public Collection<? extends DBTraceObjectValue> getParents() { public Collection<? extends DBTraceObjectValue> getParents() {
return manager.valuesByChild.get(this); try (LockHold hold = manager.trace.lockRead()) {
return manager.valuesByChild.get(this);
}
} }
protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) { protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) {
@ -422,6 +447,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
} }
// TODO: Could/should this return Stream instead?
protected Collection<? extends InternalTraceObjectValue> doGetValues(Range<Long> span, protected Collection<? extends InternalTraceObjectValue> doGetValues(Range<Long> span,
String key) { String key) {
return doGetValues(DBTraceUtils.lowerEndpoint(span), DBTraceUtils.upperEndpoint(span), key); return doGetValues(DBTraceUtils.lowerEndpoint(span), DBTraceUtils.upperEndpoint(span), key);
@ -638,8 +664,15 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@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();
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
return doGetSuccessors(span, DBTraceObjectValPath.of(), relativePredicates); Stream<? extends DBTraceObjectValPath> succcessors =
doGetSuccessors(span, empty, relativePredicates);
if (relativePredicates.matches(List.of())) {
// Pre-cat the empty path (not the empty stream)
return Stream.concat(Stream.of(empty), succcessors);
}
return succcessors;
} }
} }
@ -647,7 +680,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) { DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList()); Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
if (nextKeys.isEmpty()) { if (nextKeys.isEmpty()) {
return null; return Stream.empty();
} }
if (nextKeys.size() != 1) { if (nextKeys.size() != 1) {
throw new IllegalArgumentException("predicates must be a singleton"); throw new IllegalArgumentException("predicates must be a singleton");
@ -663,8 +696,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@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) {
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
return doGetOrderedSuccessors(span, DBTraceObjectValPath.of(), if (relativePath.isRoot()) {
return Stream.of(empty); // Not the empty stream
}
return doGetOrderedSuccessors(span, empty,
new PathPattern(relativePath.getKeyList()), forward); new PathPattern(relativePath.getKeyList()), forward);
} }
} }
@ -686,22 +723,22 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
if (key == TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME && if (key == TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME &&
value instanceof Address) { value instanceof Address) {
address = (Address) value; address = (Address) value;
Object lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan), InternalTraceObjectValue lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME); TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME);
if (!(lengthObj instanceof Integer)) { if (lengthObj == null || !(lengthObj.getValue() instanceof Integer)) {
return; return;
} }
length = (Integer) lengthObj; length = (Integer) lengthObj.getValue();
} }
else if (key == TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME && else if (key == TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME &&
value instanceof Integer) { value instanceof Integer) {
length = (Integer) value; length = (Integer) value;
Object addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan), InternalTraceObjectValue addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME); TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME);
if (!(addressObj instanceof Address)) { if (addressObj == null || !(addressObj.getValue() instanceof Address)) {
return; return;
} }
address = (Address) addressObj; address = (Address) addressObj.getValue();
} }
else { else {
return; return;
@ -715,6 +752,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
} }
protected void emitIfCanonicalInsertion(InternalTraceObjectValue value) {
if (value == null || !(value.getValue() instanceof DBTraceObject)) {
return;
}
DBTraceObject child = (DBTraceObject) value.getValue();
if (this.path.extend(value.getEntryKey()).equals(child.getCanonicalPath())) {
child.emitEvents(
new TraceChangeRecord<>(TraceObjectChangeType.INSERTED, null, child, value));
}
}
@Override @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) {
@ -750,6 +798,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
// NB. It will cause another event. good. // NB. It will cause another event. good.
applyBreakpointRangeHack(lifespan, key, value, resolution); applyBreakpointRangeHack(lifespan, key, value, resolution);
emitIfCanonicalInsertion(result);
return result; return result;
} }
} }
@ -783,18 +832,23 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
@Override @Override
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span, public Stream<? extends DBTraceObjectValPath> queryAncestorsTargetInterface(Range<Long> span,
Class<I> ifClass) { Class<? extends TargetObject> targetIf) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
// 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);
return getAncestors(span, matcher).map(p -> p.getFirstParent(this).queryInterface(ifClass)); return getAncestors(span, matcher);
} }
@Override @Override
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface( public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
Range<Long> span, Class<I> ifClass) { Class<I> ifClass) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
.map(p -> p.getFirstParent(this).queryInterface(ifClass));
}
@Override
public Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf) {
// This is a sort of meet-in-the-middle. The type search must originate from the root // 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(); List<String> parentPath = getCanonicalPath().getKeyList();
@ -806,20 +860,35 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath)) return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath))
.stream() .stream()
.filter(o -> DBTraceUtils.intersect(span, o.getLifespan())) .filter(o -> DBTraceUtils.intersect(span, o.getLifespan()))
.map(o -> o.queryInterface(ifClass)) // TODO: Post filter until GP-1301
.filter(i -> i != null); .filter(o -> o.getTargetSchema().getInterfaces().contains(targetIf));
} }
} }
return Stream.of(); return Stream.of();
} }
@Override
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
Range<Long> span, Class<I> ifClass) {
return queryCanonicalAncestorsTargetInterface(span,
TraceObjectInterfaceUtils.toTargetIf(ifClass))
.map(o -> o.queryInterface(ifClass));
}
@Override
public Stream<? extends DBTraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf) {
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
// TODO: Post filter until GP-1301
return getSuccessors(span, matcher).filter(
p -> p.getLastChild(this).getTargetSchema().getInterfaces().contains(targetIf));
}
@Override @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) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true); .map(p -> p.getLastChild(this).queryInterface(ifClass));
return getSuccessors(span, matcher).map(p -> p.getLastChild(this).queryInterface(ifClass))
.filter(i -> i != null); // because GP-1301
} }
protected void doDelete() { protected void doDelete() {
@ -869,13 +938,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
List<Range<Long>> removed = DBTraceUtils.subtract(lifespan, span); List<Range<Long>> removed = DBTraceUtils.subtract(lifespan, span);
if (removed.isEmpty()) { if (removed.isEmpty()) {
doDeleteReferringValues();
doDelete(); doDelete();
return null; return null;
} }
if (removed.size() == 2) { if (removed.size() == 2) {
throw new IllegalArgumentException("Cannot create a gap in an object's lifespan"); throw new IllegalArgumentException("Cannot create a gap in an object's lifespan");
} }
doSetLifespan(removed.get(0)); doSetLifespanAndEmit(removed.get(0));
return this; return this;
} }
} }

View file

@ -40,21 +40,39 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
protected abstract TraceChangeType<T, Range<Long>> getLifespanChangedType(); protected abstract TraceChangeType<T, Range<Long>> getLifespanChangedType();
protected abstract TraceChangeType<T, Void> getChangedType(); protected abstract TraceChangeType<T, ?> getChangedType();
protected abstract boolean appliesToKey(String key); protected abstract boolean appliesToKey(String key);
protected abstract TraceChangeType<T, Void> getDeletedType(); protected abstract TraceChangeType<T, Void> getDeletedType();
protected void emitExtraAdded() {
// Extension point
}
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
// Extension point
}
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
Object newValue) {
// Extension point
}
protected void emitExtraDeleted() {
// Extension point
}
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) { public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
TraceAddressSpace space = spaceValueKey == null ? null TraceAddressSpace space = spaceValueKey == null ? null
: spaceForValue(object, object.getMinSnap(), spaceValueKey); : spaceForValue(object, object.getMinSnap(), spaceValueKey);
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType()) { if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType()) {
TraceChangeType<T, Void> type = getAddedType(); TraceChangeType<T, Void> type = getAddedType();
if (type == null) { if (type == null) {
return null; return null;
} }
assert rec.getAffectedObject() == object; assert rec.getAffectedObject() == object;
emitExtraAdded();
return new TraceChangeRecord<>(type, space, iface, null, return new TraceChangeRecord<>(type, space, iface, null,
null); null);
} }
@ -69,6 +87,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
assert rec.getAffectedObject() == object; assert rec.getAffectedObject() == object;
TraceChangeRecord<TraceObject, Range<Long>> cast = TraceChangeRecord<TraceObject, Range<Long>> cast =
TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec); TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec);
emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue());
return new TraceChangeRecord<>(type, space, iface, return new TraceChangeRecord<>(type, space, iface,
cast.getOldValue(), cast.getNewValue()); cast.getOldValue(), cast.getNewValue());
} }
@ -76,17 +95,23 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
if (object.isDeleted()) { if (object.isDeleted()) {
return null; return null;
} }
TraceChangeType<T, Void> type = getChangedType(); TraceChangeType<T, ?> type = getChangedType();
if (type == null) { if (type == null) {
return null; return null;
} }
TraceChangeRecord<TraceObjectValue, Object> cast = TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec); TraceObjectChangeType.VALUE_CHANGED.cast(rec);
String key = cast.getAffectedObject().getEntryKey(); TraceObjectValue affected = cast.getAffectedObject();
String key = affected.getEntryKey();
if (!appliesToKey(key)) { if (!appliesToKey(key)) {
return null; return null;
} }
assert cast.getAffectedObject().getParent() == object; assert affected.getParent() == object;
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return null; // Object is not complete
}
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
cast.getNewValue());
return new TraceChangeRecord<>(type, space, iface, null, null); return new TraceChangeRecord<>(type, space, iface, null, null);
} }
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) { if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
@ -95,6 +120,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
return null; return null;
} }
assert rec.getAffectedObject() == object; assert rec.getAffectedObject() == object;
emitExtraDeleted();
return new TraceChangeRecord<>(type, space, iface, null, null); return new TraceChangeRecord<>(type, space, iface, null, null);
} }
return null; return null;

View file

@ -43,7 +43,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd
import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.database.module.TraceObjectSection;
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
import ghidra.trace.database.thread.DBTraceObjectThread; import ghidra.trace.database.thread.DBTraceObjectThread;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
@ -61,6 +60,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.Msg;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec; import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec;
@ -235,7 +235,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
protected Object validatePrimitive(Object child) { protected Object validatePrimitive(Object child) {
PrimitiveCodec.getCodec(child.getClass()); try {
PrimitiveCodec.getCodec(child.getClass());
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Cannot encode " + child, e);
}
return child; return child;
} }
@ -300,7 +305,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
setSchema(schema); setSchema(schema);
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all()); DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all());
assert root.getKey() == 0; assert root.getKey() == 0;
InternalTraceObjectValue val = doCreateValue(Range.all(), null, null, root); InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
assert val.getKey() == 0; assert val.getKey() == 0;
return val; return val;
} }
@ -378,6 +383,14 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
PathMatcher matcher = rootSchema.searchFor(targetIf, true); PathMatcher matcher = rootSchema.searchFor(targetIf, true);
return getValuePaths(span, matcher) return getValuePaths(span, matcher)
.filter(p -> {
TraceObject object = p.getLastChild(getRootObject());
if (object == null) {
Msg.error(this, "NULL VALUE! " + p.getLastEntry());
return false;
}
return true;
})
.map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass)); .map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass));
} }
@ -561,15 +574,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan, TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan,
TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE); TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE);
/**
* TODO: Test that when the ADDED events hits, that it actually appears in queries. I
* suspect this will work since the events and/or event processors should be delayed
* until the write lock is released. Certainly, a query would require the read lock.
*/
region.setName(path); region.setName(path);
region.setRange(range); region.setRange(range);
region.setFlags(flags); region.setFlags(flags);
trace.updateViewsAddRegionBlock(region);
return region; return region;
} }
} }

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@ -48,7 +49,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) { protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) {
this.parent = parent; this.parent = parent;
this.key = key; this.key = Objects.requireNonNull(key);
this.minSnap = minSnap; this.minSnap = minSnap;
} }
@ -75,19 +76,15 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
if (value == null) { if (value == null) {
return null; return null;
} }
if (value.key == null) {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * 2);
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
return buf.array();
}
byte[] keyBytes = value.key.getBytes(cs); byte[] keyBytes = value.key.getBytes(cs);
ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2); ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2);
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE); buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
buf.put(keyBytes); buf.put(keyBytes);
buf.put((byte) 0); buf.put((byte) 0);
buf.putLong(value.minSnap ^ Long.MIN_VALUE); buf.putLong(value.minSnap ^ Long.MIN_VALUE);
return buf.array(); return buf.array();
@ -101,16 +98,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
DBTraceObject parent = DBTraceObject parent =
DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE); DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE);
String key;
if (enc.length > Long.BYTES * 2) { int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position()); assert nullPos != -1;
assert nullPos != -1; String key = new String(enc, buf.position(), nullPos - buf.position(), cs);
key = new String(enc, buf.position(), nullPos - buf.position(), cs); buf.position(nullPos + 1);
buf.position(nullPos + 1);
}
else {
key = null;
}
long minSnap = buf.getLong() ^ Long.MIN_VALUE; long minSnap = buf.getLong() ^ Long.MIN_VALUE;
return new PrimaryTriple(parent, key, minSnap); return new PrimaryTriple(parent, key, minSnap);
@ -186,14 +179,17 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
indexed = true, indexed = true,
codec = PrimaryTripleDBFieldCodec.class) codec = PrimaryTripleDBFieldCodec.class)
private PrimaryTriple triple; private PrimaryTriple triple;
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME) @DBAnnotatedField(
column = MAX_SNAP_COLUMN_NAME)
private long maxSnap; private long maxSnap;
@DBAnnotatedField( @DBAnnotatedField(
column = CHILD_COLUMN_NAME, column = CHILD_COLUMN_NAME,
indexed = true, indexed = true,
codec = DBTraceObjectDBFieldCodec.class) codec = DBTraceObjectDBFieldCodec.class)
private DBTraceObject child; private DBTraceObject child;
@DBAnnotatedField(column = PRIMITIVE_COLUMN_NAME, codec = VariantDBFieldCodec.class) @DBAnnotatedField(
column = PRIMITIVE_COLUMN_NAME,
codec = VariantDBFieldCodec.class)
private Object primitive; private Object primitive;
protected final DBTraceObjectManager manager; protected final DBTraceObjectManager manager;
@ -260,12 +256,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
@Override @Override
public DBTraceObject getParent() { public DBTraceObject getParent() {
return triple.parent; return triple == null ? null : triple.parent;
} }
@Override @Override
public String getEntryKey() { public String getEntryKey() {
return triple.key; return triple == null ? null : triple.key;
} }
@Override @Override

View file

@ -71,10 +71,13 @@ interface InternalTraceObjectValue extends TraceObjectValue {
} }
} }
else { else {
entry.doTruncateOrDelete(range); InternalTraceObjectValue created = entry.doTruncateOrDelete(range);
if (!entry.isDeleted()) { if (!entry.isDeleted()) {
kept.add(entry); kept.add(entry);
} }
if (created != null) {
kept.add(created);
}
} }
} }

View file

@ -57,10 +57,23 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final class TraceObjectChangeType<T, U> public static final class TraceObjectChangeType<T, U>
extends DefaultTraceChangeType<T, U> { extends DefaultTraceChangeType<T, U> {
/** /**
* An object was created, but not necessarily inserted. * An object was created, but not yet inserted.
*
* <p>
* Between the {@link #CREATED} and {@link #INSERTED} events, an object is considered
* "incomplete," because it is likely missing its attributes. Thus, a trace client must take
* care to ensure all attributes, especially fixed attributes, are added to the object
* before it is inserted at its canonical path. Listeners may use
* {@link TraceObject#getCanonicalParent(long)} to check if an object is complete for a
* given snapshot.
*/ */
public static final TraceObjectChangeType<TraceObject, Void> CREATED = public static final TraceObjectChangeType<TraceObject, Void> CREATED =
new TraceObjectChangeType<>(); new TraceObjectChangeType<>();
/**
* An object was inserted at its canonical path.
*/
public static final TraceObjectChangeType<TraceObject, TraceObjectValue> INSERTED =
new TraceObjectChangeType<>();
/** /**
* An object's lifespan changed. * An object's lifespan changed.
*/ */
@ -75,9 +88,9 @@ public interface Trace extends DataTypeManagerDomainObject {
* An object's value changed. * An object's value changed.
* *
* <p> * <p>
* If the old value is uniform for the new value's lifespan, that value is passed as the old * If the old value was equal for the entirety of the new value's lifespan, that old value
* value. Otherwise, {@code null} is passed for the old value. If the value was cleared, * is passed as the old value. Otherwise, {@code null} is passed for the old value. If the
* {@code null} is passed for the new value. * value was cleared, {@code null} is passed for the new value.
*/ */
public static final TraceObjectChangeType<TraceObjectValue, Object> VALUE_CHANGED = public static final TraceObjectChangeType<TraceObjectValue, Object> VALUE_CHANGED =
new TraceObjectChangeType<>(); new TraceObjectChangeType<>();
@ -307,7 +320,12 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final class TraceStackChangeType<U> public static final class TraceStackChangeType<U>
extends DefaultTraceChangeType<TraceStack, U> { extends DefaultTraceChangeType<TraceStack, U> {
public static final TraceStackChangeType<Void> ADDED = new TraceStackChangeType<>(); public static final TraceStackChangeType<Void> ADDED = new TraceStackChangeType<>();
public static final TraceStackChangeType<Void> CHANGED = new TraceStackChangeType<>(); /**
* NOTE: The "new value" is the (min) snap where the change occurred. For StackFrame, it's
* Stack.getSnap(), for ObjectStackFrame, the min snap of the value entry. The "old value"
* is always 0.
*/
public static final TraceStackChangeType<Long> CHANGED = new TraceStackChangeType<>();
public static final TraceStackChangeType<Void> DELETED = new TraceStackChangeType<>(); public static final TraceStackChangeType<Void> DELETED = new TraceStackChangeType<>();
} }

Some files were not shown because too many files have changed in this diff Show more