Merge remote-tracking branch

'origin/GP-1678_Dan_objectRecorder--SQUASHED'

Conflicts:
	Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java
This commit is contained in:
Ryan Kurtz 2022-04-29 10:10:33 -04:00
commit 0e8e418bfa
110 changed files with 3191 additions and 497 deletions

View file

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

View file

@ -15,28 +15,19 @@
*/
package agent.frida.model;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.Map.Entry;
import org.junit.Ignore;
import org.junit.Test;
import agent.frida.model.iface2.FridaModelTargetProcess;
import agent.frida.model.iface2.FridaModelTargetThreadContainer;
import agent.frida.model.impl.FridaModelTargetThreadContainerImpl;
import ghidra.dbg.agent.DefaultTargetModelRoot;
import ghidra.dbg.target.*;
import ghidra.dbg.test.AbstractDebuggerModelRegistersTest;
import ghidra.dbg.test.AbstractDebuggerModelTest;
import ghidra.dbg.test.ProvidesTargetViaLaunchSpecimen;
import ghidra.dbg.test.*;
import ghidra.dbg.util.PathUtils;
public abstract class AbstractModelForFridaX64RegistersTest
@ -94,7 +85,7 @@ public abstract class AbstractModelForFridaX64RegistersTest
for (Entry<String, byte[]> ent : getRegisterWrites().entrySet()) {
String regName = ent.getKey();
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()) {
assertEquals(ent.getValue().length, (reg.getBitLength() + 7) / 8);
}
@ -134,7 +125,7 @@ public abstract class AbstractModelForFridaX64RegistersTest
for (TargetRegisterBank bank : banks.values()) {
for (String name : exp.keySet()) {
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()) {
byte[] bytes = waitOn(bank.readRegister(reg));
read.put(name, bytes);
@ -163,7 +154,7 @@ public abstract class AbstractModelForFridaX64RegistersTest
for (TargetRegisterBank bank : banks.values()) {
for (String name : write.keySet()) {
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()) {
waitOn(bank.writeRegister(reg, write.get(name)));

View file

@ -83,6 +83,6 @@ public abstract class AbstractModelForGdbFrameActivationTest
assertFalse(line.contains("\n"));
assertTrue(line.startsWith("#"));
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("*"))
.collect(Collectors.toList())).trim();
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()));
String threadGid = line.split("\\s+")[2];
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 (TargetRegisterBank bank : banks.values()) {
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()) {
waitOn(bank.writeRegister(reg, toWrite.get(name)));
}

View file

@ -81,7 +81,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (Entry<String, byte[]> ent : getRegisterWrites().entrySet()) {
String regName = ent.getKey();
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()) {
assertEquals(ent.getValue().length, (reg.getBitLength() + 7) / 8);
}
@ -121,7 +121,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (TargetRegisterBank bank : banks.values()) {
for (String name : exp.keySet()) {
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()) {
byte[] bytes = waitOn(bank.readRegister(reg));
read.put(name, bytes);
@ -149,7 +149,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (TargetRegisterBank bank : banks.values()) {
for (String name : write.keySet()) {
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()) {
waitOn(bank.writeRegister(reg, write.get(name)));

View file

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

View file

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

View file

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

View file

@ -811,7 +811,7 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
synchronized (this) {
monitor.checkCanceled();
CompletableFuture<NavigableMap<Address, byte[]>> recCapture =
recorder.captureProcessMemory(new AddressSet(range), monitor, false);
recorder.readMemoryBlocks(new AddressSet(range), monitor, false);
this.captureTask = recCapture.thenCompose(__ -> {
return recorder.getTarget().getModel().flushEvents();
}).thenCompose(__ -> {

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

View file

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

View file

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

View file

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

View file

@ -101,6 +101,7 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
*/
public default Set<DebuggerMappingOffer> getOffers(TargetObject target,
boolean includeOverrides) {
// TODO: Remove this check?
if (!(target instanceof TargetProcess)) {
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 process the target process
* @param target the target (usually a process)
* @param includeOverrides true to include override offers, i.e., those with negative confidence
* @return the offers, possibly empty, but never null
*/
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides);
}

View file

@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.debug.mapping;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.*;
public interface DebuggerMemoryMapper {
/**
@ -33,7 +32,10 @@ public interface DebuggerMemoryMapper {
* @param traceRange the range in the view'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
@ -49,5 +51,8 @@ public interface DebuggerMemoryMapper {
* @param targetRange the range in the target'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) {
assert isInFactory(traceAddr, traceAddressFactory);
return toSameNamedSpace(traceAddr, targetAddressFactory);
};
@Override
public AddressRange traceToTarget(AddressRange traceRange) {
return new AddressRangeImpl(traceToTarget(traceRange.getMinAddress()),
traceToTarget(traceRange.getMaxAddress()));
}
@Override
@ -65,10 +59,4 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
assert isInFactory(targetAddr, targetAddressFactory);
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;
public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID,
CompilerSpecID csId, Collection<String> extraRegNames)
CompilerSpecID csID, Collection<String> extraRegNames)
throws LanguageNotFoundException, CompilerSpecNotFoundException {
this.target = target;
LanguageService langServ = DefaultLanguageService.getLanguageService();
this.language = langServ.getLanguage(langID);
this.cSpec = language.getCompilerSpecByID(csId);
this.cSpec = language.getCompilerSpecByID(csID);
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 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.util.DefaultLanguageService;
@ -54,7 +55,7 @@ public class OverridesDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) {
if (!includeOverrides) {
return Set.of();
@ -65,7 +66,7 @@ public class OverridesDebuggerMappingOpinion implements DebuggerMappingOpinion {
// ALL THE SPECS!!!
new LanguageCompilerSpecQuery(null, null, null, null, null))
.stream()
.map(lcsp -> offerForLanguageAndCSpec(process, endian, lcsp))
.map(lcsp -> offerForLanguageAndCSpec(target, endian, lcsp))
.collect(Collectors.toSet());
}
}

View file

@ -49,24 +49,28 @@ public class FridaArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
protected static class FridaAarch64MacosOffer extends DefaultDebuggerMappingOffer {
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"));
}
}
@Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includesOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("frida")) {
return Set.of();
}
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();
if (os.contains("macos")) {
if (is64Bit) {
Msg.info(this, "Using os=" + os + " arch=" + arch);
return Set.of(new FridaAarch64MacosOffer(process));
return Set.of(new FridaAarch64MacosOffer((TargetProcess) target));
}
}
return Set.of();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -64,8 +64,11 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of();
}
@ -73,6 +76,6 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
return Set.of();
}
// 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
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of();
}
@ -73,6 +76,6 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
return Set.of();
}
// 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 ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg;
@ -71,8 +70,12 @@ public class LldbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("lldb")) {
return Set.of();
}

View file

@ -245,6 +245,11 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
Msg.info(this, "Ignoring " + tb +
" 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: " + tb);
}
}
private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull,

View file

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

View file

@ -21,11 +21,11 @@ import java.util.concurrent.CompletableFuture;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.app.plugin.core.debug.service.model.record.RecorderUtils;
import ghidra.dbg.target.TargetMemory;
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.memory.*;
import ghidra.util.Msg;
@ -35,20 +35,7 @@ import ghidra.util.task.TaskMonitor;
public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
// For large memory captures
private static final int BLOCK_SIZE = 4096;
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 static final int BLOCK_BITS = 12; // 4096 bytes
private final DefaultTraceRecorder recorder;
private final Trace trace;
@ -62,45 +49,7 @@ public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set,
TaskMonitor monitor, boolean toMap) {
// TODO: Figure out how to display/select per-thread memory.
// 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);
return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor, toMap);
}
@Override

View file

@ -86,7 +86,7 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
traceFrame.setProgramCounter(pc);
traceFrame.setProgramCounter(null, pc); // Not object-based, so span=null
}
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.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.TraceRecorderListener;
import ghidra.async.AsyncLazyValue;
@ -64,11 +66,11 @@ public class DefaultTraceRecorder implements TraceRecorder {
TraceObjectManager objectManager;
DefaultBreakpointRecorder breakpointRecorder;
DefaultDataTypeRecorder datatypeRecorder;
DataTypeRecorder datatypeRecorder;
DefaultMemoryRecorder memoryRecorder;
DefaultModuleRecorder moduleRecorder;
DefaultProcessRecorder processRecorder;
DefaultSymbolRecorder symbolRecorder;
SymbolRecorder symbolRecorder;
DefaultTimeRecorder timeRecorder;
//protected final PermanentTransactionExecutor seqTx;
@ -94,10 +96,10 @@ public class DefaultTraceRecorder implements TraceRecorder {
this.processRecorder = new DefaultProcessRecorder(this);
this.breakpointRecorder = new DefaultBreakpointRecorder(this);
this.datatypeRecorder = new DefaultDataTypeRecorder(this);
this.datatypeRecorder = new DataTypeRecorder(this);
this.memoryRecorder = new DefaultMemoryRecorder(this);
this.moduleRecorder = new DefaultModuleRecorder(this);
this.symbolRecorder = new DefaultSymbolRecorder(this);
this.symbolRecorder = new SymbolRecorder(this);
this.timeRecorder = new DefaultTimeRecorder(this);
this.objectManager = new TraceObjectManager(target, mapper, this);
}
@ -266,7 +268,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
/*---------------- CAPTURE METHODS -------------------*/
@Override
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set,
public CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean toMap) {
if (set.isEmpty()) {
return CompletableFuture.completedFuture(new TreeMap<>());
@ -499,12 +501,6 @@ public class DefaultTraceRecorder implements TraceRecorder {
/*---------------- LISTENER METHODS -------------------*/
// UNUSED?
@Override
public TraceEventListener getListenerForRecord() {
return objectManager.getEventListener();
}
public ListenerSet<TraceRecorderListener> getListeners() {
return objectManager.getListeners();
}
@ -526,17 +522,17 @@ public class DefaultTraceRecorder implements TraceRecorder {
}
@Override
public AddressSetView getAccessibleProcessMemory() {
public AddressSetView getAccessibleMemory() {
return processRecorder.getAccessibleProcessMemory();
}
@Override
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {
public CompletableFuture<byte[]> readMemory(Address start, int length) {
return processRecorder.readProcessMemory(start, length);
}
@Override
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) {
public CompletableFuture<Void> writeMemory(Address start, byte[] data) {
return processRecorder.writeProcessMemory(start, data);
}
@ -566,6 +562,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
return true;
}
// UNUSED?
@Override
public CompletableFuture<Void> flushTransactions() {
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.TransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx;
import ghidra.framework.model.DomainObjectException;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg;
import ghidra.util.exception.ClosedException;
public class PermanentTransactionExecutor {
private final TransactionCoalescer txc;
private final Executor[] threads;
private final ExecutorService[] threads;
private final UndoableDomainObject obj;
public PermanentTransactionExecutor(UndoableDomainObject obj, String name, int threadCount,
int delayMs) {
this.obj = obj;
txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs);
this.threads = new Executor[threadCount];
this.threads = new ExecutorService[threadCount];
for (int i = 0; i < threadCount; i++) {
ThreadFactory factory = new BasicThreadFactory.Builder()
.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
* 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)) {
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 -> {
Msg.error(this, "Trouble recording " + description, e);
return null;

View file

@ -20,7 +20,7 @@ import ghidra.util.database.UndoableTransaction;
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);
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
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) {

View file

@ -32,6 +32,13 @@ public interface AbstractRecorderMemory {
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,
DebuggerMemoryMapper memMapper);

View file

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* 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.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
@ -27,13 +29,13 @@ import ghidra.program.model.data.*;
import ghidra.trace.model.Trace;
import ghidra.util.task.TaskMonitor;
public class DefaultDataTypeRecorder {
public class DataTypeRecorder {
//private DefaultTraceRecorder recorder;
private Trace trace;
//private TraceRecorder recorder;
private final Trace trace;
private final TargetDataTypeConverter typeConverter;
public DefaultDataTypeRecorder(DefaultTraceRecorder recorder) {
public DataTypeRecorder(TraceRecorder recorder) {
//this.recorder = recorder;
this.trace = recorder.getTrace();
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
* 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.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
@ -30,12 +32,12 @@ import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class DefaultSymbolRecorder {
public class SymbolRecorder {
private DefaultTraceRecorder recorder;
private Trace trace;
private final TraceRecorder recorder;
private final Trace trace;
public DefaultSymbolRecorder(DefaultTraceRecorder recorder) {
public SymbolRecorder(TraceRecorder recorder) {
this.recorder = recorder;
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.pc = trace.getBaseLanguage().getProgramCounter();
this.pcRange = TraceRegisterUtils.rangeForRegister(pc);
this.pcRange = pc == null ? null : TraceRegisterUtils.rangeForRegister(pc);
ClassSearcher.addChangeListener(injectsChangeListener);
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(() -> {
disassembleStackPcVals(stack, stack.getSnap(), null);
disassembleStackPcVals(stack, snap, null);
});
}
@ -186,7 +186,7 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
if (space.getFrameLevel() != 0) {
return;
}
if (!range.getRange().intersects(pcRange)) {
if (pcRange == null || !range.getRange().intersects(pcRange)) {
return;
}
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);
if (frame == null) {
return;
}
Address pcVal = frame.getProgramCounter();
Address pcVal = frame.getProgramCounter(snap);
if (pcVal == null) {
return;
}
if (range == null || range.contains(pcVal)) {
// 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.DebuggerRegisterMapper;
import ghidra.app.plugin.core.debug.service.model.TraceEventListener;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.lifecycle.Internal;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
@ -251,11 +249,11 @@ public interface TraceRecorder {
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
*/
AddressSetView getAccessibleProcessMemory();
AddressSetView getAccessibleMemory();
/**
* Capture a target thread's registers.
@ -293,50 +291,49 @@ public interface TraceRecorder {
Map<Register, RegisterValue> values);
/**
* Read (and capture) a range of process memory
*
* <p>
* This will not quantize the blocks; whereas
* {@link #captureProcessMemory(AddressSetView, TaskMonitor)} will.
* Read (and capture) a range of target memory
*
* @param start the address to start at, as viewed in the trace
* @param length the number of bytes to read
* @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 data the data to write
* @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>
* Though this 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.
* The given address set is quantized to the minimal set of blocks covering the requested set.
* To capture a precise range, use {@link #readMemory(Address, int)} instead. Though this
* 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>
* 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
* captures, it is recommended to set {@code toMap} to false. The recorder will place the bytes
* into the trace where they can be retrieved later. For small captures, and where bypassing the
* database may offer some advantage, set {@code toMap} to true, and the captured bytes will be
* returned in an interval map. Connected intervals may or may not be joined.
* occurrence -- the error is logged, but the task may still complete "successfully." For large
* captures, it is recommended to set {@code returnResult} to false. The recorder will capture
* the bytes into the trace where they can be retrieved later. For small captures, and where
* bypassing the database may offer some advantage, set {@code returnResult} to true, and the
* 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 toMap true to return results in a map, false to complete with null
* @return a future which completes with the capture results
* @param returnResult true to complete with results, false to complete with null
* @return a future which completes when the task finishes
*/
CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView selection,
TaskMonitor monitor, boolean toMap);
CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean returnResult);
/**
* 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
* 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
* {@link #writeProcessMemory(Address, byte[])}.
* {@link #writeMemory(Address, byte[])}.
*
* @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
@ -356,7 +353,7 @@ public interface TraceRecorder {
default CompletableFuture<Void> writeVariable(TraceThread thread, int frameLevel,
Address address, byte[] data) {
if (address.isMemoryAddress()) {
return writeProcessMemory(address, data);
return writeMemory(address, data);
}
if (address.isRegisterAddress()) {
Language lang = getTrace().getBaseLanguage();
@ -550,22 +547,6 @@ public interface TraceRecorder {
*/
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.
*

View file

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

View file

@ -106,11 +106,11 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator {
TraceStackFrame frame;
frame = stack.getFrame(0, false);
frame.setProgramCounter(tb.addr(0x00404321));
frame.setProgramCounter(Range.all(), tb.addr(0x00404321));
frame = stack.getFrame(1, false);
frame.setProgramCounter(tb.addr(0x00401234));
frame.setProgramCounter(Range.all(), tb.addr(0x00401234));
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("echo", program, TaskMonitor.DUMMY);

View file

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

View file

@ -15,7 +15,7 @@
*/
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 java.awt.Color;
@ -1254,8 +1254,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
thread = tb.getOrAddThread("Thread 1", 0);
DBTraceStackManager sm = tb.trace.getStackManager();
TraceStack stack = sm.getStack(thread, 0, true);
stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234));
stack.getFrame(1, true).setProgramCounter(tb.addr(0x00404321));
stack.getFrame(0, true).setProgramCounter(Range.all(), tb.addr(0x00401234));
stack.getFrame(1, true).setProgramCounter(Range.all(), tb.addr(0x00404321));
}
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
@ -1346,7 +1346,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
thread = tb.getOrAddThread("Thread 1", 0);
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);
traceManager.activateThread(thread);
@ -1356,7 +1356,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
try (UndoableTransaction tid = tb.startTransaction()) {
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);

View file

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

View file

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

View file

@ -19,7 +19,7 @@ import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*;
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.LanguageID;
@ -27,12 +27,12 @@ public class TestKnownArchDebuggerMappingOpinion implements DebuggerMappingOpini
public static final String ARCH = "test-known-arch";
@Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process,
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) {
if (!ARCH.equals(env.getArchitecture())) {
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"),
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) {
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) {

View file

@ -45,6 +45,10 @@ import ghidra.dbg.target.schema.TargetAttributeType;
@DebuggerTargetObjectIface("BreakpointSpec")
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 {
/**
* 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) {
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.concurrent.CompletableFuture;
import java.util.stream.*;
import ghidra.async.AsyncFence;
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>
* 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.
*
* <p>
* If there are fewer wildcards in a pattern than given, only the left-most indices are taken.
* If there are fewer indices than wildcards in a pattern, then the right-most wildcards are
* left in the resulting pattern. Note while rare, attribute wildcards are substituted, too.
* If there are fewer wildcards in a pattern than given, only the left-most keys are taken. If
* there are fewer keys than wildcards in a pattern, then the right-most wildcards are left in
* the resulting pattern.
*
* @param indices the indices to substitute
* @param keys the keys to substitute
* @return the pattern or matcher with the applied substitutions
*/
PathPredicates applyKeys(List<String> indices);
PathPredicates applyKeys(List<String> keys);
default PathPredicates applyIndices(String... indices) {
return applyKeys(List.of(indices));
default PathPredicates applyKeys(Stream<String> keys) {
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.*;
public class EmptyDebuggerObjectModel extends AbstractDebuggerObjectModel {
protected final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0);
protected final AddressFactory factory = new DefaultAddressFactory(new AddressSpace[] { ram });
public final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0);
public final AddressSpace ram3 =
new GenericAddressSpace("ram3", 64, AddressSpace.TYPE_RAM, 0);
protected final AddressFactory factory =
new DefaultAddressFactory(new AddressSpace[] { ram, ram3 });
@Override
public AddressFactory getAddressFactory() {

View file

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

View file

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

View file

@ -32,16 +32,24 @@ public class TestMimickJavaLauncher
public TestMimickJavaLauncher(TestTargetObject parent) {
super(parent, "Java Launcher", "Launcher");
setAttributes(List.of(), Map.of(TargetMethod.PARAMETERS_ATTRIBUTE_NAME, Map.of("Suspend",
ParameterDescription.create(Boolean.class, "Suspend", false, true, "Suspend", ""),
"Quote", ParameterDescription.create(String.class, "Quote", false, "\"", "Quote", ""),
"Launcher",
ParameterDescription.create(String.class, "Launcher", false, "java", "Launcher", ""),
"Options",
ParameterDescription.create(String.class, "Options", false, "", "Options", ""), "Main",
ParameterDescription.create(String.class, "Main", false, "hw.HelloWorld", "Main", ""),
"Home", ParameterDescription.create(String.class, "Home", false,
"/opt/java-11-amazon-corretto", "Home", ""))),
setAttributes(
List.of(), Map.of(TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetParameterMap.ofEntries(
Map.entry("Suspend",
ParameterDescription.create(Boolean.class, "Suspend", false, true, "Suspend",
"")),
Map.entry("Quote",
ParameterDescription.create(String.class, "Quote", false, "\"", "Quote", "")),
Map.entry("Launcher",
ParameterDescription.create(String.class, "Launcher", false, "java", "Launcher",
"")),
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");
}
@ -52,6 +60,6 @@ public class TestMimickJavaLauncher
@Override
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;
// TODO: Test some other breakpoint conventions:
// A1) 1-1 spec-effective, where spec is effective is breakpoint (DONE)
// A2) 1-n spec-effective, where effective are children of spec
// A1) 1-1 spec-loc, where spec is loc is breakpoint (DONE)
// A2) 1-n spec-loc, where locs are children of spec
// B1) container per process (DONE)
// B2) container per session

View file

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

View file

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

View file

@ -18,15 +18,22 @@ package ghidra.dbg.model;
import java.util.List;
import java.util.Map;
import ghidra.program.model.address.AddressSpace;
public class TestTargetProcessContainer
extends DefaultTestTargetObject<TestTargetProcess, TestTargetSession> {
public TestTargetProcessContainer(TestTargetSession parent) {
super(parent, "Processes", "Processes");
}
public TestTargetProcess addProcess(int pid) {
TestTargetProcess proc = new TestTargetProcess(this, pid);
public TestTargetProcess addProcess(int pid, AddressSpace space) {
TestTargetProcess proc = new TestTargetProcess(this, pid, space);
changeElements(List.of(), List.of(proc), Map.of(), "Test Process Added");
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 ghidra.dbg.target.TargetSectionContainer;
import ghidra.program.model.address.AddressRange;
public class TestTargetSectionContainer
extends DefaultTestTargetObject<TestTargetSection, TestTargetModule> {
extends DefaultTestTargetObject<TestTargetSection, TestTargetModule>
implements TargetSectionContainer {
public TestTargetSectionContainer(TestTargetModule parent) {
super(parent, "Sections", "SectionContainer");

View file

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

View file

@ -38,6 +38,10 @@ public class TestTargetStack extends DefaultTestTargetObject<TestTargetStackFram
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
*

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;
}
public void removeThreads(TestTargetThread[] threads) {
public void removeThreads(TestTargetThread... threads) {
List<String> indices =
Stream.of(threads).map(TargetObject::getIndex).collect(Collectors.toList());
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()
.getSuccessorSchema(seedPath)
.searchFor(cls, seedPath, true)
.applyIndices(index);
.applyKeys(index);
if (matcher.isEmpty()) {
return null;
}

View file

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

View file

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

View file

@ -268,7 +268,9 @@ public class DBTraceObjectBreakpointLocation
@Override
public TraceObjectBreakpointSpec getSpecification() {
try (LockHold hold = object.getTrace().lockRead()) {
return object.queryAncestorsInterface(getLifespan(), TraceObjectBreakpointSpec.class)
return object
.queryCanonicalAncestorsInterface(getLifespan(),
TraceObjectBreakpointSpec.class)
.findAny()
.orElseThrow();
}

View file

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

View file

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

View file

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

View file

@ -107,14 +107,14 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
@Override
public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
}
}
@Override
public String getName() {
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
}
@Override

View file

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

View file

@ -17,6 +17,8 @@ package ghidra.trace.database.stack;
import java.util.List;
import com.google.common.collect.Range;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
@ -72,23 +74,25 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
}
@Override
public Address getProgramCounter() {
return TraceObjectInterfaceUtils.getValue(object, object.getMaxSnap(),
public Address getProgramCounter(long snap) {
return TraceObjectInterfaceUtils.getValue(object, snap,
TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null);
}
@Override
public void setProgramCounter(Address pc) {
public void setProgramCounter(Range<Long> span, Address pc) {
try (LockHold hold = object.getTrace().lockWrite()) {
if (pc == Address.NO_ADDRESS) {
pc = null;
}
object.setValue(object.getLifespan(), TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
object.setValue(object.getLifespan().intersection(span),
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
}
}
@Override
public String getComment() {
// TODO: Do I need to add a snap argument?
// TODO: One day, we'll have dynamic columns in the debugger
/**
* I don't use an attribute for this, because there's not a nice way track the "identity" of
@ -101,7 +105,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
* follow the "same frame" as its level changes.
*/
try (LockHold hold = object.getTrace().lockRead()) {
Address pc = getProgramCounter();
Address pc = getProgramCounter(object.getMaxSnap());
return pc == null ? null
: object.getTrace()
.getCommentAdapter()
@ -111,11 +115,12 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
@Override
public void setComment(String comment) {
// TODO: Do I need to add a span argument?
try (LockHold hold = object.getTrace().lockWrite()) {
object.getTrace()
.getCommentAdapter()
.setComment(object.getLifespan(), getProgramCounter(), CodeUnit.EOL_COMMENT,
comment);
.setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()),
CodeUnit.EOL_COMMENT, comment);
}
}
@ -124,23 +129,38 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
return object;
}
protected boolean isPcChange(TraceChangeRecord<?, ?> rec) {
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
return TargetStackFrame.PC_ATTRIBUTE_NAME.equals(cast.getAffectedObject().getEntryKey());
TraceObjectValue affected = cast.getAffectedObject();
assert affected.getParent() == object;
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
return false;
}
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return false;
}
return true;
}
protected long snapFor(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap();
}
return object.getMinSnap();
}
@Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType() ||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() ||
rec.getEventType() == TraceObjectChangeType.DELETED.getType() ||
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() &&
isPcChange(rec)) {
// NB. Affected object may be the wrapped object, or the value entry
changeApplies(rec)) {
TraceAddressSpace space =
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME);
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, getStack(), null,
null);
TraceObjectStack stack = getStack();
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack,
0L, snapFor(rec));
}
return null;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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