mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Merge remote-tracking branch
'origin/GP-1678_Dan_objectRecorder--SQUASHED' Conflicts: Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java
This commit is contained in:
commit
0e8e418bfa
110 changed files with 3191 additions and 497 deletions
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -73,7 +73,7 @@ public class PCLocationTrackingSpec implements RegisterLocationTrackingSpec {
|
|||
if (frame == null) {
|
||||
return null;
|
||||
}
|
||||
return frame.getProgramCounter();
|
||||
return frame.getProgramCounter(snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(__ -> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -410,6 +410,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
|||
return;
|
||||
}
|
||||
if (currentStack == stack) {
|
||||
stackTableModel.fireTableDataChanged();
|
||||
return;
|
||||
}
|
||||
currentStack = stack;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.AbstractPcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
|
@ -96,7 +98,8 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
|
|||
ls.writeCacheDown(trace, destSnap, traceThread, 0);
|
||||
if (synthesizeStacks) {
|
||||
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
|
||||
stack.getFrame(0, true).setProgramCounter(emuThread.getCounter());
|
||||
stack.getFrame(0, true)
|
||||
.setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -268,7 +268,9 @@ public class DBTraceObjectBreakpointLocation
|
|||
@Override
|
||||
public TraceObjectBreakpointSpec getSpecification() {
|
||||
try (LockHold hold = object.getTrace().lockRead()) {
|
||||
return object.queryAncestorsInterface(getLifespan(), TraceObjectBreakpointSpec.class)
|
||||
return object
|
||||
.queryCanonicalAncestorsInterface(getLifespan(),
|
||||
TraceObjectBreakpointSpec.class)
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package ghidra.trace.database.breakpoint;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
@ -31,6 +32,7 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.Trace.TraceBreakpointChangeType;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||
|
@ -46,7 +48,7 @@ public class DBTraceObjectBreakpointSpec
|
|||
implements TraceObjectBreakpointSpec, DBTraceObjectInterface {
|
||||
private final DBTraceObject object;
|
||||
|
||||
private Set<TraceBreakpointKind> kinds;
|
||||
private TraceBreakpointKindSet kinds;
|
||||
|
||||
public DBTraceObjectBreakpointSpec(DBTraceObject object) {
|
||||
this.object = object;
|
||||
|
@ -147,28 +149,25 @@ public class DBTraceObjectBreakpointSpec
|
|||
// TODO: Target-Trace mapping is implied by encoded name. Seems bad.
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
|
||||
kinds.stream().map(k -> k.name()).collect(Collectors.joining(",")));
|
||||
this.kinds = Set.copyOf(kinds);
|
||||
TraceBreakpointKindSet.encode(kinds));
|
||||
this.kinds = TraceBreakpointKindSet.copyOf(kinds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TraceBreakpointKind> getKinds() {
|
||||
Set<TraceBreakpointKind> result = new HashSet<>();
|
||||
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
|
||||
TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, String.class, null);
|
||||
if (kindsStr == null) {
|
||||
return kinds;
|
||||
}
|
||||
for (String name : kindsStr.split(",")) {
|
||||
try {
|
||||
result.add(TraceBreakpointKind.valueOf(name));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.warn(this, "Could not decode breakpoint kind from trace database: " + name);
|
||||
}
|
||||
try {
|
||||
return kinds = TraceBreakpointKindSet.decode(kindsStr, true);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.warn(this, "Unrecognized breakpoint kind(s) in trace database: " + e);
|
||||
return kinds = TraceBreakpointKindSet.decode(kindsStr, false);
|
||||
}
|
||||
return kinds = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -210,13 +209,17 @@ public class DBTraceObjectBreakpointSpec
|
|||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
||||
String key = cast.getAffectedObject().getEntryKey();
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
String key = affected.getEntryKey();
|
||||
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
|
||||
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key);
|
||||
if (!applies) {
|
||||
return null;
|
||||
}
|
||||
assert cast.getAffectedObject().getParent() == object;
|
||||
assert affected.getParent() == object;
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return null; // Incomplete object
|
||||
}
|
||||
for (TraceObjectBreakpointLocation loc : getLocations()) {
|
||||
DBTraceObjectBreakpointLocation dbLoc = (DBTraceObjectBreakpointLocation) loc;
|
||||
TraceAddressSpace space = dbLoc.getTraceAddressSpace();
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.google.common.collect.Range;
|
|||
import ghidra.dbg.target.TargetMemoryRegion;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
|
@ -71,6 +72,27 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
protected TraceChangeType<TraceMemoryRegion, Void> getDeletedType() {
|
||||
return TraceMemoryRegionChangeType.DELETED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraAdded() {
|
||||
updateViewsAdded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
updateViewsLifespanChanged(oldLifespan, newLifespan);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
|
||||
Object newValue) {
|
||||
updateViewsValueChanged(lifespan, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emitExtraDeleted() {
|
||||
updateViewsDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
private final DBTraceObject object;
|
||||
|
@ -96,7 +118,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
public void setName(String name) {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||
object.getTrace().updateViewsChangeRegionBlockName(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +137,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
}
|
||||
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
|
||||
newLifespan);
|
||||
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +177,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
return;
|
||||
}
|
||||
object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
|
||||
object.getTrace().updateViewsChangeRegionBlockRange(this, oldRange, newRange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +248,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
Boolean val = flags.contains(flag) ? true : null;
|
||||
object.setValue(lifespan, keyForFlag(flag), val);
|
||||
}
|
||||
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +257,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
for (TraceMemoryFlag flag : flags) {
|
||||
object.setValue(lifespan, keyForFlag(flag), true);
|
||||
}
|
||||
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +266,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
for (TraceMemoryFlag flag : flags) {
|
||||
object.setValue(lifespan, keyForFlag(flag), null);
|
||||
}
|
||||
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,7 +312,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
public void delete() {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.deleteTree();
|
||||
object.getTrace().updateViewsDeleteRegionBlock(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,4 +324,35 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
|||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||
return translator.translate(rec);
|
||||
}
|
||||
|
||||
protected void updateViewsAdded() {
|
||||
object.getTrace().updateViewsAddRegionBlock(this);
|
||||
}
|
||||
|
||||
protected void updateViewsLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
|
||||
}
|
||||
|
||||
protected void updateViewsValueChanged(Range<Long> lifespan, String key, Object oldValue,
|
||||
Object newValue) {
|
||||
DBTrace trace = object.getTrace();
|
||||
switch (key) {
|
||||
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
|
||||
trace.updateViewsChangeRegionBlockRange(this,
|
||||
(AddressRange) oldValue, (AddressRange) newValue);
|
||||
return;
|
||||
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
|
||||
trace.updateViewsChangeRegionBlockName(this);
|
||||
return;
|
||||
case TargetMemoryRegion.READABLE_ATTRIBUTE_NAME:
|
||||
case TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME:
|
||||
case TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME:
|
||||
trace.updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateViewsDeleted() {
|
||||
object.getTrace().updateViewsDeleteRegionBlock(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,15 @@ import ghidra.dbg.target.TargetRegister;
|
|||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.memory.TraceObjectRegister;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||
import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
|
||||
public class DBTraceObjectRegister implements TraceObjectRegister {
|
||||
public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObjectInterface {
|
||||
private final DBTraceObject object;
|
||||
|
||||
public DBTraceObjectRegister(DBTraceObject object) {
|
||||
|
@ -102,4 +104,10 @@ public class DBTraceObjectRegister implements TraceObjectRegister {
|
|||
return TraceMemoryState.values()[TraceObjectInterfaceUtils.getValue(object, snap, KEY_STATE,
|
||||
Integer.class, TraceMemoryState.UNKNOWN.ordinal())];
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||
// TODO: Once we decide how to map registers....
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,14 +107,14 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
|||
@Override
|
||||
public void setName(String name) {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
|
||||
object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
|
||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
||||
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,7 +24,8 @@ import com.google.common.collect.Range;
|
|||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.trace.database.target.*;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
import ghidra.trace.model.Trace.TraceStackChangeType;
|
||||
import ghidra.trace.model.stack.*;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
@ -52,7 +53,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
}
|
||||
|
||||
@Override
|
||||
protected TraceChangeType<TraceStack, Void> getChangedType() {
|
||||
protected TraceChangeType<TraceStack, ?> getChangedType() {
|
||||
return TraceStackChangeType.CHANGED;
|
||||
}
|
||||
|
||||
|
@ -106,7 +107,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
PathMatcher matcher = object.getTargetSchema().searchFor(TargetStackFrame.class, true);
|
||||
List<String> relKeyList =
|
||||
matcher.applyIndices(PathUtils.makeIndex(level)).getSingletonPath();
|
||||
matcher.applyKeys(PathUtils.makeIndex(level)).getSingletonPath();
|
||||
if (relKeyList == null) {
|
||||
throw new IllegalStateException("Could not determine where to create new frame");
|
||||
}
|
||||
|
@ -117,8 +118,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
}
|
||||
|
||||
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
|
||||
// TODO: All attributes or just those known to StackFrame?
|
||||
to.setProgramCounter(from.getProgramCounter());
|
||||
// TODO: All attributes within a given span, intersected to that span?
|
||||
to.setProgramCounter(to.getObject().getLifespan(),
|
||||
from.getProgramCounter(from.getObject().getMaxSnap()));
|
||||
}
|
||||
|
||||
protected void shiftFrameAttributes(int from, int to, int count,
|
||||
|
@ -141,12 +143,13 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) {
|
||||
for (int i = start; i < end; i++) {
|
||||
TraceObjectStackFrame frame = frames.get(i);
|
||||
frame.setProgramCounter(null);
|
||||
frame.setProgramCounter(frame.getObject().getLifespan(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDepth(int depth, boolean atInner) {
|
||||
// TODO: Need a span parameter
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
List<TraceObjectStackFrame> frames = // Want mutable list
|
||||
doGetFrames().collect(Collectors.toCollection(ArrayList::new));
|
||||
|
@ -179,7 +182,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||
protected TraceStackFrame doGetFrame(int level) {
|
||||
TargetObjectSchema schema = object.getTargetSchema();
|
||||
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
|
||||
matcher = matcher.applyIndices(PathUtils.makeIndex(level));
|
||||
matcher = matcher.applyKeys(PathUtils.makeIndex(level));
|
||||
return object.getSuccessors(object.getLifespan(), matcher)
|
||||
.findAny()
|
||||
.map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class))
|
||||
|
|
|
@ -17,6 +17,8 @@ package ghidra.trace.database.stack;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -72,23 +74,25 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
}
|
||||
|
||||
@Override
|
||||
public Address getProgramCounter() {
|
||||
return TraceObjectInterfaceUtils.getValue(object, object.getMaxSnap(),
|
||||
public Address getProgramCounter(long snap) {
|
||||
return TraceObjectInterfaceUtils.getValue(object, snap,
|
||||
TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgramCounter(Address pc) {
|
||||
public void setProgramCounter(Range<Long> span, Address pc) {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
if (pc == Address.NO_ADDRESS) {
|
||||
pc = null;
|
||||
}
|
||||
object.setValue(object.getLifespan(), TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
|
||||
object.setValue(object.getLifespan().intersection(span),
|
||||
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
// TODO: Do I need to add a snap argument?
|
||||
// TODO: One day, we'll have dynamic columns in the debugger
|
||||
/**
|
||||
* I don't use an attribute for this, because there's not a nice way track the "identity" of
|
||||
|
@ -101,7 +105,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
* follow the "same frame" as its level changes.
|
||||
*/
|
||||
try (LockHold hold = object.getTrace().lockRead()) {
|
||||
Address pc = getProgramCounter();
|
||||
Address pc = getProgramCounter(object.getMaxSnap());
|
||||
return pc == null ? null
|
||||
: object.getTrace()
|
||||
.getCommentAdapter()
|
||||
|
@ -111,11 +115,12 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
|
||||
@Override
|
||||
public void setComment(String comment) {
|
||||
// TODO: Do I need to add a span argument?
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
object.getTrace()
|
||||
.getCommentAdapter()
|
||||
.setComment(object.getLifespan(), getProgramCounter(), CodeUnit.EOL_COMMENT,
|
||||
comment);
|
||||
.setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()),
|
||||
CodeUnit.EOL_COMMENT, comment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,23 +129,38 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
|||
return object;
|
||||
}
|
||||
|
||||
protected boolean isPcChange(TraceChangeRecord<?, ?> rec) {
|
||||
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
|
||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
||||
return TargetStackFrame.PC_ATTRIBUTE_NAME.equals(cast.getAffectedObject().getEntryKey());
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
assert affected.getParent() == object;
|
||||
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
|
||||
return false;
|
||||
}
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected long snapFor(TraceChangeRecord<?, ?> rec) {
|
||||
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
|
||||
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap();
|
||||
}
|
||||
return object.getMinSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
|
||||
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType() ||
|
||||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() ||
|
||||
rec.getEventType() == TraceObjectChangeType.DELETED.getType() ||
|
||||
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() &&
|
||||
isPcChange(rec)) {
|
||||
// NB. Affected object may be the wrapped object, or the value entry
|
||||
changeApplies(rec)) {
|
||||
TraceAddressSpace space =
|
||||
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME);
|
||||
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, getStack(), null,
|
||||
null);
|
||||
TraceObjectStack stack = getStack();
|
||||
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack,
|
||||
0L, snapFor(rec));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -212,8 +212,8 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
|
|||
}
|
||||
doUpdateFrameKeys();
|
||||
}
|
||||
manager.trace
|
||||
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this));
|
||||
manager.trace.setChanged(
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this, 0L, getSnap()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,6 +18,8 @@ package ghidra.trace.database.stack;
|
|||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -98,12 +100,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
|||
}
|
||||
|
||||
@Override
|
||||
public Address getProgramCounter() {
|
||||
public Address getProgramCounter(long snap) {
|
||||
return pc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgramCounter(Address pc) {
|
||||
public void setProgramCounter(Range<Long> span, Address pc) {
|
||||
//System.err.println("setPC(threadKey=" + stack.getThread().getKey() + ",snap=" +
|
||||
// stack.getSnap() + ",level=" + level + ",pc=" + pc + ");");
|
||||
manager.trace.assertValidAddress(pc);
|
||||
|
@ -115,7 +117,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
|||
update(PC_COLUMN);
|
||||
}
|
||||
manager.trace.setChanged(
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack));
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
|
||||
stack.getSnap()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,7 +133,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
|
|||
update(COMMENT_COLUMN);
|
||||
}
|
||||
manager.trace.setChanged(
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack));
|
||||
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
|
||||
stack.getSnap()));
|
||||
}
|
||||
|
||||
@Internal
|
||||
|
|
|
@ -205,6 +205,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap);
|
||||
}
|
||||
|
||||
protected void doSetLifespanAndEmit(Range<Long> lifespan) {
|
||||
Range<Long> oldLifespan = getLifespan();
|
||||
doSetLifespan(lifespan);
|
||||
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
|
||||
oldLifespan, lifespan));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTrace getTrace() {
|
||||
return manager.trace;
|
||||
|
@ -247,6 +254,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue getCanonicalParent(long snap) {
|
||||
// TODO: If this is invoked often, perhaps keep as field
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (isRoot()) {
|
||||
return manager.valueStore.getObjectAt(0);
|
||||
}
|
||||
String canonicalKey = path.key();
|
||||
TraceObjectKeyPath canonicalTail = path.parent();
|
||||
return manager.valuesByChild.getLazily(this)
|
||||
.stream()
|
||||
.filter(v -> canonicalKey.equals(v.getEntryKey()))
|
||||
.filter(v -> v.getLifespan().contains(snap))
|
||||
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRoot() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
|
@ -275,10 +301,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
public void setLifespan(Range<Long> lifespan) {
|
||||
// TODO: Could derive fixed attributes from schema and set their lifespans, too....
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
Range<Long> oldLifespan = getLifespan();
|
||||
doSetLifespan(lifespan);
|
||||
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
|
||||
oldLifespan, lifespan));
|
||||
doSetLifespanAndEmit(lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,7 +358,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
@Override
|
||||
public Collection<? extends DBTraceObjectValue> getParents() {
|
||||
return manager.valuesByChild.get(this);
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return manager.valuesByChild.get(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) {
|
||||
|
@ -422,6 +447,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Could/should this return Stream instead?
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(Range<Long> span,
|
||||
String key) {
|
||||
return doGetValues(DBTraceUtils.lowerEndpoint(span), DBTraceUtils.upperEndpoint(span), key);
|
||||
|
@ -638,8 +664,15 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getSuccessors(
|
||||
Range<Long> span, PathPredicates relativePredicates) {
|
||||
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetSuccessors(span, DBTraceObjectValPath.of(), relativePredicates);
|
||||
Stream<? extends DBTraceObjectValPath> succcessors =
|
||||
doGetSuccessors(span, empty, relativePredicates);
|
||||
if (relativePredicates.matches(List.of())) {
|
||||
// Pre-cat the empty path (not the empty stream)
|
||||
return Stream.concat(Stream.of(empty), succcessors);
|
||||
}
|
||||
return succcessors;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -647,7 +680,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
|
||||
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
|
||||
if (nextKeys.isEmpty()) {
|
||||
return null;
|
||||
return Stream.empty();
|
||||
}
|
||||
if (nextKeys.size() != 1) {
|
||||
throw new IllegalArgumentException("predicates must be a singleton");
|
||||
|
@ -663,8 +696,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getOrderedSuccessors(Range<Long> span,
|
||||
TraceObjectKeyPath relativePath, boolean forward) {
|
||||
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetOrderedSuccessors(span, DBTraceObjectValPath.of(),
|
||||
if (relativePath.isRoot()) {
|
||||
return Stream.of(empty); // Not the empty stream
|
||||
}
|
||||
return doGetOrderedSuccessors(span, empty,
|
||||
new PathPattern(relativePath.getKeyList()), forward);
|
||||
}
|
||||
}
|
||||
|
@ -686,22 +723,22 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
if (key == TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME &&
|
||||
value instanceof Address) {
|
||||
address = (Address) value;
|
||||
Object lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
InternalTraceObjectValue lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME);
|
||||
if (!(lengthObj instanceof Integer)) {
|
||||
if (lengthObj == null || !(lengthObj.getValue() instanceof Integer)) {
|
||||
return;
|
||||
}
|
||||
length = (Integer) lengthObj;
|
||||
length = (Integer) lengthObj.getValue();
|
||||
}
|
||||
else if (key == TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME &&
|
||||
value instanceof Integer) {
|
||||
length = (Integer) value;
|
||||
Object addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
InternalTraceObjectValue addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
|
||||
TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME);
|
||||
if (!(addressObj instanceof Address)) {
|
||||
if (addressObj == null || !(addressObj.getValue() instanceof Address)) {
|
||||
return;
|
||||
}
|
||||
address = (Address) addressObj;
|
||||
address = (Address) addressObj.getValue();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
|
@ -715,6 +752,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
protected void emitIfCanonicalInsertion(InternalTraceObjectValue value) {
|
||||
if (value == null || !(value.getValue() instanceof DBTraceObject)) {
|
||||
return;
|
||||
}
|
||||
DBTraceObject child = (DBTraceObject) value.getValue();
|
||||
if (this.path.extend(value.getEntryKey()).equals(child.getCanonicalPath())) {
|
||||
child.emitEvents(
|
||||
new TraceChangeRecord<>(TraceObjectChangeType.INSERTED, null, child, value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
|
||||
ConflictResolution resolution) {
|
||||
|
@ -750,6 +798,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
// NB. It will cause another event. good.
|
||||
applyBreakpointRangeHack(lifespan, key, value, resolution);
|
||||
emitIfCanonicalInsertion(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -783,18 +832,23 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
||||
Class<I> ifClass) {
|
||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
public Stream<? extends DBTraceObjectValPath> queryAncestorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
||||
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
||||
return getAncestors(span, matcher).map(p -> p.getFirstParent(this).queryInterface(ifClass));
|
||||
return getAncestors(span, matcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
||||
Range<Long> span, Class<I> ifClass) {
|
||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
|
||||
Class<I> ifClass) {
|
||||
return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||
.map(p -> p.getFirstParent(this).queryInterface(ifClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
||||
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
||||
List<String> parentPath = getCanonicalPath().getKeyList();
|
||||
|
@ -806,20 +860,35 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath))
|
||||
.stream()
|
||||
.filter(o -> DBTraceUtils.intersect(span, o.getLifespan()))
|
||||
.map(o -> o.queryInterface(ifClass))
|
||||
.filter(i -> i != null);
|
||||
// TODO: Post filter until GP-1301
|
||||
.filter(o -> o.getTargetSchema().getInterfaces().contains(targetIf));
|
||||
}
|
||||
}
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
|
||||
Range<Long> span, Class<I> ifClass) {
|
||||
return queryCanonicalAncestorsTargetInterface(span,
|
||||
TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||
.map(o -> o.queryInterface(ifClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
|
||||
// TODO: Post filter until GP-1301
|
||||
return getSuccessors(span, matcher).filter(
|
||||
p -> p.getLastChild(this).getTargetSchema().getInterfaces().contains(targetIf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I extends TraceObjectInterface> Stream<I> querySuccessorsInterface(Range<Long> span,
|
||||
Class<I> ifClass) {
|
||||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
|
||||
return getSuccessors(span, matcher).map(p -> p.getLastChild(this).queryInterface(ifClass))
|
||||
.filter(i -> i != null); // because GP-1301
|
||||
return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
|
||||
.map(p -> p.getLastChild(this).queryInterface(ifClass));
|
||||
}
|
||||
|
||||
protected void doDelete() {
|
||||
|
@ -869,13 +938,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
List<Range<Long>> removed = DBTraceUtils.subtract(lifespan, span);
|
||||
if (removed.isEmpty()) {
|
||||
doDeleteReferringValues();
|
||||
doDelete();
|
||||
return null;
|
||||
}
|
||||
if (removed.size() == 2) {
|
||||
throw new IllegalArgumentException("Cannot create a gap in an object's lifespan");
|
||||
}
|
||||
doSetLifespan(removed.get(0));
|
||||
doSetLifespanAndEmit(removed.get(0));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,21 +40,39 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
|
||||
protected abstract TraceChangeType<T, Range<Long>> getLifespanChangedType();
|
||||
|
||||
protected abstract TraceChangeType<T, Void> getChangedType();
|
||||
protected abstract TraceChangeType<T, ?> getChangedType();
|
||||
|
||||
protected abstract boolean appliesToKey(String key);
|
||||
|
||||
protected abstract TraceChangeType<T, Void> getDeletedType();
|
||||
|
||||
protected void emitExtraAdded() {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
|
||||
Object newValue) {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
protected void emitExtraDeleted() {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
|
||||
TraceAddressSpace space = spaceValueKey == null ? null
|
||||
: spaceForValue(object, object.getMinSnap(), spaceValueKey);
|
||||
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType()) {
|
||||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType()) {
|
||||
TraceChangeType<T, Void> type = getAddedType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
assert rec.getAffectedObject() == object;
|
||||
emitExtraAdded();
|
||||
return new TraceChangeRecord<>(type, space, iface, null,
|
||||
null);
|
||||
}
|
||||
|
@ -69,6 +87,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
assert rec.getAffectedObject() == object;
|
||||
TraceChangeRecord<TraceObject, Range<Long>> cast =
|
||||
TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec);
|
||||
emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue());
|
||||
return new TraceChangeRecord<>(type, space, iface,
|
||||
cast.getOldValue(), cast.getNewValue());
|
||||
}
|
||||
|
@ -76,17 +95,23 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
if (object.isDeleted()) {
|
||||
return null;
|
||||
}
|
||||
TraceChangeType<T, Void> type = getChangedType();
|
||||
TraceChangeType<T, ?> type = getChangedType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
TraceChangeRecord<TraceObjectValue, Object> cast =
|
||||
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
|
||||
String key = cast.getAffectedObject().getEntryKey();
|
||||
TraceObjectValue affected = cast.getAffectedObject();
|
||||
String key = affected.getEntryKey();
|
||||
if (!appliesToKey(key)) {
|
||||
return null;
|
||||
}
|
||||
assert cast.getAffectedObject().getParent() == object;
|
||||
assert affected.getParent() == object;
|
||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||
return null; // Object is not complete
|
||||
}
|
||||
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
|
||||
cast.getNewValue());
|
||||
return new TraceChangeRecord<>(type, space, iface, null, null);
|
||||
}
|
||||
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
|
||||
|
@ -95,6 +120,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
return null;
|
||||
}
|
||||
assert rec.getAffectedObject() == object;
|
||||
emitExtraDeleted();
|
||||
return new TraceChangeRecord<>(type, space, iface, null, null);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -43,7 +43,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd
|
|||
import ghidra.trace.database.module.TraceObjectSection;
|
||||
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
|
@ -61,6 +60,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec;
|
||||
|
@ -235,7 +235,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
}
|
||||
|
||||
protected Object validatePrimitive(Object child) {
|
||||
PrimitiveCodec.getCodec(child.getClass());
|
||||
try {
|
||||
PrimitiveCodec.getCodec(child.getClass());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Cannot encode " + child, e);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
|
@ -300,7 +305,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
setSchema(schema);
|
||||
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all());
|
||||
assert root.getKey() == 0;
|
||||
InternalTraceObjectValue val = doCreateValue(Range.all(), null, null, root);
|
||||
InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
|
||||
assert val.getKey() == 0;
|
||||
return val;
|
||||
}
|
||||
|
@ -378,6 +383,14 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
|
||||
PathMatcher matcher = rootSchema.searchFor(targetIf, true);
|
||||
return getValuePaths(span, matcher)
|
||||
.filter(p -> {
|
||||
TraceObject object = p.getLastChild(getRootObject());
|
||||
if (object == null) {
|
||||
Msg.error(this, "NULL VALUE! " + p.getLastEntry());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass));
|
||||
}
|
||||
|
||||
|
@ -561,15 +574,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
try (LockHold hold = trace.lockWrite()) {
|
||||
TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan,
|
||||
TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE);
|
||||
/**
|
||||
* TODO: Test that when the ADDED events hits, that it actually appears in queries. I
|
||||
* suspect this will work since the events and/or event processors should be delayed
|
||||
* until the write lock is released. Certainly, a query would require the read lock.
|
||||
*/
|
||||
region.setName(path);
|
||||
region.setRange(range);
|
||||
region.setFlags(flags);
|
||||
trace.updateViewsAddRegionBlock(region);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.io.IOException;
|
|||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
@ -48,7 +49,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
|
||||
protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) {
|
||||
this.parent = parent;
|
||||
this.key = key;
|
||||
this.key = Objects.requireNonNull(key);
|
||||
this.minSnap = minSnap;
|
||||
}
|
||||
|
||||
|
@ -75,19 +76,15 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value.key == null) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * 2);
|
||||
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
|
||||
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
byte[] keyBytes = value.key.getBytes(cs);
|
||||
ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2);
|
||||
|
||||
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
|
||||
|
||||
buf.put(keyBytes);
|
||||
buf.put((byte) 0);
|
||||
|
||||
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
|
||||
|
||||
return buf.array();
|
||||
|
@ -101,16 +98,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
|
||||
DBTraceObject parent =
|
||||
DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE);
|
||||
String key;
|
||||
if (enc.length > Long.BYTES * 2) {
|
||||
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
|
||||
assert nullPos != -1;
|
||||
key = new String(enc, buf.position(), nullPos - buf.position(), cs);
|
||||
buf.position(nullPos + 1);
|
||||
}
|
||||
else {
|
||||
key = null;
|
||||
}
|
||||
|
||||
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
|
||||
assert nullPos != -1;
|
||||
String key = new String(enc, buf.position(), nullPos - buf.position(), cs);
|
||||
buf.position(nullPos + 1);
|
||||
|
||||
long minSnap = buf.getLong() ^ Long.MIN_VALUE;
|
||||
|
||||
return new PrimaryTriple(parent, key, minSnap);
|
||||
|
@ -186,14 +179,17 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
indexed = true,
|
||||
codec = PrimaryTripleDBFieldCodec.class)
|
||||
private PrimaryTriple triple;
|
||||
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
|
||||
@DBAnnotatedField(
|
||||
column = MAX_SNAP_COLUMN_NAME)
|
||||
private long maxSnap;
|
||||
@DBAnnotatedField(
|
||||
column = CHILD_COLUMN_NAME,
|
||||
indexed = true,
|
||||
codec = DBTraceObjectDBFieldCodec.class)
|
||||
private DBTraceObject child;
|
||||
@DBAnnotatedField(column = PRIMITIVE_COLUMN_NAME, codec = VariantDBFieldCodec.class)
|
||||
@DBAnnotatedField(
|
||||
column = PRIMITIVE_COLUMN_NAME,
|
||||
codec = VariantDBFieldCodec.class)
|
||||
private Object primitive;
|
||||
|
||||
protected final DBTraceObjectManager manager;
|
||||
|
@ -260,12 +256,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return triple.parent;
|
||||
return triple == null ? null : triple.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryKey() {
|
||||
return triple.key;
|
||||
return triple == null ? null : triple.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -71,10 +71,13 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
|||
}
|
||||
}
|
||||
else {
|
||||
entry.doTruncateOrDelete(range);
|
||||
InternalTraceObjectValue created = entry.doTruncateOrDelete(range);
|
||||
if (!entry.isDeleted()) {
|
||||
kept.add(entry);
|
||||
}
|
||||
if (created != null) {
|
||||
kept.add(created);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue