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

This commit is contained in:
Dan
2022-04-28 15:13:01 -04:00
parent 20706efea3
commit 032ae36005
109 changed files with 3184 additions and 481 deletions
@@ -78,6 +78,6 @@ public abstract class AbstractModelForDbgengProcessActivationTest
.collect(Collectors.toList())).trim(); .collect(Collectors.toList())).trim();
String procId = getIdFromCapture(line); String procId = getIdFromCapture(line);
assertEquals(expected.getPath(), assertEquals(expected.getPath(),
getProcessPattern().applyIndices(procId).getSingletonPath()); getProcessPattern().applyKeys(procId).getSingletonPath());
} }
} }
@@ -83,6 +83,6 @@ public abstract class AbstractModelForGdbFrameActivationTest
assertFalse(line.contains("\n")); assertFalse(line.contains("\n"));
assertTrue(line.startsWith("#")); assertTrue(line.startsWith("#"));
String frameLevel = line.substring(1).split("\\s+")[0]; String frameLevel = line.substring(1).split("\\s+")[0];
assertEquals(expected.getPath(), STACK_PATTERN.applyIndices(frameLevel).getSingletonPath()); assertEquals(expected.getPath(), STACK_PATTERN.applyKeys(frameLevel).getSingletonPath());
} }
} }
@@ -74,6 +74,6 @@ public abstract class AbstractModelForGdbInferiorActivationTest
.filter(l -> l.trim().startsWith("*")) .filter(l -> l.trim().startsWith("*"))
.collect(Collectors.toList())).trim(); .collect(Collectors.toList())).trim();
String inferiorId = line.split("\\s+")[1]; String inferiorId = line.split("\\s+")[1];
assertEquals(expected.getPath(), INF_PATTERN.applyIndices(inferiorId).getSingletonPath()); assertEquals(expected.getPath(), INF_PATTERN.applyKeys(inferiorId).getSingletonPath());
} }
} }
@@ -87,6 +87,6 @@ public abstract class AbstractModelForGdbThreadActivationTest
.collect(Collectors.toList())); .collect(Collectors.toList()));
String threadGid = line.split("\\s+")[2]; String threadGid = line.split("\\s+")[2];
assertEquals(expected.getPath(), assertEquals(expected.getPath(),
THREAD_PATTERN.applyIndices(threadGid, threadGid).getSingletonPath()); THREAD_PATTERN.applyKeys(threadGid, threadGid).getSingletonPath());
} }
} }
@@ -60,7 +60,7 @@ public abstract class AbstractModelForLldbScenarioX64RegistersTest
for (String name : toWrite.keySet()) { for (String name : toWrite.keySet()) {
for (TargetRegisterBank bank : banks.values()) { for (TargetRegisterBank bank : banks.values()) {
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
bank.getPath(), pred -> pred.applyIndices(name), false); bank.getPath(), pred -> pred.applyKeys(name), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
waitOn(bank.writeRegister(reg, toWrite.get(name))); waitOn(bank.writeRegister(reg, toWrite.get(name)));
} }
@@ -81,7 +81,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (Entry<String, byte[]> ent : getRegisterWrites().entrySet()) { for (Entry<String, byte[]> ent : getRegisterWrites().entrySet()) {
String regName = ent.getKey(); String regName = ent.getKey();
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
path, pred -> pred.applyIndices(regName), false); path, pred -> pred.applyKeys(regName), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
assertEquals(ent.getValue().length, (reg.getBitLength() + 7) / 8); assertEquals(ent.getValue().length, (reg.getBitLength() + 7) / 8);
} }
@@ -121,7 +121,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (TargetRegisterBank bank : banks.values()) { for (TargetRegisterBank bank : banks.values()) {
for (String name : exp.keySet()) { for (String name : exp.keySet()) {
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
bank.getPath(), pred -> pred.applyIndices(name), false); bank.getPath(), pred -> pred.applyKeys(name), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
byte[] bytes = waitOn(bank.readRegister(reg)); byte[] bytes = waitOn(bank.readRegister(reg));
read.put(name, bytes); read.put(name, bytes);
@@ -149,7 +149,7 @@ public abstract class AbstractModelForLldbX64RegistersTest
for (TargetRegisterBank bank : banks.values()) { for (TargetRegisterBank bank : banks.values()) {
for (String name : write.keySet()) { for (String name : write.keySet()) {
Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class, Map<List<String>, TargetRegister> regs = m.findAll(TargetRegister.class,
bank.getPath(), pred -> pred.applyIndices(name), false); bank.getPath(), pred -> pred.applyKeys(name), false);
for (TargetRegister reg : regs.values()) { for (TargetRegister reg : regs.values()) {
waitOn(bank.writeRegister(reg, write.get(name))); waitOn(bank.writeRegister(reg, write.get(name)));
@@ -67,7 +67,7 @@ public abstract class DebuggerReadsMemoryTrait {
Trace trace = current.getTrace(); Trace trace = current.getTrace();
TraceRecorder recorder = current.getRecorder(); TraceRecorder recorder = current.getRecorder();
BackgroundUtils.async(tool, trace, NAME, true, true, false, BackgroundUtils.async(tool, trace, NAME, true, true, false,
(__, monitor) -> recorder.captureProcessMemory(selection, monitor, false)); (__, monitor) -> recorder.readMemoryBlocks(selection, monitor, false));
} }
@Override @Override
@@ -78,7 +78,7 @@ public abstract class DebuggerReadsMemoryTrait {
} }
TraceRecorder recorder = current.getRecorder(); TraceRecorder recorder = current.getRecorder();
// TODO: Either allow partial, or provide action to intersect with accessible // TODO: Either allow partial, or provide action to intersect with accessible
if (!recorder.getAccessibleProcessMemory().contains(selection)) { if (!recorder.getAccessibleMemory().contains(selection)) {
return false; return false;
} }
return true; return true;
@@ -73,7 +73,7 @@ public class PCLocationTrackingSpec implements RegisterLocationTrackingSpec {
if (frame == null) { if (frame == null) {
return null; return null;
} }
return frame.getProgramCounter(); return frame.getProgramCounter(snap);
} }
@Override @Override
@@ -56,7 +56,7 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
} }
TraceRecorder recorder = coordinates.getRecorder(); TraceRecorder recorder = coordinates.getRecorder();
AddressSet visibleAccessible = AddressSet visibleAccessible =
recorder.getAccessibleProcessMemory().intersect(visible); recorder.getAccessibleMemory().intersect(visible);
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = AddressSetView alreadyKnown =
mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible, mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible,
@@ -67,6 +67,6 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }
return recorder.captureProcessMemory(toRead, TaskMonitor.DUMMY, false); return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY, false);
} }
} }
@@ -58,7 +58,7 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
} }
TraceRecorder recorder = coordinates.getRecorder(); TraceRecorder recorder = coordinates.getRecorder();
AddressSet visibleAccessible = AddressSet visibleAccessible =
recorder.getAccessibleProcessMemory().intersect(visible); recorder.getAccessibleMemory().intersect(visible);
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = AddressSetView alreadyKnown =
mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible, mm.getAddressesWithState(coordinates.getSnap(), visibleAccessible,
@@ -92,6 +92,6 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }
return recorder.captureProcessMemory(toRead, TaskMonitor.DUMMY, false); return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY, false);
} }
} }
@@ -810,7 +810,7 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
throws Exception { throws Exception {
synchronized (this) { synchronized (this) {
monitor.checkCanceled(); monitor.checkCanceled();
this.captureTask = recorder.captureProcessMemory(new AddressSet(range), monitor, false); this.captureTask = recorder.readMemoryBlocks(new AddressSet(range), monitor, false);
} }
try { try {
captureTask.get(); // Not a fan, but whatever. captureTask.get(); // Not a fan, but whatever.
@@ -854,6 +854,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
public boolean isRoot(ActionContext context) {
TargetObject object = this.getObjectFromContext(context);
if (object == null) {
return false;
}
return object.isRoot();
}
public boolean isInstance(ActionContext context, Class<? extends TargetObject> clazz) { public boolean isInstance(ActionContext context, Class<? extends TargetObject> clazz) {
TargetObject object = this.getObjectFromContext(context); TargetObject object = this.getObjectFromContext(context);
if (object == null) { if (object == null) {
@@ -1128,8 +1136,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
.popupMenuGroup(DebuggerResources.GROUP_TARGET, "T" + groupTargetIndex) .popupMenuGroup(DebuggerResources.GROUP_TARGET, "T" + groupTargetIndex)
.popupMenuIcon(AbstractRecordAction.ICON) .popupMenuIcon(AbstractRecordAction.ICON)
.helpLocation(new HelpLocation(plugin.getName(), "record")) .helpLocation(new HelpLocation(plugin.getName(), "record"))
.enabledWhen(ctx -> isInstance(ctx, TargetProcess.class)) .enabledWhen(ctx -> isInstance(ctx, TargetProcess.class) || isRoot(ctx))
.popupWhen(ctx -> isInstance(ctx, TargetProcess.class)) .popupWhen(ctx -> isInstance(ctx, TargetProcess.class) || isRoot(ctx))
.onAction(ctx -> performStartRecording(ctx)) .onAction(ctx -> performStartRecording(ctx))
.enabled(true) .enabled(true)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
@@ -1592,7 +1600,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
} }
public void startRecording(TargetProcess targetObject, boolean prompt) { public void startRecording(TargetObject targetObject, boolean prompt) {
TraceRecorder rec = modelService.getRecorder(targetObject); TraceRecorder rec = modelService.getRecorder(targetObject);
if (rec != null) { if (rec != null) {
return; // Already being recorded return; // Already being recorded
@@ -1630,6 +1638,11 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
public void performStartRecording(ActionContext context) { public void performStartRecording(ActionContext context) {
TargetObject maybeRoot = getObjectFromContext(context);
if (maybeRoot.isRoot()) {
startRecording(maybeRoot, true);
return;
}
performAction(context, false, TargetProcess.class, proc -> { performAction(context, false, TargetProcess.class, proc -> {
TargetProcess valid = DebugModelConventions.liveProcessOrNull(proc); TargetProcess valid = DebugModelConventions.liveProcessOrNull(proc);
if (valid != null) { if (valid != null) {
@@ -410,6 +410,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
return; return;
} }
if (currentStack == stack) { if (currentStack == stack) {
stackTableModel.fireTableDataChanged();
return; return;
} }
currentStack = stack; currentStack = stack;
@@ -28,41 +28,50 @@ import ghidra.util.database.UndoableTransaction;
public class StackFrameRow { public class StackFrameRow {
public static class Synthetic extends StackFrameRow { public static class Synthetic extends StackFrameRow {
private Address pc;
public Synthetic(DebuggerStackProvider provider, Address pc) { public Synthetic(DebuggerStackProvider provider, Address pc) {
super(provider, pc); super(provider);
this.pc = pc;
} }
public void updateProgramCounter(Address pc) { public void updateProgramCounter(Address pc) {
this.pc = pc; this.pc = pc;
} }
@Override
public Address getProgramCounter() {
return pc;
}
} }
private final DebuggerStackProvider provider; private final DebuggerStackProvider provider;
final TraceStackFrame frame; final TraceStackFrame frame;
private int level; private int level;
Address pc;
public StackFrameRow(DebuggerStackProvider provider, TraceStackFrame frame) { public StackFrameRow(DebuggerStackProvider provider, TraceStackFrame frame) {
this.provider = provider; this.provider = provider;
this.frame = frame; this.frame = frame;
this.level = frame.getLevel(); this.level = frame.getLevel();
this.pc = frame.getProgramCounter();
} }
private StackFrameRow(DebuggerStackProvider provider, Address pc) { private StackFrameRow(DebuggerStackProvider provider) {
this.provider = provider; this.provider = provider;
this.frame = null; this.frame = null;
this.level = 0; this.level = 0;
this.pc = pc;
} }
public int getFrameLevel() { public int getFrameLevel() {
return level; return level;
} }
public long getSnap() {
return provider.current.getSnap();
}
public Address getProgramCounter() { public Address getProgramCounter() {
return pc; return frame.getProgramCounter(getSnap());
} }
public String getComment() { public String getComment() {
@@ -88,11 +97,12 @@ public class StackFrameRow {
if (curThread == null) { if (curThread == null) {
return null; return null;
} }
Address pc = getProgramCounter();
if (pc == null) { if (pc == null) {
return null; return null;
} }
TraceLocation dloc = new DefaultTraceLocation(curThread.getTrace(), TraceLocation dloc = new DefaultTraceLocation(curThread.getTrace(),
curThread, Range.singleton(provider.current.getSnap()), pc); curThread, Range.singleton(getSnap()), pc);
ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc); ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc);
if (sloc == null) { if (sloc == null) {
return null; return null;
@@ -103,6 +113,5 @@ public class StackFrameRow {
protected void update() { protected void update() {
assert frame != null; // Should never update a synthetic stack assert frame != null; // Should never update a synthetic stack
level = frame.getLevel(); level = frame.getLevel();
pc = frame.getProgramCounter();
} }
} }
@@ -578,11 +578,18 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
} }
private ProgramLocation getDynamicLocation(ProgramLocation someLoc) { private ProgramLocation getDynamicLocation(ProgramLocation someLoc) {
if (someLoc == null) {
return null;
}
TraceProgramView view = current.getView(); TraceProgramView view = current.getView();
if (view == null) { if (view == null) {
return null; return null;
} }
if (someLoc.getProgram() instanceof TraceProgramView) { Program program = someLoc.getProgram();
if (program == null) {
return null;
}
if (program instanceof TraceProgramView) {
return someLoc; return someLoc;
} }
return mappingService.getDynamicLocationFromStatic(view, someLoc); return mappingService.getDynamicLocationFromStatic(view, someLoc);
@@ -101,6 +101,7 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
*/ */
public default Set<DebuggerMappingOffer> getOffers(TargetObject target, public default Set<DebuggerMappingOffer> getOffers(TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
// TODO: Remove this check?
if (!(target instanceof TargetProcess)) { if (!(target instanceof TargetProcess)) {
return Set.of(); return Set.of();
} }
@@ -117,13 +118,13 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
} }
/** /**
* Produce this opinion's offers for the given environment and target process * Produce this opinion's offers for the given environment and target
* *
* @param env the environment associated with the target * @param env the environment associated with the target
* @param process the target process * @param target the target (usually a process)
* @param includeOverrides true to include override offers, i.e., those with negative confidence * @param includeOverrides true to include override offers, i.e., those with negative confidence
* @return the offers, possibly empty, but never null * @return the offers, possibly empty, but never null
*/ */
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides); boolean includeOverrides);
} }
@@ -15,8 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.app.plugin.core.debug.mapping;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressRange;
public interface DebuggerMemoryMapper { public interface DebuggerMemoryMapper {
/** /**
@@ -33,7 +32,10 @@ public interface DebuggerMemoryMapper {
* @param traceRange the range in the view's address space * @param traceRange the range in the view's address space
* @return the "same range" in the target's address space * @return the "same range" in the target's address space
*/ */
AddressRange traceToTarget(AddressRange traceRange); default AddressRange traceToTarget(AddressRange traceRange) {
return new AddressRangeImpl(traceToTarget(traceRange.getMinAddress()),
traceToTarget(traceRange.getMaxAddress()));
}
/** /**
* Map the given address from the target process into the trace * Map the given address from the target process into the trace
@@ -49,5 +51,8 @@ public interface DebuggerMemoryMapper {
* @param targetRange the range in the target's address space * @param targetRange the range in the target's address space
* @return the "same range" in the trace's address space * @return the "same range" in the trace's address space
*/ */
AddressRange targetToTrace(AddressRange targetRange); default AddressRange targetToTrace(AddressRange targetRange) {
return new AddressRangeImpl(targetToTrace(targetRange.getMinAddress()),
targetToTrace(targetRange.getMaxAddress()));
}
} }
@@ -45,12 +45,6 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
public Address traceToTarget(Address traceAddr) { public Address traceToTarget(Address traceAddr) {
assert isInFactory(traceAddr, traceAddressFactory); assert isInFactory(traceAddr, traceAddressFactory);
return toSameNamedSpace(traceAddr, targetAddressFactory); return toSameNamedSpace(traceAddr, targetAddressFactory);
};
@Override
public AddressRange traceToTarget(AddressRange traceRange) {
return new AddressRangeImpl(traceToTarget(traceRange.getMinAddress()),
traceToTarget(traceRange.getMaxAddress()));
} }
@Override @Override
@@ -65,10 +59,4 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
assert isInFactory(targetAddr, targetAddressFactory); assert isInFactory(targetAddr, targetAddressFactory);
return toSameNamedSpace(targetAddr, traceAddressFactory); return toSameNamedSpace(targetAddr, traceAddressFactory);
} }
@Override
public AddressRange targetToTrace(AddressRange targetRange) {
return new AddressRangeImpl(targetToTrace(targetRange.getMinAddress()),
targetToTrace(targetRange.getMaxAddress()));
}
} }
@@ -35,12 +35,12 @@ public class DefaultDebuggerTargetTraceMapper implements DebuggerTargetTraceMapp
protected final Set<String> extraRegNames; protected final Set<String> extraRegNames;
public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID, public DefaultDebuggerTargetTraceMapper(TargetObject target, LanguageID langID,
CompilerSpecID csId, Collection<String> extraRegNames) CompilerSpecID csID, Collection<String> extraRegNames)
throws LanguageNotFoundException, CompilerSpecNotFoundException { throws LanguageNotFoundException, CompilerSpecNotFoundException {
this.target = target; this.target = target;
LanguageService langServ = DefaultLanguageService.getLanguageService(); LanguageService langServ = DefaultLanguageService.getLanguageService();
this.language = langServ.getLanguage(langID); this.language = langServ.getLanguage(langID);
this.cSpec = language.getCompilerSpecByID(csId); this.cSpec = language.getCompilerSpecByID(csID);
this.extraRegNames = Set.copyOf(extraRegNames); this.extraRegNames = Set.copyOf(extraRegNames);
} }
@@ -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 java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.*; import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
@@ -54,7 +55,7 @@ public class OverridesDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!includeOverrides) { if (!includeOverrides) {
return Set.of(); return Set.of();
@@ -65,7 +66,7 @@ public class OverridesDebuggerMappingOpinion implements DebuggerMappingOpinion {
// ALL THE SPECS!!! // ALL THE SPECS!!!
new LanguageCompilerSpecQuery(null, null, null, null, null)) new LanguageCompilerSpecQuery(null, null, null, null, null))
.stream() .stream()
.map(lcsp -> offerForLanguageAndCSpec(process, endian, lcsp)) .map(lcsp -> offerForLanguageAndCSpec(target, endian, lcsp))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }
@@ -49,24 +49,28 @@ public class FridaArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
protected static class FridaAarch64MacosOffer extends DefaultDebuggerMappingOffer { protected static class FridaAarch64MacosOffer extends DefaultDebuggerMappingOffer {
public FridaAarch64MacosOffer(TargetProcess process) { public FridaAarch64MacosOffer(TargetProcess process) {
super(process, 50, "AARCH64/Frida on macos", LANG_ID_AARCH64,COMP_ID_DEFAULT, super(process, 50, "AARCH64/Frida on macos", LANG_ID_AARCH64, COMP_ID_DEFAULT,
Set.of("cpsr")); Set.of("cpsr"));
} }
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includesOverrides) { boolean includesOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("frida")) { if (!env.getDebugger().toLowerCase().contains("frida")) {
return Set.of(); return Set.of();
} }
String arch = env.getArchitecture(); String arch = env.getArchitecture();
boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm"); boolean is64Bit =
arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm");
String os = env.getOperatingSystem(); String os = env.getOperatingSystem();
if (os.contains("macos")) { if (os.contains("macos")) {
if (is64Bit) { if (is64Bit) {
Msg.info(this, "Using os=" + os + " arch=" + arch); Msg.info(this, "Using os=" + os + " arch=" + arch);
return Set.of(new FridaAarch64MacosOffer(process)); return Set.of(new FridaAarch64MacosOffer((TargetProcess) target));
} }
} }
return Set.of(); return Set.of();
@@ -49,14 +49,17 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
protected static class LldbAarch64MacosOffer extends DefaultDebuggerMappingOffer { protected static class LldbAarch64MacosOffer extends DefaultDebuggerMappingOffer {
public LldbAarch64MacosOffer(TargetProcess process) { public LldbAarch64MacosOffer(TargetProcess process) {
super(process, 50, "AARCH64/LLDB on macos", LANG_ID_AARCH64,COMP_ID_DEFAULT, super(process, 50, "AARCH64/LLDB on macos", LANG_ID_AARCH64, COMP_ID_DEFAULT,
Set.of("cpsr")); Set.of("cpsr"));
} }
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includesOverrides) { boolean includesOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("lldb")) { if (!env.getDebugger().toLowerCase().contains("lldb")) {
return Set.of(); return Set.of();
} }
@@ -66,7 +69,7 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
if (os.contains("macos")) { if (os.contains("macos")) {
if (is64Bit) { if (is64Bit) {
Msg.info(this, "Using os=" + os + " arch=" + arch); Msg.info(this, "Using os=" + os + " arch=" + arch);
return Set.of(new LldbAarch64MacosOffer(process)); return Set.of(new LldbAarch64MacosOffer((TargetProcess) target));
} }
} }
return Set.of(); return Set.of();
@@ -75,15 +75,18 @@ public class DbgengX64DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (env == null || !env.getDebugger().toLowerCase().contains("dbg")) { if (env == null || !env.getDebugger().toLowerCase().contains("dbg")) {
return Set.of(); return Set.of();
} }
boolean is64Bit = boolean is64Bit =
env.getArchitecture().contains("x86_64") || env.getArchitecture().contains("x64_32"); env.getArchitecture().contains("x86_64") || env.getArchitecture().contains("x64_32");
if (is64Bit) { if (is64Bit) {
return Set.of(new DbgI386X86_64WindowsOffer(process)); return Set.of(new DbgI386X86_64WindowsOffer((TargetProcess) target));
} }
return null; return null;
} }
@@ -96,7 +96,7 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject {
try { try {
// This is on its own task thread, so whatever. // This is on its own task thread, so whatever.
// Just don't hang it indefinitely. // Just don't hang it indefinitely.
recorder.captureProcessMemory(set, TaskMonitor.DUMMY, false) recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, false)
.get(1000, TimeUnit.MILLISECONDS); .get(1000, TimeUnit.MILLISECONDS);
} }
catch (InterruptedException | ExecutionException | TimeoutException e) { catch (InterruptedException | ExecutionException | TimeoutException e) {
@@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.frida;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -71,16 +70,22 @@ public class FridaX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("frida")) { if (!env.getDebugger().toLowerCase().contains("frida")) {
return Set.of(); return Set.of();
} }
String arch = env.getArchitecture(); String arch = env.getArchitecture();
boolean is32Bit = arch.contains("ia32") ||arch.contains("x86-32") || arch.contains("i386") || boolean is32Bit =
arch.contains("x86_32"); arch.contains("ia32") || arch.contains("x86-32") || arch.contains("i386") ||
boolean is64Bit = arch.contains("x64") ||arch.contains("x86-64") || arch.contains("x64-32") || arch.contains("x86_32");
arch.contains("x86_64") || arch.contains("x64_32") || arch.contains("i686"); boolean is64Bit =
arch.contains("x64") || arch.contains("x86-64") || arch.contains("x64-32") ||
arch.contains("x86_64") || arch.contains("x64_32") || arch.contains("i686");
String os = env.getOperatingSystem(); String os = env.getOperatingSystem();
if (os.contains("darwin")) { if (os.contains("darwin")) {
if (is64Bit) { if (is64Bit) {
@@ -21,7 +21,8 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.*; import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
@@ -81,7 +82,7 @@ public class DefaultGdbDebuggerMappingOpinion implements DebuggerMappingOpinion
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!isGdb(env)) { if (!isGdb(env)) {
return Set.of(); return Set.of();
@@ -90,7 +91,7 @@ public class DefaultGdbDebuggerMappingOpinion implements DebuggerMappingOpinion
String arch = env.getArchitecture(); String arch = env.getArchitecture();
return getCompilerSpecsForGnu(arch, endian).stream() return getCompilerSpecsForGnu(arch, endian).stream()
.flatMap(lcsp -> offersForLanguageAndCSpec(process, arch, endian, lcsp).stream()) .flatMap(lcsp -> offersForLanguageAndCSpec(target, arch, endian, lcsp).stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }
@@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.gdb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@@ -39,8 +38,11 @@ public class GdbM68kDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }
@@ -54,7 +56,7 @@ public class GdbM68kDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
String arch = env.getArchitecture(); String arch = env.getArchitecture();
if (arch.startsWith("m68k")) { if (arch.startsWith("m68k")) {
return Set.of(new GdbM68kBELinux32DefOffer(process)); return Set.of(new GdbM68kBELinux32DefOffer((TargetProcess) target));
} }
return Set.of(); return Set.of();
} }
@@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.gdb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@@ -79,8 +78,12 @@ public class GdbMipsDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }
@@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.gdb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
@@ -65,8 +64,12 @@ public class GdbPowerPCDebuggerMappingOpinion implements DebuggerMappingOpinion
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }
@@ -100,8 +100,12 @@ public class GdbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("gdb")) { if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of(); return Set.of();
} }
@@ -64,8 +64,11 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().contains("Java Debug Interface")) { if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of(); return Set.of();
} }
@@ -73,6 +76,6 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
return Set.of(); return Set.of();
} }
// NOTE: Not worried about JRE version // NOTE: Not worried about JRE version
return Set.of(new DalvikDebuggerMappingOffer(process)); return Set.of(new DalvikDebuggerMappingOffer((TargetProcess) target));
} }
} }
@@ -64,8 +64,11 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
if (!env.getDebugger().contains("Java Debug Interface")) { if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of(); return Set.of();
} }
@@ -73,6 +76,6 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
return Set.of(); return Set.of();
} }
// NOTE: Not worried about JRE version // NOTE: Not worried about JRE version
return Set.of(new JavaDebuggerMappingOffer(process)); return Set.of(new JavaDebuggerMappingOffer((TargetProcess) target));
} }
} }
@@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.platform.lldb;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID; import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -71,8 +70,12 @@ public class LldbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
} }
@Override @Override
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process, public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetObject target,
boolean includeOverrides) { boolean includeOverrides) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
if (!env.getDebugger().toLowerCase().contains("lldb")) { if (!env.getDebugger().toLowerCase().contains("lldb")) {
return Set.of(); return Set.of();
} }
@@ -250,6 +250,11 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
Msg.info(this, "Ignoring " + breakpoint + Msg.info(this, "Ignoring " + breakpoint +
" changed until service has finished loading its trace"); " changed until service has finished loading its trace");
} }
catch (NoSuchElementException e) {
// TODO: This catch clause should not be necessary.
Msg.error(this,
"!!!! Object-based breakpoint emitted event without a spec: " + breakpoint);
}
} }
private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull, private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull,
@@ -69,7 +69,7 @@ public class ReadsTargetMemoryPcodeExecutorState
if (!isLive()) { if (!isLive()) {
return false; return false;
} }
waitTimeout(recorder.captureProcessMemory(unknown, TaskMonitor.DUMMY, false)); waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true; return true;
} }
@@ -21,11 +21,11 @@ import java.util.concurrent.CompletableFuture;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder; import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder;
import ghidra.async.AsyncUtils; import ghidra.app.plugin.core.debug.service.model.record.RecorderUtils;
import ghidra.async.TypeSpec;
import ghidra.dbg.target.TargetMemory; import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion; import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.program.model.address.*; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.*; import ghidra.trace.model.memory.*;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -35,20 +35,7 @@ import ghidra.util.task.TaskMonitor;
public class DefaultMemoryRecorder implements ManagedMemoryRecorder { public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
// For large memory captures // For large memory captures
private static final int BLOCK_SIZE = 4096; private static final int BLOCK_BITS = 12; // 4096 bytes
private static final long BLOCK_MASK = -1L << 12;
protected static AddressSetView expandToBlocks(AddressSetView asv) {
AddressSet result = new AddressSet();
// Not terribly efficient, but this is one range most of the time
for (AddressRange range : asv) {
AddressSpace space = range.getAddressSpace();
Address min = space.getAddress(range.getMinAddress().getOffset() & BLOCK_MASK);
Address max = space.getAddress(range.getMaxAddress().getOffset() | ~BLOCK_MASK);
result.add(new AddressRangeImpl(min, max));
}
return result;
}
private final DefaultTraceRecorder recorder; private final DefaultTraceRecorder recorder;
private final Trace trace; private final Trace trace;
@@ -62,45 +49,7 @@ public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set, public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set,
TaskMonitor monitor, boolean toMap) { TaskMonitor monitor, boolean toMap) {
// TODO: Figure out how to display/select per-thread memory. return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor, toMap);
// Probably need a thread parameter passed in then?
// NOTE: That thread memory will already be chained to process memory. Good.
// NOTE: I don't intend to warn about the number of requests.
// They're delivered in serial, and there's a cancel button that works
int total = 0;
AddressSetView expSet = expandToBlocks(set)
.intersect(trace.getMemoryManager().getRegionsAddressSet(recorder.getSnap()));
for (AddressRange r : expSet) {
total += Long.divideUnsigned(r.getLength() + BLOCK_SIZE - 1, BLOCK_SIZE);
}
monitor.initialize(total);
monitor.setMessage("Capturing memory");
// TODO: Read blocks in parallel? Probably NO. Tends to overload the agent.
NavigableMap<Address, byte[]> result = toMap ? new TreeMap<>() : null;
return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> {
AddressRangeChunker blocks = new AddressRangeChunker(r, BLOCK_SIZE);
AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (vBlk, inner) -> {
// The listener in the recorder will copy to the Trace.
monitor.incrementProgress(1);
AddressRange tBlk = recorder.getMemoryMapper().traceToTarget(vBlk);
recorder.getProcessMemory()
.readMemory(tBlk.getMinAddress(), (int) tBlk.getLength())
.thenAccept(data -> {
if (toMap) {
result.put(tBlk.getMinAddress(), data);
}
})
.exceptionally(e -> {
Msg.error(this, "Error reading block " + tBlk + ": " + e);
// NOTE: Above may double log, since recorder listens for errors, too
return null; // Continue looping on errors
})
.thenApply(__ -> !monitor.isCancelled())
.handle(inner::repeatWhile);
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
}).thenApply(__ -> result);
} }
@Override @Override
@@ -86,7 +86,7 @@ public class DefaultStackRecorder implements ManagedStackRecorder {
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) { public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true); TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
traceFrame.setProgramCounter(pc); traceFrame.setProgramCounter(null, pc); // Not object-based, so span=null
} }
public void recordFrame(TargetStackFrame frame) { public void recordFrame(TargetStackFrame frame) {
@@ -22,6 +22,8 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.*; import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.app.plugin.core.debug.service.model.record.DataTypeRecorder;
import ghidra.app.plugin.core.debug.service.model.record.SymbolRecorder;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener; import ghidra.app.services.TraceRecorderListener;
import ghidra.async.AsyncLazyValue; import ghidra.async.AsyncLazyValue;
@@ -64,11 +66,11 @@ public class DefaultTraceRecorder implements TraceRecorder {
TraceObjectManager objectManager; TraceObjectManager objectManager;
DefaultBreakpointRecorder breakpointRecorder; DefaultBreakpointRecorder breakpointRecorder;
DefaultDataTypeRecorder datatypeRecorder; DataTypeRecorder datatypeRecorder;
DefaultMemoryRecorder memoryRecorder; DefaultMemoryRecorder memoryRecorder;
DefaultModuleRecorder moduleRecorder; DefaultModuleRecorder moduleRecorder;
DefaultProcessRecorder processRecorder; DefaultProcessRecorder processRecorder;
DefaultSymbolRecorder symbolRecorder; SymbolRecorder symbolRecorder;
DefaultTimeRecorder timeRecorder; DefaultTimeRecorder timeRecorder;
//protected final PermanentTransactionExecutor seqTx; //protected final PermanentTransactionExecutor seqTx;
@@ -94,10 +96,10 @@ public class DefaultTraceRecorder implements TraceRecorder {
this.processRecorder = new DefaultProcessRecorder(this); this.processRecorder = new DefaultProcessRecorder(this);
this.breakpointRecorder = new DefaultBreakpointRecorder(this); this.breakpointRecorder = new DefaultBreakpointRecorder(this);
this.datatypeRecorder = new DefaultDataTypeRecorder(this); this.datatypeRecorder = new DataTypeRecorder(this);
this.memoryRecorder = new DefaultMemoryRecorder(this); this.memoryRecorder = new DefaultMemoryRecorder(this);
this.moduleRecorder = new DefaultModuleRecorder(this); this.moduleRecorder = new DefaultModuleRecorder(this);
this.symbolRecorder = new DefaultSymbolRecorder(this); this.symbolRecorder = new SymbolRecorder(this);
this.timeRecorder = new DefaultTimeRecorder(this); this.timeRecorder = new DefaultTimeRecorder(this);
this.objectManager = new TraceObjectManager(target, mapper, this); this.objectManager = new TraceObjectManager(target, mapper, this);
} }
@@ -266,7 +268,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
/*---------------- CAPTURE METHODS -------------------*/ /*---------------- CAPTURE METHODS -------------------*/
@Override @Override
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set, public CompletableFuture<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean toMap) { TaskMonitor monitor, boolean toMap) {
if (set.isEmpty()) { if (set.isEmpty()) {
return CompletableFuture.completedFuture(new TreeMap<>()); return CompletableFuture.completedFuture(new TreeMap<>());
@@ -499,12 +501,6 @@ public class DefaultTraceRecorder implements TraceRecorder {
/*---------------- LISTENER METHODS -------------------*/ /*---------------- LISTENER METHODS -------------------*/
// UNUSED?
@Override
public TraceEventListener getListenerForRecord() {
return objectManager.getEventListener();
}
public ListenerSet<TraceRecorderListener> getListeners() { public ListenerSet<TraceRecorderListener> getListeners() {
return objectManager.getListeners(); return objectManager.getListeners();
} }
@@ -526,17 +522,17 @@ public class DefaultTraceRecorder implements TraceRecorder {
} }
@Override @Override
public AddressSetView getAccessibleProcessMemory() { public AddressSetView getAccessibleMemory() {
return processRecorder.getAccessibleProcessMemory(); return processRecorder.getAccessibleProcessMemory();
} }
@Override @Override
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) { public CompletableFuture<byte[]> readMemory(Address start, int length) {
return processRecorder.readProcessMemory(start, length); return processRecorder.readProcessMemory(start, length);
} }
@Override @Override
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) { public CompletableFuture<Void> writeMemory(Address start, byte[] data) {
return processRecorder.writeProcessMemory(start, data); return processRecorder.writeProcessMemory(start, data);
} }
@@ -566,6 +562,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
return true; return true;
} }
// UNUSED?
@Override @Override
public CompletableFuture<Void> flushTransactions() { public CompletableFuture<Void> flushTransactions() {
return parTx.flush(); return parTx.flush();
@@ -24,20 +24,22 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer; import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer; import ghidra.app.plugin.core.debug.utils.TransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx; import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx;
import ghidra.framework.model.DomainObjectException;
import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.ClosedException;
public class PermanentTransactionExecutor { public class PermanentTransactionExecutor {
private final TransactionCoalescer txc; private final TransactionCoalescer txc;
private final Executor[] threads; private final ExecutorService[] threads;
private final UndoableDomainObject obj; private final UndoableDomainObject obj;
public PermanentTransactionExecutor(UndoableDomainObject obj, String name, int threadCount, public PermanentTransactionExecutor(UndoableDomainObject obj, String name, int threadCount,
int delayMs) { int delayMs) {
this.obj = obj; this.obj = obj;
txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs); txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs);
this.threads = new Executor[threadCount]; this.threads = new ExecutorService[threadCount];
for (int i = 0; i < threadCount; i++) { for (int i = 0; i < threadCount; i++) {
ThreadFactory factory = new BasicThreadFactory.Builder() ThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern(name + "thread-" + i + "-%d") .namingPattern(name + "thread-" + i + "-%d")
@@ -46,6 +48,12 @@ public class PermanentTransactionExecutor {
} }
} }
public void shutdownNow() {
for (ExecutorService t : threads) {
t.shutdownNow();
}
}
/** /**
* This hash is borrowed from {@link HashMap}, except for the power-of-two masking, since I * This hash is borrowed from {@link HashMap}, except for the power-of-two masking, since I
* don't want to force the thread count to be a power of two (though it probably is). In the * don't want to force the thread count to be a power of two (though it probably is). In the
@@ -70,6 +78,12 @@ public class PermanentTransactionExecutor {
try (CoalescedTx tx = txc.start(description)) { try (CoalescedTx tx = txc.start(description)) {
runnable.run(); runnable.run();
} }
catch (DomainObjectException e) {
if (e.getCause() instanceof ClosedException) {
Msg.info(this, obj + " is closed. Shutting down transaction executor.");
shutdownNow();
}
}
}, selectThread(sel)).exceptionally(e -> { }, selectThread(sel)).exceptionally(e -> {
Msg.error(this, "Trouble recording " + description, e); Msg.error(this, "Trouble recording " + description, e);
return null; return null;
@@ -20,7 +20,7 @@ import ghidra.util.database.UndoableTransaction;
public class RecorderPermanentTransaction implements AutoCloseable { public class RecorderPermanentTransaction implements AutoCloseable {
static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) { public static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) {
UndoableTransaction tid = UndoableTransaction.start(obj, description, true); UndoableTransaction tid = UndoableTransaction.start(obj, description, true);
return new RecorderPermanentTransaction(obj, tid); return new RecorderPermanentTransaction(obj, tid);
} }
@@ -79,13 +79,6 @@ public class RecorderSimpleMemory implements AbstractRecorderMemory {
} }
} }
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
@Override @Override
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred, public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) { DebuggerMemoryMapper memMapper) {
@@ -32,6 +32,13 @@ public interface AbstractRecorderMemory {
public CompletableFuture<Void> writeMemory(Address address, byte[] data); public CompletableFuture<Void> writeMemory(Address address, byte[] data);
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred, public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper); DebuggerMemoryMapper memMapper);
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.model; package ghidra.app.plugin.core.debug.service.model.record;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.RecorderPermanentTransaction;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
@@ -27,13 +29,13 @@ import ghidra.program.model.data.*;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DefaultDataTypeRecorder { public class DataTypeRecorder {
//private DefaultTraceRecorder recorder; //private TraceRecorder recorder;
private Trace trace; private final Trace trace;
private final TargetDataTypeConverter typeConverter; private final TargetDataTypeConverter typeConverter;
public DefaultDataTypeRecorder(DefaultTraceRecorder recorder) { public DataTypeRecorder(TraceRecorder recorder) {
//this.recorder = recorder; //this.recorder = recorder;
this.trace = recorder.getTrace(); this.trace = recorder.getTrace();
this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager()); this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager());
@@ -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());
}
}
}

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