ghidra/Ghidra/Debug/Debugger/garbage/DefaultTraceRecorderPre666Onto741.java

2598 lines
84 KiB
Java

/* ###
* 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;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.collect.*;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener;
import ghidra.async.*;
import ghidra.async.AsyncLazyMap.KeyedFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
import ghidra.dbg.DebugModelConventions.SubTreeListenerAdapter;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.attributes.TargetObjectList;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.error.DebuggerModelAccessException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.listing.TraceCodeManager;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.stack.*;
import ghidra.trace.model.symbol.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class DefaultTraceRecorderOnto741 implements TraceRecorder {
private static final boolean LOG_STACK_TRACE = false;
// For large memory captures
private static final int BLOCK_SIZE = 4096;
private static final long BLOCK_MASK = -1L << 12;
static final PathMatcher HARDCODED_MATCHER = new PathMatcher() {
{
// Paths for GDB
addPattern(PathUtils.parse("Breakpoints[]."));
addPattern(PathUtils.parse("Inferiors[].Memory[]"));
addPattern(PathUtils.parse("Inferiors[].Modules[].Sections[]"));
addPattern(PathUtils.parse("Inferiors[].Registers[]"));
addPattern(PathUtils.parse("Inferiors[].Threads[]"));
addPattern(PathUtils.parse("Inferiors[].Threads[].Stack[]"));
// Paths for dbgeng
addPattern(PathUtils.parse("Sessions[].Processes[].Memory[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Modules[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Registers[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Stack[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Debug.Breakpoints[]"));
// (Additional) paths for dbgmodel
addPattern(PathUtils.parse("Sessions[].Attributes"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Stack.Frames[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].TTD.Position"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Registers.User."));
// Paths for JDI
addPattern(PathUtils.parse("VirtualMachines[]"));
addPattern(PathUtils.parse("VirtualMachines[].Breakpoints"));
addPattern(PathUtils.parse("VirtualMachines[].Classes[]"));
addPattern(PathUtils.parse("VirtualMachines[].Classes[].Sections[]"));
addPattern(PathUtils.parse("VirtualMachines[].Threads[]"));
addPattern(PathUtils.parse("VirtualMachines[].Threads[].Registers[]"));
addPattern(PathUtils.parse("VirtualMachines[].Threads[].Stack[]"));
}
};
protected static class PermanentTransaction implements AutoCloseable {
static PermanentTransaction start(Trace trace, String description) {
UndoableTransaction tid = null;
try {
tid = UndoableTransaction.start(trace, description, true);
}
catch (Throwable t) {
tid.close();
return ExceptionUtils.rethrow(t);
}
return new PermanentTransaction(trace, tid);
}
private final Trace trace;
private final UndoableTransaction tid;
public PermanentTransaction(Trace trace, UndoableTransaction tid) {
this.trace = trace;
this.tid = tid;
}
@Override
public void close() {
tid.close();
trace.clearUndo();
}
}
protected final AsyncLazyMap<TargetRegisterBank, AllRequiredAccess> accessibilityByRegBank =
new AsyncLazyMap<>(new HashMap<>(), this::fetchRegAccessibility) {
public AllRequiredAccess remove(TargetRegisterBank key) {
AllRequiredAccess acc = super.remove(key);
if (acc != null) {
acc.removeChangeListener(listenerRegAccChanged);
}
return acc;
}
};
protected final Map<TargetMemoryRegion, TargetMemory> byRegion = new HashMap<>();
protected final AsyncLazyMap<TargetMemory, AllRequiredAccess> accessibilityByMemory =
new AsyncLazyMap<>(new HashMap<>(), this::fetchMemAccessibility) {
public AllRequiredAccess remove(TargetMemory key) {
AllRequiredAccess acc = super.remove(key);
if (acc != null) {
acc.removeChangeListener(processMemory.memAccListeners.fire);
}
return acc;
}
};
protected CompletableFuture<AllRequiredAccess> fetchRegAccessibility(TargetRegisterBank bank) {
return DebugModelConventions.trackAccessibility(bank).thenApply(acc -> {
acc.addChangeListener(listenerRegAccChanged);
return acc;
});
}
protected CompletableFuture<AllRequiredAccess> fetchMemAccessibility(TargetMemory mem) {
return DebugModelConventions.trackAccessibility(mem).thenApply(acc -> {
acc.addChangeListener(processMemory.memAccListeners.fire);
return acc;
});
}
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @return the computed set
*/
protected AddressSet getAccessibleMemory(Predicate<TargetMemory> pred) {
synchronized (accessibilityByMemory) {
// TODO: Might accomplish by using listeners and tracking the accessible set
AddressSet accessible = new AddressSet();
for (Entry<TargetMemoryRegion, TargetMemory> ent : byRegion.entrySet()) {
TargetMemory mem = ent.getValue();
if (!pred.test(mem)) {
continue;
}
AllRequiredAccess acc = accessibilityByMemory.getCompletedMap().get(mem);
if (acc == null || !acc.getAllAccessibility()) {
continue;
}
accessible.add(memMapper.targetToTrace(ent.getKey().getRange()));
}
return accessible;
}
}
protected class ComposedMemory {
protected final ComposedMemory chain;
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final ListenerSet<TriConsumer<Boolean, Boolean, Void>> memAccListeners =
new ListenerSet(TriConsumer.class);
public ComposedMemory() {
this.chain = null;
}
public ComposedMemory(ComposedMemory chain) {
this.chain = chain;
}
protected void addRegion(TargetMemoryRegion region, TargetMemory memory) {
synchronized (accessibilityByMemory) {
TargetMemory old = byRegion.put(region, memory);
assert old == null;
byMin.put(region.getRange().getMinAddress(), region);
accessibilityByMemory.get(memory).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
Msg.error(this, "Could not track memory accessibility: " + e.getMessage());
return null;
});
}
}
protected boolean removeRegion(TargetObject invalid) {
if (!(invalid instanceof TargetMemoryRegion)) {
return false;
}
synchronized (accessibilityByMemory) {
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
TargetMemory old = byRegion.remove(invRegion);
assert old != null;
byMin.remove(invRegion.getRange().getMinAddress());
if (!old.isValid() || !byRegion.containsValue(old)) {
accessibilityByMemory.remove(old);
}
return true;
}
}
protected AllRequiredAccess findChainedMemoryAccess(TargetMemoryRegion region) {
synchronized (accessibilityByMemory) {
TargetMemory mem = byRegion.get(region);
if (mem != null) {
return accessibilityByMemory.getCompletedMap().get(mem);
}
return chain == null ? null : chain.findChainedMemoryAccess(region);
}
}
protected Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
synchronized (accessibilityByMemory) {
Entry<Address, TargetMemoryRegion> myFloor = byMin.floorEntry(address);
Entry<Address, TargetMemoryRegion> byChain =
chain == null ? null : chain.findChainedFloor(address);
if (byChain == null) {
return myFloor;
}
if (myFloor == null) {
return byChain;
}
int c = myFloor.getKey().compareTo(byChain.getKey());
if (c < 0) {
return byChain;
}
return myFloor;
}
}
protected AddressRange align(Address address, int length) {
AddressSpace space = address.getAddressSpace();
long offset = address.getOffset();
Address start = space.getAddress(offset & BLOCK_MASK);
Address end = space.getAddress(((offset + length - 1) & BLOCK_MASK) + BLOCK_SIZE - 1);
return new AddressRangeImpl(start, end);
}
protected AddressRange alignWithLimit(Address address, int length,
TargetMemoryRegion limit) {
return align(address, length).intersect(limit.getRange());
}
protected AddressRange alignAndLimitToFloor(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
return null;
}
return alignWithLimit(address, length, floor.getValue());
}
protected AddressRange alignWithOptionalLimit(Address address, int length,
TargetMemoryRegion limit) {
if (limit == null) {
return alignAndLimitToFloor(address, length);
}
return alignWithLimit(address, length, limit);
}
protected CompletableFuture<byte[]> readMemory(Address address, int length) {
synchronized (accessibilityByMemory) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
throw new IllegalArgumentException(
"address " + address + " is not in any known region");
}
Address max;
try {
max = address.addNoWrap(length - 1);
}
catch (AddressOverflowException e) {
throw new IllegalArgumentException("read extends beyond the address space");
}
if (!floor.getValue().getRange().contains(max)) {
throw new IllegalArgumentException("read extends beyond a single region");
}
TargetMemory mem = byRegion.get(floor.getValue());
if (mem != null) {
return mem.readMemory(address, length);
}
return CompletableFuture.completedFuture(new byte[0]);
}
}
protected CompletableFuture<Void> writeMemory(Address address, byte[] data) {
synchronized (accessibilityByMemory) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
throw new IllegalArgumentException(
"address " + address + " is not in any known region");
}
Address max;
try {
max = address.addNoWrap(data.length - 1);
}
catch (AddressOverflowException e) {
throw new IllegalArgumentException("read extends beyond the address space");
}
if (!floor.getValue().getRange().contains(max)) {
throw new IllegalArgumentException("read extends beyond a single region");
}
TargetMemory mem = byRegion.get(floor.getValue());
if (mem != null) {
return mem.writeMemory(address, data);
}
throw new IllegalArgumentException("read starts outside any address space");
}
}
}
protected static class ThreadMap {
protected final Map<TargetThread, ThreadRecorder> byTargetThread = new HashMap<>();
protected final Map<TraceThread, ThreadRecorder> byTraceThread = new HashMap<>();
public void put(ThreadRecorder rec) {
byTargetThread.put(rec.targetThread, rec);
byTraceThread.put(rec.traceThread, rec);
}
public ThreadRecorder getForSuccessor(TargetObject successor) {
while (successor != null) {
ThreadRecorder rec = byTargetThread.get(successor);
if (rec != null) {
return rec;
}
successor = successor.getParent();
}
return null;
}
public ThreadRecorder get(TargetThread thread) {
return byTargetThread.get(thread);
}
public ThreadRecorder get(TargetObject maybeThread) {
return byTargetThread.get(maybeThread);
}
public ThreadRecorder get(TraceThread thread) {
return byTraceThread.get(thread);
}
public void remove(ThreadRecorder rec) {
ThreadRecorder rByTarget = byTargetThread.remove(rec.targetThread);
ThreadRecorder rByTrace = byTraceThread.remove(rec.traceThread);
assert rec == rByTarget;
assert rec == rByTrace;
}
public Collection<ThreadRecorder> recorders() {
return byTargetThread.values();
}
}
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;
}
protected static AddressRange range(Address min, Integer length) {
if (length == null) {
length = 1;
}
try {
return new AddressRangeImpl(min, length);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
protected static String nameBreakpoint(TargetBreakpointLocation bpt) {
if (bpt instanceof TargetBreakpointSpec) {
return bpt.getIndex();
}
return bpt.getSpecification().getIndex() + "." + bpt.getIndex();
}
protected static int getFrameLevel(TargetStackFrame frame) {
// TODO: A fair assumption? frames are elements with numeric base-10 indices
return Integer.decode(frame.getIndex());
}
protected class ThreadRecorder {
protected final TargetThread targetThread;
protected final TraceThread traceThread;
protected DebuggerRegisterMapper regMapper;
protected TargetRegister pcReg;
protected TargetRegister spReg;
protected Map<Integer, TargetRegisterBank> regs = new HashMap<>();
protected NavigableMap<Integer, TargetStackFrame> stack =
Collections.synchronizedNavigableMap(new TreeMap<>());
protected final ComposedMemory threadMemory = new ComposedMemory(processMemory);
protected TargetBreakpointContainer threadBreakpointContainer;
protected TargetExecutionState state = TargetExecutionState.ALIVE;
protected ThreadRecorder(TargetThread targetThread, TraceThread traceThread) {
this.targetThread = targetThread;
this.traceThread = traceThread;
if (targetThread instanceof TargetExecutionStateful) {
TargetExecutionStateful stateful = (TargetExecutionStateful) targetThread;
state = stateful.getExecutionState();
}
}
protected synchronized CompletableFuture<Void> initRegMapper(
TargetRegisterContainer registers) {
/**
* TODO: At the moment, this assumes the recorded thread has one register container, or
* at least that all register banks in the thread use the same register container
* (descriptors). If this becomes a problem, then we'll need to keep a separate register
* mapper per register container. This would likely also require some notion of multiple
* languages in the mapper (seems an unlikely design choice). NOTE: In cases where a
* single process may (at least appear to) execute multiple languages, the model should
* strive to present the registers of the physical machine, as they are most likely
* uniform across the process, not those being emulated in the moment. In cases where an
* abstract machine is involved, it is probably more fitting to present separate
* containers (likely provided by separate models) than to present both the physical and
* abstract machine in the same target.
*
* <p>
* TODO: Should I formalize that only one register container is present in a recorded
* thread? This seems counter to the model's flexibility. Traces allow polyglot
* disassembly, but not polyglot register spaces.
*/
/*if (regMapper != null) {
return AsyncUtils.NIL;
}*/
return regMappers.get(registers).thenAccept(rm -> {
synchronized (this) {
regMapper = rm;
Language language = trace.getBaseLanguage();
pcReg = regMapper.traceToTarget(language.getProgramCounter());
spReg = regMapper.traceToTarget(trace.getBaseCompilerSpec().getStackPointer());
extraRegs = new LinkedHashSet<>();
for (String rn : mapper.getExtraRegNames()) {
Register traceReg = language.getRegister(rn);
if (traceReg == null) {
Msg.error(this,
"Mapper's extra register '" + rn + "' is not in the language!");
continue;
}
TargetRegister targetReg = regMapper.traceToTarget(traceReg);
if (targetReg == null) {
Msg.error(this,
"Mapper's extra register '" + traceReg + "' is not mappable!");
continue;
}
extraRegs.add(targetReg);
}
}
listenerForRecord.retroOfferRegMapperDependents();
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex);
return null;
});
}
protected void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg,
boolean removed) {
boolean doUpdateRegs = false;
String name = reg.getIndex();
synchronized (this) {
if (regMapper != rm) {
return;
}
TargetRegister newPcReg =
regMapper.traceToTarget(trace.getBaseLanguage().getProgramCounter());
if (pcReg != newPcReg) {
pcReg = newPcReg;
doUpdateRegs |= pcReg != null;
}
TargetRegister newSpReg =
regMapper.traceToTarget(trace.getBaseCompilerSpec().getStackPointer());
if (spReg != newSpReg) {
spReg = newSpReg;
doUpdateRegs |= spReg != null;
}
if (mapper.getExtraRegNames().contains(name)) {
if (removed) {
extraRegs.remove(reg);
}
else {
extraRegs.add(reg);
}
doUpdateRegs = true;
}
}
if (removed) {
return;
}
TargetRegisterBank bank = regs.get(0);
if (bank != null) {
byte[] cachedVal = bank.getCachedRegisters().get(name);
if (cachedVal != null) {
recordRegisterValues(bank, Map.of(name, cachedVal));
}
if (doUpdateRegs) {
updateRegsMem(null);
}
}
// TODO: This may be too heavy-handed
// listenerForRecord.retroOfferRegMapperDependents();
}
protected int getSuccessorFrameLevel(TargetObject successor) {
NavigableSet<Integer> observedPathLengths = new TreeSet<>();
for (TargetStackFrame frame : stack.values()) {
observedPathLengths.add(frame.getPath().size());
}
List<String> path = successor.getPath();
for (int l : observedPathLengths.descendingSet()) {
if (l > path.size()) {
continue;
}
List<String> sub = path.subList(0, l);
if (!PathUtils.isIndex(sub)) {
continue;
}
int index = Integer.decode(PathUtils.getIndex(sub));
TargetStackFrame frame = stack.get(index);
if (frame == null || !Objects.equals(sub, frame.getPath())) {
continue;
}
return index;
}
return 0;
}
CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank bank) {
int frameLevel = getSuccessorFrameLevel(bank);
TargetRegisterContainer descs = bank.getDescriptions();
if (descs == null) {
Msg.error(this, "Cannot create mapper, yet: Descriptions is null.");
return AsyncUtils.NIL;
}
return initRegMapper(descs).thenAccept(__ -> {
if (frameLevel == 0) {
recordRegisterValues(bank, bank.getCachedRegisters());
updateRegsMem(null);
}
listeners.fire.registerBankMapped(DefaultTraceRecorder.this);
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex);
return null;
});
}
protected void offerRegisters(TargetRegisterBank newRegs) {
int frameLevel = getSuccessorFrameLevel(newRegs);
if (regs.isEmpty()) {
// TODO: Technically, each frame may need its own mapper....
doFetchAndInitRegMapper(newRegs);
}
TargetRegisterBank oldRegs = regs.put(frameLevel, newRegs);
if (oldRegs == newRegs) {
return;
}
synchronized (accessibilityByRegBank) {
if (oldRegs != null) {
accessibilityByRegBank.remove(oldRegs);
}
accessibilityByRegBank.get(newRegs).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
Msg.error(this, "Could not track register accessibility: " + e.getMessage());
return null;
});
}
}
protected void offerStackFrame(TargetStackFrame frame) {
stack.put(getFrameLevel(frame), frame);
recordFrame(frame);
}
protected void offerThreadRegion(TargetMemoryRegion region) {
TargetMemory mem = region.getMemory();
threadMemory.addRegion(region, mem);
initMemMapper(mem);
// TODO: Add region to trace memory manager (when allowed for threads)
updateRegsMem(region).exceptionally(ex -> {
Msg.error(this, "Could not add thread memory region", ex);
return null;
});
}
protected void offerThreadBreakpointContainer(TargetBreakpointContainer bc) {
if (threadBreakpointContainer != null) {
Msg.warn(this, "Thread already has a breakpoint container");
}
threadBreakpointContainer = bc;
}
/**
* Inform the recorder the given object is no longer valid
*
* @param invalid the invalidated object
* @return true if this recorder should be invalidated, too
*/
protected synchronized boolean objectRemoved(TargetObject invalid) {
if (checkThreadRemoved(invalid)) {
return true;
}
if (checkRegistersRemoved(invalid)) {
//return false;
// Regs could also be a stack frame
}
if (checkStackFrameRemoved(invalid)) {
return false;
}
if (threadMemory.removeRegion(invalid)) {
return false;
}
Msg.trace(this, "Ignored removed object: " + invalid);
return false;
}
protected boolean checkThreadRemoved(TargetObject invalid) {
if (targetThread == invalid) {
threadDestroyed();
return true;
}
return false;
}
protected boolean checkRegistersRemoved(TargetObject invalid) {
synchronized (accessibilityByRegBank) {
if (regs.values().remove(invalid)) {
accessibilityByRegBank.remove((TargetRegisterBank) invalid);
return true;
}
return false;
}
}
protected boolean checkStackFrameRemoved(TargetObject invalid) {
if (stack.values().remove(invalid)) {
popStack();
return true;
}
return false;
}
protected Address pcFromStack() {
TargetStackFrame frame = stack.get(0);
if (frame == null) {
return null;
}
return frame.getProgramCounter();
}
protected boolean checkReadCondition(Address traceAddress) {
/**
* TODO: This heuristic doesn't really belong here, but I have to implement it here so
* that it doesn't "override" the listing's implementation. Once watches are
* implemented, we should be able to drop this garbage.
*/
TraceMemoryRegion region =
memoryManager.getRegionContaining(snapshot.getKey(), traceAddress);
if (region == null) {
return false;
}
if (region.isWrite()) {
return true;
}
Entry<TraceAddressSnapRange, TraceMemoryState> ent =
memoryManager.getMostRecentStateEntry(snapshot.getKey(), traceAddress);
if (ent == null) {
return true;
}
if (ent.getValue() == TraceMemoryState.KNOWN) {
return false;
}
return true;
}
protected CompletableFuture readAlignedConditionally(String name, Address targetAddress,
TargetMemoryRegion limit) {
if (targetAddress == null) {
return AsyncUtils.NIL;
}
Address traceAddress = memMapper.targetToTrace(targetAddress);
if (traceAddress == null) {
return AsyncUtils.NIL;
}
if (!checkReadCondition(traceAddress)) {
return AsyncUtils.NIL;
}
AddressRange targetRange = threadMemory.alignWithOptionalLimit(targetAddress, 1, limit);
if (targetRange == null) {
return AsyncUtils.NIL;
}
TimedMsg.info(this,
" Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")");
// NOTE: Recorder takes data via memoryUpdated callback
// TODO: In that callback, sort out process memory from thread memory?
return threadMemory
.readMemory(targetRange.getMinAddress(), (int) targetRange.getLength())
.exceptionally(ex -> {
Msg.error(this, "Could not read memory at " + name, ex);
return null;
});
}
Address registerValueToTargetAddress(TargetRegister reg, byte[] value) {
/**
* TODO: This goes around the horn and back just to select a default address space. We
* should really just go directly to target address space.
*/
RegisterValue rv = regMapper.targetToTrace(reg, value);
if (rv == null) {
return null;
}
Address traceAddress = trace.getBaseLanguage()
.getDefaultSpace()
.getAddress(rv.getUnsignedValue().longValue());
return memMapper.traceToTarget(traceAddress);
}
protected CompletableFuture<Void> updateRegsMem(TargetMemoryRegion limit) {
TargetRegisterBank bank;
TargetRegister pc;
TargetRegister sp;
Set<TargetRegister> toRead = new LinkedHashSet<>();
synchronized (DefaultTraceRecorder.this) {
if (regMapper == null) {
return AsyncUtils.NIL;
}
bank = regs.get(0);
pc = pcReg;
sp = spReg;
toRead.addAll(extraRegs);
toRead.add(sp);
toRead.add(pc);
}
if (bank == null || pc == null || sp == null) {
return AsyncUtils.NIL;
}
TimedMsg.info(this, "Reading " + toRead + " of " + targetThread);
return bank.readRegisters(toRead).thenCompose(vals -> {
synchronized (DefaultTraceRecorder.this) {
if (memMapper == null) {
return AsyncUtils.NIL;
}
}
if (threadMemory == null) {
return AsyncUtils.NIL;
}
AsyncFence fence = new AsyncFence();
Address pcTargetAddr = pcFromStack();
if (pcTargetAddr == null) {
pcTargetAddr = registerValueToTargetAddress(pcReg, vals.get(pcReg.getIndex()));
}
fence.include(readAlignedConditionally("PC", pcTargetAddr, limit));
Address spTargetAddr =
registerValueToTargetAddress(spReg, vals.get(spReg.getIndex()));
fence.include(readAlignedConditionally("SP", spTargetAddr, limit));
return fence.ready();
}).exceptionally(ex -> {
if (LOG_STACK_TRACE) {
Msg.error(this, "Could not read registers", ex);
}
else {
Msg.error(this, "Could not read registers");
}
return null;
});
}
public void stateChanged(final TargetExecutionState newState) {
if (newState == TargetExecutionState.STOPPED) {
updateRegsMem(null);
}
state = newState;
}
public void threadDestroyed() {
String path = PathUtils.toString(targetThread.getPath());
try (PermanentTransaction tid =
PermanentTransaction.start(trace, path + " destroyed")) {
// TODO: Should it be key - 1
// Perhaps, since the thread should not exist
// But it could imply earlier destruction than actually observed
traceThread.setDestructionSnap(snapshot.getKey());
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be shrinking
}
}
public void recordRegisterValues(TargetObject bank, Map<String, byte[]> updates) {
synchronized (DefaultTraceRecorder.this) {
if (regMapper == null) {
return;
}
}
int frameLevel = getSuccessorFrameLevel(bank);
TimedMsg.info(this, "Reg values changed: " + updates.keySet());
try (PermanentTransaction tid = PermanentTransaction.start(trace,
"Registers changed in " + PathUtils.toString(bank.getPath()))) {
TraceMemoryRegisterSpace regSpace =
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, true);
for (Entry<String, byte[]> ent : updates.entrySet()) {
RegisterValue rv = regMapper.targetToTrace(ent.getKey(), ent.getValue());
if (rv == null) {
continue; // mapper does not know this register....
}
regSpace.setValue(snapshot.getKey(), rv);
if (rv.getRegister() == trace.getBaseLanguage().getProgramCounter() &&
pcFromStack() == null) {
Address pcTargetAddr = registerValueToTargetAddress(pcReg, ent.getValue());
readAlignedConditionally("PC", pcTargetAddr, null); // NB: Reports errors
}
if (rv.getRegister() == trace.getBaseCompilerSpec().getStackPointer()) {
Address spTargetAddr = registerValueToTargetAddress(spReg, ent.getValue());
readAlignedConditionally("SP", spTargetAddr, null); // NB: Reports errors
}
}
}
}
public void recordFrame(TargetStackFrame frame) {
recordFrame(frame, frame.getProgramCounter());
}
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
traceFrame.setProgramCounter(pc);
}
public void recordFrame(TargetStackFrame frame, Address pc) {
synchronized (DefaultTraceRecorder.this) {
if (memMapper == null) {
return;
}
Address tracePc = pc == null ? null : memMapper.targetToTrace(pc);
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Stack frame added")) {
TraceStack traceStack =
stackManager.getStack(traceThread, snapshot.getKey(), true);
doRecordFrame(traceStack, getFrameLevel(frame), tracePc);
}
}
}
protected int stackDepth() {
return stack.isEmpty() ? 0 : stack.lastKey() + 1;
}
public void recordStack() {
synchronized (DefaultTraceRecorder.this) {
if (memMapper == null) {
return;
}
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Stack changed")) {
TraceStack traceStack =
stackManager.getStack(traceThread, snapshot.getKey(), true);
traceStack.setDepth(stackDepth(), false);
for (Map.Entry<Integer, TargetStackFrame> ent : stack.entrySet()) {
Address tracePc =
memMapper.targetToTrace(ent.getValue().getProgramCounter());
doRecordFrame(traceStack, ent.getKey(), tracePc);
}
}
}
}
public void popStack() {
synchronized (DefaultTraceRecorder.this) {
try (PermanentTransaction tid = PermanentTransaction.start(trace, "Stack popped")) {
TraceStack traceStack =
stackManager.getStack(traceThread, snapshot.getKey(), true);
traceStack.setDepth(stackDepth(), false);
}
}
}
public void onThreadBreakpointContainers(
Consumer<? super TargetBreakpointContainer> action) {
if (threadBreakpointContainer == null) {
return;
}
action.accept(threadBreakpointContainer);
}
}
protected class EffectiveBreakpointResolver {
private final TargetBreakpointLocation bpt;
private TargetBreakpointSpec spec;
private boolean affectsProcess = false;
private final Set<TraceThread> threadsAffected = new LinkedHashSet<>();
public EffectiveBreakpointResolver(TargetBreakpointLocation bpt) {
this.bpt = bpt;
}
public CompletableFuture<Void> resolve() {
AsyncFence fence = new AsyncFence();
this.spec = bpt.getSpecification();
for (TargetObject ref : bpt.getAffects()) {
if (ref.equals(target)) {
affectsProcess = true;
}
else {
fence.include(resolveThread(ref));
}
}
return fence.ready();
}
// TODO: If affects is empty/null, also try to default to the containing process
private CompletableFuture<Void> resolveThread(TargetObject ref) {
return DebugModelConventions.findThread(ref).thenAccept(thread -> {
if (thread == null) {
Msg.error(this,
"Could not find process or thread from breakpoint-affected object: " + ref);
return;
}
if (!ref.equals(thread)) {
Msg.warn(this, "Effective breakpoint should apply to process or threads. Got " +
ref + ". Resolved to " + thread);
return;
}
if (!PathUtils.isAncestor(target.getPath(), thread.getPath())) {
/**
* Perfectly normal if the breakpoint container is outside the process
* container. Don't record such in this trace, though.
*/
return;
}
ThreadRecorder rec = listenerForRecord.getOrCreateThreadRecorder(thread);
synchronized (threadsAffected) {
threadsAffected.add(rec.traceThread);
}
}).exceptionally(ex -> {
Msg.error(this, "Error resolving thread from breakpoint-affected object: " + ref);
return null;
});
}
public void applyChecksAndConventions() {
if (affectsProcess && !threadsAffected.isEmpty()) {
Msg.warn(this, "Breakpoint affects process and individual threads?: " + bpt);
threadsAffected.clear();
}
// Check ancestry for "affects"
if (!affectsProcess && threadsAffected.isEmpty()) {
if (PathUtils.isAncestor(target.getPath(), bpt.getPath())) {
for (ThreadRecorder rec : threadMap.byTargetThread.values()) {
if (PathUtils.isAncestor(rec.targetThread.getPath(), bpt.getPath())) {
threadsAffected.add(rec.traceThread);
break; // Only one thread could be its ancestor
}
}
if (threadsAffected.isEmpty()) {
affectsProcess = true;
}
}
}
}
}
public class ListenerForRecord extends SubTreeListenerAdapter implements DebuggerModelListener {
//protected final Map<String, TargetModule> modulesByName = new HashMap<>();
protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>();
@Override
protected boolean checkFire(TargetObject object) {
return true;
}
@Override
protected boolean checkDescend(TargetObject ref) {
// NOTE, cannot return false on match, since it could be a prefix of another
if (HARDCODED_MATCHER.successorCouldMatch(ref.getPath())) {
return true;
}
return false;
}
// TODO: Move this into conventions?
protected CompletableFuture<TargetObject> findThreadOrProcess(TargetObject successor) {
return new DebugModelConventions.AncestorTraversal<TargetObject>(successor) {
@Override
protected Result check(TargetObject obj) {
if (obj.isRoot()) {
return Result.FOUND;
}
if (obj instanceof TargetThread) {
return Result.FOUND;
}
if (obj instanceof TargetProcess) {
return Result.FOUND;
}
return Result.CONTINUE;
}
@Override
protected TargetObject finish(TargetObject obj) {
return obj;
}
}.start();
}
@Override
protected void objectAdded(TargetObject added) {
if (!valid) {
return;
}
if (added instanceof TargetThread) {
getOrCreateThreadRecorder((TargetThread) added);
}
if (added instanceof TargetStack) {
// Actually, this may not matter
}
// Do stack frame first, since bank would be it or child.
// Need frames indexed first to determine level of bank
if (added instanceof TargetStackFrame) {
ThreadRecorder rec = threadMap.getForSuccessor(added);
if (rec == null) {
Msg.error(this, "Frame without thread?: " + added);
}
else {
rec.offerStackFrame((TargetStackFrame) added);
}
}
if (added instanceof TargetRegisterBank) {
ThreadRecorder rec = threadMap.getForSuccessor(added);
if (rec == null) {
Msg.error(this, "Bank without thread?: " + added);
}
else {
rec.offerRegisters((TargetRegisterBank) added);
}
}
if (added instanceof TargetRegisterContainer) {
// These are picked up when a bank is added with these descriptions
}
if (added instanceof TargetRegister) {
TargetRegister reg = (TargetRegister) added;
regMappers.get(reg.getContainer()).thenAccept(rm -> {
rm.targetRegisterAdded(reg);
for (ThreadRecorder rec : threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, reg, false);
}
});
}
if (added instanceof TargetMemory) {
initMemMapper((TargetMemory) added);
}
if (added instanceof TargetMemoryRegion) {
TargetMemoryRegion region = (TargetMemoryRegion) added;
findThreadOrProcess(added).thenAccept(obj -> {
if (obj == target) {
offerProcessRegion(region);
return;
}
if (obj instanceof TargetThread) {
ThreadRecorder rec = getOrCreateThreadRecorder((TargetThread) obj);
rec.offerThreadRegion(region);
}
}).exceptionally(ex -> {
Msg.error(this, "Error recording memory region", ex);
return null;
});
}
if (added instanceof TargetModule) {
TargetModule module = (TargetModule) added;
offerProcessModule(module);
}
if (added instanceof TargetSection) {
TargetSection section = (TargetSection) added;
offerProcessModuleSection(section.getModule(), section);
}
if (added instanceof TargetBreakpointContainer) {
TargetBreakpointContainer breaks = (TargetBreakpointContainer) added;
findThreadOrProcess(added).thenAccept(obj -> {
if (obj == target) {
offerProcessBreakpointContainer(breaks);
return;
}
if (obj.isRoot()) {
return;
}
ThreadRecorder rec = getOrCreateThreadRecorder((TargetThread) obj);
rec.offerThreadBreakpointContainer(breaks);
}).exceptionally(ex -> {
Msg.error(this, "Error recording breakpoint container", ex);
return null;
});
}
if (added instanceof TargetBreakpointSpec) {
// I don't think this matters. UI for live recording only.
}
if (added instanceof TargetBreakpointLocation) {
TargetBreakpointLocation bpt = (TargetBreakpointLocation) added;
breakpoints.add(bpt);
offerEffectiveBreakpoint(bpt);
}
}
@Override
protected void objectRemoved(TargetObject removed) {
if (!valid) {
return;
}
if (target == removed) {
stopRecording();
return;
}
if (removed instanceof TargetRegisterContainer) {
regMappers.remove((TargetRegisterContainer) removed);
}
if (removed instanceof TargetRegister) {
TargetRegister reg = (TargetRegister) removed;
DebuggerRegisterMapper rm = regMappers.getCompletedMap().get(reg.getContainer());
if (rm == null) {
return;
}
rm.targetRegisterRemoved(reg);
for (ThreadRecorder rec : threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, reg, true);
}
}
if (removed instanceof TargetMemoryRegion) {
TargetMemoryRegion region = (TargetMemoryRegion) removed;
if (processMemory.removeRegion(region)) {
removeProcessRegion(region);
return;
}
// Allow removal notice to fall through to thread recorders
}
if (removed instanceof TargetModule) {
TargetModule module = (TargetModule) removed;
removeProcessModule(module);
return;
}
if (removed instanceof TargetBreakpointLocation) {
TargetBreakpointLocation bpt = (TargetBreakpointLocation) removed;
breakpoints.remove(bpt);
removeEffectiveBreakpoint(bpt);
return;
}
synchronized (threadMap) {
for (Iterator<ThreadRecorder> it = threadMap.recorders().iterator(); it
.hasNext();) {
ThreadRecorder rec = it.next();
if (rec.objectRemoved(removed)) {
it.remove();
}
}
}
}
protected boolean successor(TargetObject ref) {
return PathUtils.isAncestor(target.getPath(), ref.getPath());
}
protected boolean anyRef(Collection<Object> parameters) {
for (Object p : parameters) {
if (!(p instanceof TargetObject)) {
continue;
}
return true;
}
return false;
}
protected boolean anySuccessor(Collection<Object> parameters) {
for (Object p : parameters) {
if (!(p instanceof TargetObject)) {
continue;
}
TargetObject ref = (TargetObject) p;
if (!successor(ref)) {
continue;
}
return true;
}
return false;
}
protected boolean eventApplies(TargetObject eventThread, TargetEventType type,
List<Object> parameters) {
if (type == TargetEventType.RUNNING) {
return false;
/**
* 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.
*/
}
if (eventThread != null) {
return successor(eventThread);
}
if (anyRef(parameters)) {
return anySuccessor(parameters);
}
return true; // Some session-wide event, I suppose
}
@Override
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
String description, List<Object> parameters) {
if (!valid) {
return;
}
TimedMsg.info(this, "Event: " + type + " thread=" + eventThread + " description=" +
description + " params=" + parameters);
// Just use this to step the snaps. Creation/destruction still handled in add/remove
if (!eventApplies(eventThread, type, parameters)) {
return;
}
ThreadRecorder rec = threadMap.get(eventThread);
createSnapshot(description, rec == null ? null : rec.traceThread, null);
if (type == TargetEventType.THREAD_CREATED) {
if (rec == null) {
return;
}
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Adjust thread creation", true)) {
rec.traceThread.setCreationSnap(snapshot.getKey());
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be shrinking
}
}
else if (type == TargetEventType.MODULE_LOADED) {
Object p0 = parameters.get(0);
if (!(p0 instanceof TargetObject)) {
return;
}
TargetObject obj = (TargetObject) p0;
if (!(obj instanceof TargetModule)) {
return;
}
TargetModule mod = (TargetModule) obj;
TraceModule traceModule = getTraceModule(mod);
if (traceModule == null) {
return;
}
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Adjust module load", true)) {
traceModule.setLoadedSnap(snapshot.getKey());
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not set module loaded snap", e);
}
}
}
@Override
public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
super.attributesChanged(parent, removed, added);
if (!valid) {
return;
}
// Dispatch attribute changes which don't have "built-in" events.
if (parent instanceof TargetBreakpointLocation) {
if (added.containsKey(TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME)) {
breakpointLengthChanged((TargetBreakpointLocation) parent,
(Integer) added.get(TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME));
}
}
if (parent instanceof TargetStackFrame) {
if (added.containsKey(TargetStackFrame.PC_ATTRIBUTE_NAME)) {
framePcUpdated((TargetStackFrame) parent);
}
}
if (parent instanceof TargetRegisterBank) {
if (added.containsKey(TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME)) {
ThreadRecorder rec = threadMap.getForSuccessor(parent);
if (rec != null) {
rec.doFetchAndInitRegMapper((TargetRegisterBank) parent);
}
}
}
// This should be fixed at construction.
/*if (parent instanceof TargetModule) {
if (added.containsKey(TargetModule.BASE_ATTRIBUTE_NAME)) {
moduleBaseUpdated((TargetModule) parent,
(Address) added.get(TargetModule.BASE_ATTRIBUTE_NAME));
}
}*/
}
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
public void executionStateChanged(TargetObject stateful, TargetExecutionState state) {
if (!valid) {
return;
}
TimedMsg.info(this, "State " + state + " for " + stateful);
findThreadOrProcess(stateful).thenAccept(threadOrProcess -> {
if (threadOrProcess == target && state == TargetExecutionState.TERMINATED) {
stopRecording();
return;
}
ThreadRecorder rec = null;
synchronized (threadMap) {
if (threadOrProcess instanceof TargetThread) {
rec = threadMap.get((TargetThread) threadOrProcess);
}
}
if (rec != null) {
rec.stateChanged(state);
}
// Else we'll discover it and sync state later
});
}
protected ThreadRecorder getOrCreateThreadRecorder(TargetThread thread) {
synchronized (threadMap) {
ThreadRecorder rec = threadMap.get(thread);
if (rec != null) {
return rec;
}
TraceThread traceThread;
String path = PathUtils.toString(thread.getPath());
try (PermanentTransaction tid =
PermanentTransaction.start(trace, path + " created")) {
// Note, if THREAD_CREATED is emitted, it will adjust the creation snap
traceThread = threadManager.createThread(path, thread.getShortDisplay(),
snapshot.getKey());
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be a new thread in model
}
rec = new ThreadRecorder(thread, traceThread);
threadMap.put(rec);
return rec;
}
}
@Override
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
if (!valid) {
return;
}
ThreadRecorder rec = threadMap.getForSuccessor(bank);
if (rec == null) {
return;
}
rec.recordRegisterValues(bank, updates);
}
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
if (!valid) {
return;
}
synchronized (DefaultTraceRecorder.this) {
if (memMapper == null) {
Msg.warn(this, "Received memory write before a region has been added");
return;
}
}
Address traceAddr = memMapper.targetToTrace(address);
long snap = snapshot.getKey();
TimedMsg.info(this, "Memory updated: " + address + " (" + data.length + ")");
try (PermanentTransaction tid = PermanentTransaction.start(trace, "Memory observed")) {
ByteBuffer newBytes = ByteBuffer.wrap(data);
memoryManager.putBytes(snap, traceAddr, newBytes);
}
}
@Override
public void memoryReadError(TargetObject memory, AddressRange range,
DebuggerMemoryAccessException e) {
if (!valid) {
return;
}
Msg.error(this, "Error reading range " + range, e);
Address traceMin = memMapper.targetToTrace(range.getMinAddress());
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Memory read error")) {
memoryManager.setState(snapshot.getKey(), traceMin, TraceMemoryState.ERROR);
// TODO: Bookmark to describe error?
}
}
@AttributeCallback(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME)
public void breakpointToggled(TargetObject obj, boolean enabled) {
if (!valid) {
return;
}
TargetBreakpointSpec spec = (TargetBreakpointSpec) obj;
spec.getLocations().thenAccept(bpts -> {
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Breakpoint toggled")) {
for (TargetBreakpointLocation eb : bpts) {
TraceBreakpoint traceBpt = getTraceBreakpoint(eb);
if (traceBpt == null) {
String path = PathUtils.toString(eb.getPath());
Msg.warn(this, "Cannot find toggled trace breakpoint for " + path);
continue;
}
// Verify attributes match? Eh. If they don't, someone has fiddled with it.
traceBpt.splitWithEnabled(snapshot.getKey(), enabled);
}
}
}).exceptionally(ex -> {
Msg.error(this, "Error recording toggled breakpoint spec: " + spec, ex);
return null;
});
}
protected void breakpointLengthChanged(TargetBreakpointLocation bpt, int length) {
Address traceAddr = memMapper.targetToTrace(bpt.getAddress());
String path = PathUtils.toString(bpt.getPath());
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
if (traceBpt.getLength() == length) {
continue; // Nothing to change
}
// TODO: Verify all other attributes match?
// TODO: Should this be allowed to happen?
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Breakpoint length changed")) {
long snap = snapshot.getKey();
if (traceBpt.getPlacedSnap() == snap) {
traceBpt.delete();
}
else {
traceBpt.setClearedSnap(snap - 1);
}
breakpointManager.placeBreakpoint(path, snap, range(traceAddr, length),
traceBpt.getThreads(), traceBpt.getKinds(), traceBpt.isEnabled(),
traceBpt.getComment());
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Split, and length matters not
}
}
}
protected void framePcUpdated(TargetStackFrame frame) {
ThreadRecorder rec = threadMap.getForSuccessor(frame);
// Yes, entire stack, otherwise, the stack seems to be just one deep.
rec.recordStack();
}
protected void stackUpdated(TargetStack stack) {
ThreadRecorder rec = threadMap.getForSuccessor(stack);
rec.recordStack();
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
if (!valid) {
return;
}
if (PathUtils.isAncestor(target.getPath(), focused.getPath())) {
curFocus = focused;
}
}
protected void retroOfferRegMapperDependents() {
List<ThreadRecorder> copy;
synchronized (objects) {
copy = List.copyOf(threadMap.byTargetThread.values());
}
for (ThreadRecorder rec : copy) {
TargetRegisterBank bank = rec.regs.get(0);
if (bank != null) {
rec.recordRegisterValues(bank, bank.getCachedRegisters());
rec.updateRegsMem(null);
}
}
}
protected void retroOfferMemMapperDependents() {
List<TargetObject> copy;
synchronized (objects) {
copy = List.copyOf(objects.values());
}
synchronized (DefaultTraceRecorder.this) {
for (TargetObject obj : copy) {
if (obj instanceof TargetModule) {
offerProcessModule((TargetModule) obj);
}
if (obj instanceof TargetSection) {
TargetSection section = (TargetSection) obj;
offerProcessModuleSection(section.getModule(), section);
}
if (obj instanceof TargetBreakpointLocation) {
offerEffectiveBreakpoint((TargetBreakpointLocation) obj);
}
if (obj instanceof TargetStack) {
stackUpdated((TargetStack) obj);
}
}
}
}
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
synchronized (objects) {
return (TargetMemoryRegion) objects.get(PathUtils.parse(region.getPath()));
}
}
public TargetModule getTargetModule(TraceModule module) {
synchronized (objects) {
return (TargetModule) objects.get(PathUtils.parse(module.getPath()));
}
}
public TargetSection getTargetSection(TraceSection section) {
synchronized (objects) {
return (TargetSection) objects.get(PathUtils.parse(section.getPath()));
}
}
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
synchronized (objects) {
return (TargetBreakpointLocation) objects.get(PathUtils.parse(bpt.getPath()));
}
}
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
synchronized (objects) {
return breakpoints.stream().filter(bpt -> {
TargetObjectList affects = bpt.getAffects();
// N.B. in case thread is null (process), affects.contains(thread) is always false
return affects.isEmpty() || affects.contains(thread) ||
affects.contains(target);
}).collect(Collectors.toList());
}
}
protected void onProcessBreakpointContainers(
Consumer<? super TargetBreakpointContainer> action) {
synchronized (objects) {
if (processBreakpointContainer == null) {
for (TargetThread thread : threadsView) {
onThreadBreakpointContainers(thread, action);
}
}
else {
action.accept(processBreakpointContainer);
}
}
}
protected void onThreadBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointContainer> action) {
synchronized (objects) {
getOrCreateThreadRecorder(thread).onThreadBreakpointContainers(action);
}
}
protected void onBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointContainer> action) {
if (thread == null) {
onProcessBreakpointContainers(action);
}
else {
onThreadBreakpointContainers(thread, action);
}
}
}
protected final DebuggerModelServicePlugin plugin;
protected final PluginTool tool;
protected final Trace trace;
protected final TargetObject target;
protected final ComposedMemory processMemory = new ComposedMemory();
protected TargetBreakpointContainer processBreakpointContainer;
protected final TraceBreakpointManager breakpointManager;
protected final TraceCodeManager codeManager;
protected final TraceBasedDataTypeManager dataTypeManager;
protected final TraceEquateManager equateManager;
protected final TraceMemoryManager memoryManager;
protected final TraceModuleManager moduleManager;
protected final TraceStackManager stackManager;
protected final TraceSymbolManager symbolManager;
protected final TraceThreadManager threadManager;
protected final TraceTimeManager timeManager;
protected final AbstractDebuggerTargetTraceMapper mapper;
protected DebuggerMemoryMapper memMapper;
protected AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> regMappers;
protected final TargetDataTypeConverter typeConverter;
protected Collection<TargetRegister> extraRegs;
// TODO: Support automatic recording of user-specified extra registers...
// NOTE: Probably via watches, once we have those
// TODO: Probably move all the auto-reads into watches
protected final ListenerSet<TraceRecorderListener> listeners =
new ListenerSet<>(TraceRecorderListener.class);
protected final TriConsumer<Boolean, Boolean, Void> listenerRegAccChanged =
this::registerAccessibilityChanged;
protected final TriConsumer<Boolean, Boolean, Void> listenerProcMemAccChanged =
this::processMemoryAccessibilityChanged;
private final ListenerForRecord listenerForRecord;
protected final ThreadMap threadMap = new ThreadMap();
protected final Set<TargetThread> threadsView =
Collections.unmodifiableSet(threadMap.byTargetThread.keySet());
protected final BiMap<TargetBreakpointLocation, TraceBreakpoint> processBreakpointsMap =
HashBiMap.create();
protected final AsyncLazyValue<Void> lazyInit = new AsyncLazyValue<>(this::doInit);
protected TraceSnapshot snapshot = null;
private boolean valid = true;
protected TargetFocusScope focusScope;
protected TargetObject curFocus;
public DefaultTraceRecorder(DebuggerModelServicePlugin plugin, Trace trace, TargetObject target,
AbstractDebuggerTargetTraceMapper mapper) {
this.plugin = plugin;
this.tool = plugin.getTool();
this.trace = trace;
this.target = target;
this.breakpointManager = trace.getBreakpointManager();
this.codeManager = trace.getCodeManager();
this.dataTypeManager = trace.getDataTypeManager();
this.equateManager = trace.getEquateManager();
this.memoryManager = trace.getMemoryManager();
this.moduleManager = trace.getModuleManager();
this.stackManager = trace.getStackManager();
this.symbolManager = trace.getSymbolManager();
this.threadManager = trace.getThreadManager();
this.timeManager = trace.getTimeManager();
this.mapper = mapper;
this.regMappers =
new AsyncLazyMap<>(new HashMap<>(), descs -> mapper.offerRegisters(descs));
this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager());
this.listenerForRecord = new ListenerForRecord();
processMemory.memAccListeners.add(listenerProcMemAccChanged);
trace.addConsumer(this);
}
protected void registerAccessibilityChanged(boolean old, boolean acc, Void __) {
listeners.fire.registerAccessibilityChanged(this);
}
protected void processMemoryAccessibilityChanged(boolean old, boolean acc, Void __) {
listeners.fire.processMemoryAccessibilityChanged(this);
}
@Override
public CompletableFuture<Void> init() {
return lazyInit.request();
}
protected CompletableFuture<Void> doInit() {
createSnapshot("Started recording " + PathUtils.toString(target.getPath()) + " in " +
target.getModel(), null, null);
AsyncFence fence = new AsyncFence();
CompletableFuture<? extends TargetBreakpointContainer> futureBreaks =
DebugModelConventions.findSuitable(TargetBreakpointContainer.class, target);
fence.include(futureBreaks.thenAccept(breaks -> {
if (breaks != null && !PathUtils.isAncestor(target.getPath(), breaks.getPath())) {
offerProcessBreakpointContainer(breaks); // instead of objectAdded
listenerForRecord.addListenerAndConsiderSuccessors(breaks);
}
}).exceptionally(e -> {
Msg.error(this, "Could not search for breakpoint container", e);
return null;
}));
CompletableFuture<? extends TargetEventScope> futureEvents =
DebugModelConventions.findSuitable(TargetEventScope.class, target);
fence.include(futureEvents.thenAccept(events -> {
if (events != null && !PathUtils.isAncestor(target.getPath(), events.getPath())) {
// Don't descend. Scope may be the entire session.
listenerForRecord.addListener(events);
}
}).exceptionally(e -> {
Msg.warn(this, "Could not search for event scope", e);
return null;
}));
CompletableFuture<? extends TargetFocusScope> futureFocus =
DebugModelConventions.findSuitable(TargetFocusScope.class, target);
fence.include(futureFocus.thenAccept(focus -> {
if (focus != null && !PathUtils.isAncestor(target.getPath(), focus.getPath())) {
// Don't descend. Scope may be the entire session.
offerFocusScope(focus);
listenerForRecord.addListener(focus);
}
}).exceptionally(e -> {
Msg.error(this, "Could not search for focus scope", e);
return null;
}));
return fence.ready().thenAccept(__ -> {
listenerForRecord.objectAdded(target); // TODO: This seems wrong
listenerForRecord.addListenerAndConsiderSuccessors(target);
});
}
protected synchronized void doAdvanceSnap(String description, TraceThread eventThread) {
snapshot = timeManager.createSnapshot(description);
snapshot.setEventThread(eventThread);
}
@Override
public TraceSnapshot forceSnapshot() {
createSnapshot("User-forced snapshot", null, null);
return snapshot;
}
protected void createSnapshot(String description, TraceThread eventThread,
PermanentTransaction tid) {
if (tid != null) {
doAdvanceSnap(description, eventThread);
listeners.fire.snapAdvanced(this, snapshot.getKey());
return;
}
try (PermanentTransaction tid2 = PermanentTransaction.start(trace, description)) {
doAdvanceSnap(description, eventThread);
}
listeners.fire.snapAdvanced(this, snapshot.getKey());
}
// TODO: This could probably be discovered by the offer and passed in at construction
protected synchronized CompletableFuture<Void> initMemMapper(TargetMemory memory) {
/**
* TODO: At the moment, there's no real dependency on the memory. When there is, see that
* additional memories can be incorporated into the mapper, and stale ones removed.
* Alternatively, formalize that there is no possible dependency on memory.
*/
if (memMapper != null) {
return AsyncUtils.NIL;
}
return mapper.offerMemory(memory).thenAccept(mm -> {
synchronized (this) {
memMapper = mm;
}
listenerForRecord.retroOfferMemMapperDependents();
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize memory mapper", ex);
return null;
});
}
protected Collection<TraceMemoryFlag> getTraceFlags(TargetMemoryRegion region) {
Collection<TraceMemoryFlag> flags = new HashSet<>();
if (region.isReadable()) {
flags.add(TraceMemoryFlag.READ);
}
if (region.isWritable()) {
flags.add(TraceMemoryFlag.WRITE);
}
if (region.isExecutable()) {
flags.add(TraceMemoryFlag.EXECUTE);
}
// TODO: Volatile? Can any debugger report that?
return flags;
}
protected void offerProcessRegion(TargetMemoryRegion region) {
TargetMemory mem = region.getMemory();
processMemory.addRegion(region, mem);
initMemMapper(mem);
synchronized (this) {
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Memory region added")) {
String path = PathUtils.toString(region.getPath());
TraceMemoryRegion traceRegion =
memoryManager.getLiveRegionByPath(snapshot.getKey(), path);
if (traceRegion != null) {
Msg.warn(this, "Region " + path + " already recorded");
return;
}
traceRegion = memoryManager.addRegion(path, Range.atLeast(snapshot.getKey()),
memMapper.targetToTrace(region.getRange()), getTraceFlags(region));
traceRegion.setName(region.getName());
}
catch (TraceOverlappedRegionException e) {
Msg.error(this, "Failed to create region due to overlap", e);
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Just checked for existing
}
}
updateAllThreadsRegsMem(region).exceptionally(ex -> {
Msg.error(this, "Could not add process memory region", ex);
return null;
});
}
protected synchronized void removeProcessRegion(TargetMemoryRegion region) {
// Already removed from processMemory. That's how we knew to go here.
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Memory region removed")) {
String path = PathUtils.toString(region.getPath());
long snap = snapshot.getKey();
TraceMemoryRegion traceRegion = memoryManager.getLiveRegionByPath(snap, path);
if (traceRegion == null) {
Msg.warn(this, "Could not find region " + path + " in trace to remove");
return;
}
traceRegion.setDestructionSnap(snap - 1);
}
catch (DuplicateNameException | TraceOverlappedRegionException e) {
throw new AssertionError(e); // Region is shrinking in time
}
}
protected void recordBreakpoint(TargetBreakpointSpec spec, TargetBreakpointLocation bpt,
Set<TraceThread> traceThreads) {
synchronized (this) {
if (memMapper == null) {
throw new IllegalStateException(
"No memory mapper! Have not recorded a region, yet.");
}
}
String path = PathUtils.toString(bpt.getPath());
String name = nameBreakpoint(bpt);
Address traceAddr = memMapper.targetToTrace(bpt.getAddress());
AddressRange traceRange = range(traceAddr, bpt.getLength());
try (PermanentTransaction tid = PermanentTransaction.start(trace, "Breakpoint placed")) {
boolean enabled = spec.isEnabled();
Set<TraceBreakpointKind> traceKinds =
TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds());
TraceBreakpoint traceBpt = breakpointManager.placeBreakpoint(path, snapshot.getKey(),
traceRange, traceThreads, traceKinds, enabled, spec.getExpression());
traceBpt.setName(name);
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be new to model, or already cleared
}
}
protected void offerProcessBreakpointContainer(TargetBreakpointContainer bc) {
if (processBreakpointContainer != null) {
Msg.warn(this, "Already have a breakpoint container for this process");
}
processBreakpointContainer = bc;
}
protected void offerFocusScope(TargetFocusScope scope) {
if (this.focusScope != null) {
Msg.warn(this, "Already have a focus scope: " + this.focusScope);
}
this.focusScope = scope;
}
protected synchronized TraceModule offerProcessModule(TargetModule module) {
if (memMapper == null) {
return null;
}
String path = PathUtils.toString(module.getPath());
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snapshot.getKey(), path);
if (traceModule != null) {
return traceModule;
}
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Module " + path + " loaded")) {
AddressRange targetRange = module.getRange();
AddressRange traceRange =
targetRange == null ? null : memMapper.targetToTrace(targetRange);
traceModule = moduleManager.addLoadedModule(path, module.getModuleName(), traceRange,
snapshot.getKey());
return traceModule;
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // We checked for existing by path
}
}
protected synchronized TraceSection offerProcessModuleSection(TargetModule module,
TargetSection section) {
if (memMapper == null) {
return null;
}
String path = PathUtils.toString(section.getPath());
TraceModule traceModule = offerProcessModule(module);
TraceSection traceSection = moduleManager.getLoadedSectionByPath(snapshot.getKey(), path);
if (traceSection != null) {
Msg.warn(this, path + " already recorded");
return traceSection;
}
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Section " + path + " added")) {
AddressRange targetRange = section.getRange();
AddressRange traceRange = memMapper.targetToTrace(targetRange);
traceSection = traceModule.addSection(path, section.getIndex(), traceRange);
return traceSection;
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // We checked for existing by name
}
}
protected synchronized void removeProcessModule(TargetModule module) {
String path = PathUtils.toString(module.getPath());
long snap = snapshot.getKey();
TraceThread eventThread = snapshot.getEventThread();
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snap, path);
if (traceModule == null) {
Msg.warn(this, "unloaded " + path + " is not in the trace");
return;
}
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Module " + path + " unloaded")) {
if (traceModule.getLoadedSnap() == snap) {
Msg.warn(this, "Observed module unload in the same snap as its load");
createSnapshot("WARN: Module removed", eventThread, tid);
snap = snapshot.getKey();
}
traceModule.setUnloadedSnap(snap - 1);
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Module lifespan should be shrinking
}
}
// NB: No removeProcessModuleSection, because sections should be immutable
// They are removed when the module is removed
protected void offerEffectiveBreakpoint(TargetBreakpointLocation bpt) {
synchronized (this) {
if (memMapper == null) {
return;
}
}
EffectiveBreakpointResolver resolver = new EffectiveBreakpointResolver(bpt);
resolver.resolve().thenAccept(__ -> {
if (resolver.affectsProcess || !resolver.threadsAffected.isEmpty()) {
recordBreakpoint(resolver.spec, bpt, resolver.threadsAffected);
}
}).exceptionally(ex -> {
Msg.error(this, "Could record target breakpoint: " + bpt, ex);
return null;
});
}
protected void removeEffectiveBreakpoint(TargetBreakpointLocation bpt) {
String path = PathUtils.toString(bpt.getPath());
long snap = snapshot.getKey();
try (PermanentTransaction tid = PermanentTransaction.start(trace, "Breakpoint deleted")) {
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
if (traceBpt.getPlacedSnap() > snap) {
Msg.error(this,
"Tracked, now removed breakpoint was placed in the future? " + bpt);
}
else if (traceBpt.getPlacedSnap() == snap) {
// TODO: I forget if this is allowed for DBTrace iteration
traceBpt.delete();
}
else {
traceBpt.setClearedSnap(snap - 1);
}
}
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Lifespan in shrinking
}
}
protected CompletableFuture<Void> updateAllThreadsRegsMem(TargetMemoryRegion limit) {
AsyncFence fence = new AsyncFence();
for (ThreadRecorder rec : threadMap.recorders()) {
fence.include(rec.updateRegsMem(limit));
}
return fence.ready();
}
@Override
public TargetObject getTarget() {
return target;
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public long getSnap() {
return snapshot.getKey();
}
@Override
public boolean isRecording() {
return valid;
}
@Override
public void stopRecording() {
invalidate();
listeners.fire.recordingStopped(this);
}
@Override
public void addListener(TraceRecorderListener l) {
listeners.add(l);
}
@Override
public void removeListener(TraceRecorderListener l) {
listeners.remove(l);
}
@Override
public boolean isViewAtPresent(TraceProgramView view) {
if (!valid) {
return false;
}
if (!Objects.equals(trace, view.getTrace())) {
return false;
}
if (snapshot.getKey() != view.getSnap()) {
return false;
}
return true;
}
@Override
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
return listenerForRecord.getTargetBreakpoint(bpt);
}
@Override
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
String path = PathUtils.toString(bpt.getPath());
return breakpointManager.getPlacedBreakpointByPath(snapshot.getKey(), path);
}
@Override
public List<TargetBreakpointContainer> collectBreakpointContainers(TargetThread thread) {
List<TargetBreakpointContainer> result = new ArrayList<>();
listenerForRecord.onBreakpointContainers(thread, result::add);
return result;
}
@Override
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
return listenerForRecord.collectBreakpoints(thread);
}
@Override
public Set<TraceBreakpointKind> getSupportedBreakpointKinds() {
Set<TargetBreakpointKind> tKinds = new HashSet<>();
listenerForRecord.onBreakpointContainers(null, cont -> {
tKinds.addAll(cont.getSupportedBreakpointKinds());
});
return TraceRecorder.targetToTraceBreakpointKinds(tKinds);
}
@Override
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
return listenerForRecord.getTargetMemoryRegion(region);
}
@Override
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
String path = PathUtils.toString(region.getPath());
return memoryManager.getLiveRegionByPath(snapshot.getKey(), path);
}
@Override
public TargetModule getTargetModule(TraceModule module) {
return listenerForRecord.getTargetModule(module);
}
@Override
public TraceModule getTraceModule(TargetModule module) {
String path = PathUtils.toString(module.getPath());
return moduleManager.getLoadedModuleByPath(snapshot.getKey(), path);
}
@Override
public TargetSection getTargetSection(TraceSection section) {
return listenerForRecord.getTargetSection(section);
}
@Override
public TraceSection getTraceSection(TargetSection section) {
String path = PathUtils.toString(section.getPath());
return moduleManager.getLoadedSectionByPath(snapshot.getKey(), path);
}
@Override
public TargetThread getTargetThread(TraceThread thread) {
ThreadRecorder rec = threadMap.get(thread);
return rec == null ? null : rec.targetThread;
}
@Override
public TargetExecutionState getTargetThreadState(TargetThread thread) {
ThreadRecorder rec = threadMap.get(thread);
return rec == null ? null : rec.state;
}
@Override
public TargetExecutionState getTargetThreadState(TraceThread thread) {
ThreadRecorder rec = threadMap.get(thread);
return rec == null ? null : rec.state;
}
@Override
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
if (bank == null) {
return false;
}
synchronized (accessibilityByRegBank) {
KeyedFuture<?, AllRequiredAccess> future = accessibilityByRegBank.get(bank);
if (future == null) {
return false;
}
AllRequiredAccess acc = future.getNow(null);
if (acc == null) {
return false;
}
return acc.get();
}
}
@Override
public boolean isRegisterBankAccessible(TraceThread thread, int frameLevel) {
return isRegisterBankAccessible(getTargetRegisterBank(thread, frameLevel));
}
@Override
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
ThreadRecorder rec = threadMap.get(thread);
return rec == null ? null : rec.regs.get(frameLevel);
}
@Override
public Set<TargetThread> getLiveTargetThreads() {
return threadsView;
}
@Override
public TraceThread getTraceThread(TargetThread thread) {
ThreadRecorder rec = threadMap.byTargetThread.get(thread);
return rec == null ? null : rec.traceThread;
}
@Override
public TraceThread getTraceThreadForSuccessor(TargetObject successor) {
ThreadRecorder rec = threadMap.getForSuccessor(successor);
return rec == null ? null : rec.traceThread;
}
protected TraceStackFrame getTraceStackFrame(TraceThread thread, int level) {
TraceStack stack = trace.getStackManager().getLatestStack(thread, snapshot.getKey());
if (stack == null) {
return null;
}
return stack.getFrame(level, false);
}
@Override
public TraceStackFrame getTraceStackFrame(TargetStackFrame frame) {
ThreadRecorder rec = threadMap.getForSuccessor(frame);
if (rec == null) {
return null;
}
int level = getFrameLevel(frame);
if (rec.stack.get(level) != frame) {
return null;
}
return getTraceStackFrame(rec.traceThread, level);
}
@Override
public TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor) {
ThreadRecorder rec = threadMap.getForSuccessor(successor);
if (rec == null) {
return null;
}
int level = rec.getSuccessorFrameLevel(successor);
return getTraceStackFrame(rec.traceThread, level);
}
@Override
public TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel) {
ThreadRecorder rec = threadMap.get(thread);
if (rec == null) {
return null;
}
return rec.stack.get(frameLevel);
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return memMapper;
}
@Override
public DebuggerRegisterMapper getRegisterMapper(TraceThread thread) {
ThreadRecorder rec = threadMap.get(thread);
if (rec == null) {
return null;
}
return rec.regMapper;
}
@Override
public AddressSetView getAccessibleProcessMemory() {
// TODO: Efficiently distinguish which memory is process vs. thread
return getAccessibleMemory(mem -> true);
}
protected void invalidate() {
valid = false;
listenerForRecord.dispose();
trace.release(this);
}
protected TraceThread findLiveThreadByName(String name) {
for (TraceThread traceThread : threadManager.getThreadsByPath(name)) {
if (traceThread != null && traceThread.isAlive()) {
return traceThread;
}
}
return null;
}
@Override
public CompletableFuture<Void> captureThreadRegisters(TraceThread thread, int frameLevel,
Set<Register> registers) {
DebuggerRegisterMapper regMapper = getRegisterMapper(thread);
if (regMapper == null) {
throw new IllegalStateException("Have not found register descriptions for " + thread);
}
if (!regMapper.getRegistersOnTarget().containsAll(registers)) {
throw new IllegalArgumentException(
"All given registers must be recognized by the target");
}
if (registers.isEmpty()) {
return AsyncUtils.NIL;
}
List<TargetRegister> tRegs =
registers.stream().map(regMapper::traceToTarget).collect(Collectors.toList());
TargetRegisterBank bank = getTargetRegisterBank(thread, frameLevel);
if (bank == null) {
throw new IllegalArgumentException(
"Given thread and frame level does not have a live register bank");
}
// NOTE: Cache update, if applicable, will cause recorder to write values to trace
return bank.readRegisters(tRegs).thenApply(__ -> null);
}
@Override
public CompletableFuture<Void> writeThreadRegisters(TraceThread thread, int frameLevel,
Map<Register, RegisterValue> values) {
DebuggerRegisterMapper regMapper = getRegisterMapper(thread);
if (regMapper == null) {
throw new IllegalStateException("Have not found register descriptions for " + thread);
}
if (!regMapper.getRegistersOnTarget().containsAll(values.keySet())) {
throw new IllegalArgumentException(
"All given registers must be recognized by the target");
}
if (values.isEmpty()) {
return AsyncUtils.NIL;
}
Map<String, byte[]> tVals = values.entrySet().stream().map(ent -> {
if (ent.getKey() != ent.getValue().getRegister()) {
throw new IllegalArgumentException("register name mismatch in value");
}
return regMapper.traceToTarget(ent.getValue());
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
TargetRegisterBank bank = getTargetRegisterBank(thread, frameLevel);
if (bank == null) {
throw new IllegalArgumentException(
"Given thread and frame level does not have a live register bank");
}
// NOTE: Model + recorder will cause applicable trace updates
return bank.writeRegistersNamed(tVals).thenApply(__ -> null);
}
@Override
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {
Address tStart = memMapper.traceToTarget(start);
return processMemory.readMemory(tStart, length);
}
@Override
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) {
Address tStart = memMapper.traceToTarget(start);
return processMemory.writeMemory(tStart, data);
}
@Override
public CompletableFuture<Void> captureProcessMemory(AddressSetView set, TaskMonitor monitor) {
if (set.isEmpty()) {
return AsyncUtils.NIL;
}
// 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.
int total = 0;
AddressSetView expSet =
expandToBlocks(set).intersect(memoryManager.getRegionsAddressSet(snapshot.getKey()));
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.
return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> {
AddressRangeChunker it = new AddressRangeChunker(r, BLOCK_SIZE);
AsyncUtils.each(TypeSpec.VOID, it.iterator(), (vRng, inner) -> {
// The listener in the recorder will copy to the Trace.
monitor.incrementProgress(1);
AddressRange tRng = memMapper.traceToTarget(vRng);
processMemory.readMemory(tRng.getMinAddress(), (int) tRng.getLength())
.thenApply(b -> !monitor.isCancelled())
.handle(inner::repeatWhile);
}).exceptionally(e -> {
Msg.error(this, "Error reading range " + r + ": " + e);
// NOTE: Above may double log, since recorder listens for errors, too
return null; // Continue looping on errors
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
});
}
@Override
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor) {
if (!valid) {
return AsyncUtils.NIL;
}
String path = PathUtils.toString(namespace.getPath());
monitor.setMessage("Capturing data types for " + path);
return namespace.getTypes().thenCompose(types -> {
monitor.initialize(types.size());
AsyncFence fence = new AsyncFence();
List<DataType> converted = new ArrayList<>();
for (TargetNamedDataType type : types) {
if (monitor.isCancelled()) {
fence.ready().cancel(false);
return AsyncUtils.nil();
}
monitor.incrementProgress(1);
fence.include(typeConverter.convertTargetDataType(type).thenAccept(converted::add));
}
return fence.ready().thenApply(__ -> converted);
}).thenAccept(converted -> {
if (converted == null) {
return;
}
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Capture data types for " + path)) {
// NOTE: createCategory is actually getOrCreate
Category category = dataTypeManager.createCategory(new CategoryPath("/" + path));
for (DataType dataType : converted) {
category.addDataType(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
});
}
@Override
public CompletableFuture<Void> captureDataTypes(TraceModule module, TaskMonitor monitor) {
TargetModule targetModule = getTargetModule(module);
if (targetModule == null) {
Msg.error(this, "Module " + module + " is not loaded");
return AsyncUtils.NIL;
}
CompletableFuture<? extends Map<String, ? extends TargetDataTypeNamespace>> future =
targetModule.fetchChildrenSupporting(TargetDataTypeNamespace.class);
// NOTE: I should expect exactly one namespace...
return future.thenCompose(namespaces -> {
AsyncFence fence = new AsyncFence();
for (TargetDataTypeNamespace ns : namespaces.values()) {
fence.include(captureDataTypes(ns, monitor));
}
return fence.ready();
});
}
private TraceNamespaceSymbol createNamespaceIfAbsent(String path) {
try {
return symbolManager.namespaces()
.add(path, symbolManager.getGlobalNamespace(), SourceType.IMPORTED);
}
catch (DuplicateNameException e) {
Msg.info(this, "Namespace for module " + path +
" already exists or another exists with a conflicting name. Using the existing one: " +
e);
TraceNamespaceSymbol ns = symbolManager.namespaces().getGlobalNamed(path);
if (ns != null) {
return ns;
}
Msg.error(this, "Existing namespace for " + path +
" is not a plain namespace. Using global namespace.");
return symbolManager.getGlobalNamespace();
}
catch (InvalidInputException | IllegalArgumentException e) {
Msg.error(this,
"Could not create namespace for new module: " + path + ". Using global namespace.",
e);
return symbolManager.getGlobalNamespace();
}
}
@Override
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
TaskMonitor monitor) {
if (!valid) {
return AsyncUtils.NIL;
}
String path = PathUtils.toString(namespace.getPath());
monitor.setMessage("Capturing symbols for " + path);
return namespace.getSymbols().thenAccept(symbols -> {
try (PermanentTransaction tid =
PermanentTransaction.start(trace, "Capture types and symbols for " + path)) {
TraceNamespaceSymbol ns = createNamespaceIfAbsent(path);
monitor.setMessage("Capturing symbols for " + path);
monitor.initialize(symbols.size());
for (TargetSymbol sym : symbols) {
if (monitor.isCancelled()) {
return;
}
monitor.incrementProgress(1);
String symName = sym.getIndex();
if (sym.isConstant()) {
// TODO: Equate namespaces?
TraceEquate equate = equateManager.getByName(symName);
long symVal = sym.getValue().getOffset();
if (equate != null && equate.getValue() == symVal) {
continue;
}
try {
equateManager.create(symName, symVal);
}
catch (DuplicateNameException | IllegalArgumentException e) {
Msg.error(this, "Could not create equate: " + symName, e);
}
continue;
}
Address addr = memMapper.targetToTrace(sym.getValue());
try {
symbolManager.labels()
.create(snapshot.getKey(), null, addr, symName, ns,
SourceType.IMPORTED);
}
catch (InvalidInputException e) {
Msg.error(this, "Could not add module symbol " + sym + ": " + e);
}
/**
* TODO: Lay down data type, if present
*
* TODO: Interpret "address" type correctly. A symbol with this type is itself
* the pointer. In other words, it is not specifying the type to lay down in
* memory.
*/
}
}
});
}
@Override
public CompletableFuture<Void> captureSymbols(TraceModule module, TaskMonitor monitor) {
TargetModule targetModule = getTargetModule(module);
if (targetModule == null) {
Msg.error(this, "Module " + module + " is not loaded");
return AsyncUtils.NIL;
}
CompletableFuture<? extends Map<String, ? extends TargetSymbolNamespace>> future =
targetModule.fetchChildrenSupporting(TargetSymbolNamespace.class);
// NOTE: I should expect exactly one namespace...
return future.thenCompose(namespaces -> {
AsyncFence fence = new AsyncFence();
for (TargetSymbolNamespace ns : namespaces.values()) {
fence.include(captureSymbols(ns, monitor));
}
return fence.ready();
});
}
@Override
public boolean isSupportsFocus() {
return focusScope != null;
}
@Override
public TargetObject getFocus() {
if (curFocus == null) {
if (focusScope == null) {
return null;
}
TargetObject focus = focusScope.getFocus();
if (focus == null || !PathUtils.isAncestor(target.getPath(), focus.getPath())) {
return null;
}
curFocus = focus;
}
return curFocus;
}
@Override
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
if (!isSupportsFocus()) {
return CompletableFuture
.failedFuture(new IllegalArgumentException("Target does not support focus"));
}
if (!PathUtils.isAncestor(target.getPath(), focus.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested focus path is not a successor of the target"));
}
if (!PathUtils.isAncestor(focusScope.getPath(), focus.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested focus path is not a successor of the focus scope"));
}
return focusScope.requestFocus(focus).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) {
String msg = "Could not focus " + focus + ": " + ex.getMessage();
Msg.info(this, msg);
plugin.getTool().setStatusInfo(msg);
}
Msg.showError(this, null, "Focus Sync", "Could not focus " + focus, ex);
return false;
});
}
@Override
public ListenerForRecord getListenerForRecord() {
return listenerForRecord;
}
}