mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-739,741,742,666,681,823: combine listener interfaces, remove attribute-specific callbacks, update-mode schema, recorder refactor, model testing, and double-launch fix
This commit is contained in:
parent
e83a893493
commit
015858b5d3
533 changed files with 29293 additions and 8011 deletions
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.breakpoint;
|
||||
|
||||
import static ghidra.lifecycle.Unfinished.*;
|
||||
import static ghidra.lifecycle.Unfinished.TODO;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.*;
|
||||
|
@ -51,6 +51,7 @@ import ghidra.trace.model.Trace.TraceBreakpointChangeType;
|
|||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.ObjectKey;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
@ -121,10 +122,10 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
protected static class LogicalBreakpointTableModel extends RowWrappedEnumeratedColumnTableModel< //
|
||||
LogicalBreakpointTableColumns, LogicalBreakpointRow, LogicalBreakpoint> {
|
||||
LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> {
|
||||
|
||||
public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) {
|
||||
super("Breakpoints", LogicalBreakpointTableColumns.class,
|
||||
super("Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
|
||||
lb -> new LogicalBreakpointRow(provider, lb));
|
||||
}
|
||||
|
||||
|
@ -198,10 +199,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||
|
||||
protected static class BreakpointLocationTableModel
|
||||
extends RowWrappedEnumeratedColumnTableModel< //
|
||||
BreakpointLocationTableColumns, BreakpointLocationRow, TraceBreakpoint> {
|
||||
BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> {
|
||||
|
||||
public BreakpointLocationTableModel() {
|
||||
super("Locations", BreakpointLocationTableColumns.class, BreakpointLocationRow::new);
|
||||
super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey,
|
||||
BreakpointLocationRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -965,7 +967,9 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
public void setSelectedLocations(Set<TraceBreakpoint> sel) {
|
||||
DebuggerResources.setSelectedRows(sel, locationTableModel.getMap(), locationTable,
|
||||
DebuggerResources.setSelectedRows(
|
||||
sel.stream().map(b -> b.getObjectKey()).collect(Collectors.toSet()),
|
||||
locationTableModel.getMap(), locationTable,
|
||||
locationTableModel, locationFilterPanel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.app.plugin.core.debug.gui.interpreters;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -30,10 +31,9 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.PinInterpreterAction;
|
||||
import ghidra.app.plugin.core.interpreter.InterpreterComponentProvider;
|
||||
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
|
||||
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
|
||||
import ghidra.dbg.target.TargetConsole.Channel;
|
||||
import ghidra.dbg.target.TargetConsole.TargetConsoleListener;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetInterpreter.TargetInterpreterListener;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
@ -45,7 +45,11 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
|||
* We inherit console text output from interpreter listener, even though we may be listening to
|
||||
* a plain console.
|
||||
*/
|
||||
protected class ForInterpreterListener implements TargetInterpreterListener {
|
||||
protected class ForInterpreterListener extends AnnotatedDebuggerAttributeListener {
|
||||
public ForInterpreterListener() {
|
||||
super(MethodHandles.lookup());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleOutput(TargetObject console, Channel channel, byte[] out) {
|
||||
OutputStream os;
|
||||
|
@ -77,15 +81,15 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
|
||||
public void displayChanged(TargetObject object, String display) {
|
||||
// TODO: Add setSubTitle(String) to InterpreterConsole
|
||||
InterpreterComponentProvider provider = (InterpreterComponentProvider) guiConsole;
|
||||
Swing.runLater(() -> provider.setSubTitle(display));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void promptChanged(TargetInterpreter i, String prompt) {
|
||||
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME)
|
||||
public void promptChanged(TargetObject interpreter, String prompt) {
|
||||
Swing.runLater(() -> guiConsole.setPrompt(prompt));
|
||||
}
|
||||
|
||||
|
@ -109,7 +113,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
|||
protected final T targetConsole;
|
||||
|
||||
protected final AtomicBoolean running = new AtomicBoolean(false);
|
||||
protected final TargetConsoleListener listener = new ForInterpreterListener();
|
||||
protected final ForInterpreterListener listener = new ForInterpreterListener();
|
||||
protected Thread thread;
|
||||
protected InterpreterConsole guiConsole;
|
||||
protected BufferedReader inReader;
|
||||
|
@ -193,7 +197,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
|||
try {
|
||||
while (running.get()) {
|
||||
String line = inReader.readLine();
|
||||
if (!running.get()) {
|
||||
if (line == null || !running.get()) {
|
||||
return;
|
||||
}
|
||||
sendLine(line).exceptionally(e -> {
|
||||
|
|
|
@ -27,7 +27,8 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
|||
import ghidra.app.plugin.core.debug.event.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
|
@ -238,7 +239,7 @@ public class DebuggerObjectsPlugin extends AbstractDebuggerPlugin
|
|||
System.err.println("modelModified " + model);
|
||||
}
|
||||
|
||||
public void setFocus(TargetFocusScope object, TargetObject focused) {
|
||||
public void setFocus(TargetObject object, TargetObject focused) {
|
||||
for (DebuggerObjectsProvider p : providers) {
|
||||
p.setFocus(object, focused);
|
||||
}
|
||||
|
|
|
@ -52,8 +52,8 @@ import ghidra.dbg.target.*;
|
|||
import ghidra.dbg.target.TargetConsole.Channel;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.TargetObject.TargetObjectListener;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.DebuggerCallbackReorderer;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
@ -70,10 +70,8 @@ import ghidra.util.*;
|
|||
import ghidra.util.table.GhidraTable;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class DebuggerObjectsProvider extends ComponentProviderAdapter implements //AllTargetObjectListenerAdapter,
|
||||
TargetObjectListener, //
|
||||
DebuggerModelListener, //
|
||||
ObjectContainerListener {
|
||||
public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
implements ObjectContainerListener {
|
||||
|
||||
public static final String PATH_JOIN_CHAR = ".";
|
||||
//private static final String AUTOUPDATE_ATTRIBUTE_NAME = "autoupdate";
|
||||
|
@ -170,7 +168,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
description = "The foreground color for links to items in the objects tree", //
|
||||
help = @HelpInfo(anchor = "colors") //
|
||||
)
|
||||
Color linkForegroundColor = Color.GREEN;
|
||||
Color linkForegroundColor = Color.GREEN.darker();
|
||||
|
||||
@AutoOptionDefined( //
|
||||
name = "Default Extended Step", //
|
||||
|
@ -202,6 +200,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
};
|
||||
|
||||
private boolean asTree = true;
|
||||
private MyObjectListener listener = new MyObjectListener();
|
||||
|
||||
public DebuggerMethodInvocationDialog launchDialog;
|
||||
public DebuggerAttachDialog attachDialog;
|
||||
|
@ -301,7 +300,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
|
||||
public void setModel(DebuggerObjectModel model) {
|
||||
currentModel = model;
|
||||
currentModel.addModelListener(this, true);
|
||||
currentModel.addModelListener(getListener(), true);
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
@ -463,8 +462,10 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
List<ObjectContainer> containers = new ArrayList<>();
|
||||
for (String path : targetMap.keySet()) {
|
||||
if (path.endsWith(key)) {
|
||||
ObjectContainer container = targetMap.get(path);
|
||||
containers.add(container);
|
||||
synchronized (targetMap) {
|
||||
ObjectContainer container = targetMap.get(path);
|
||||
containers.add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ObjectContainer container : containers) {
|
||||
|
@ -659,8 +660,10 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
if (targetObject != null && !container.isLink()) {
|
||||
String key = targetObject.getJoinedPath(PATH_JOIN_CHAR);
|
||||
container.subscribe();
|
||||
targetMap.put(key, container);
|
||||
refSet.add(targetObject);
|
||||
synchronized (targetMap) {
|
||||
targetMap.put(key, container);
|
||||
refSet.add(targetObject);
|
||||
}
|
||||
if (targetObject instanceof TargetInterpreter) {
|
||||
TargetInterpreter interpreter = (TargetInterpreter) targetObject;
|
||||
getPlugin().showConsole(interpreter);
|
||||
|
@ -675,8 +678,10 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
public void deleteFromMap(ObjectContainer container) {
|
||||
TargetObject targetObject = container.getTargetObject();
|
||||
if (targetObject != null) {
|
||||
targetMap.remove(targetObject.getJoinedPath(PATH_JOIN_CHAR));
|
||||
refSet.remove(targetObject);
|
||||
synchronized (targetMap) {
|
||||
targetMap.remove(targetObject.getJoinedPath(PATH_JOIN_CHAR));
|
||||
refSet.remove(targetObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -741,7 +746,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
public void closeComponent() {
|
||||
TargetObject targetObject = getRoot().getTargetObject();
|
||||
if (targetObject != null) {
|
||||
targetObject.removeListener(this);
|
||||
targetObject.removeListener(getListener());
|
||||
}
|
||||
super.closeComponent();
|
||||
}
|
||||
|
@ -1153,9 +1158,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
.helpLocation(AbstractSetBreakpointAction.help(plugin))
|
||||
//.withContext(ObjectActionContext.class)
|
||||
.enabledWhen(ctx ->
|
||||
isInstance(ctx, TargetBreakpointContainer.class) && isStopped(ctx))
|
||||
isInstance(ctx, TargetBreakpointSpecContainer.class) && isStopped(ctx))
|
||||
.popupWhen(ctx ->
|
||||
isInstance(ctx, TargetBreakpointContainer.class) && isStopped(ctx))
|
||||
isInstance(ctx, TargetBreakpointSpecContainer.class) && isStopped(ctx))
|
||||
.onAction(ctx -> performSetBreakpoint(ctx))
|
||||
.enabled(false)
|
||||
.buildAndInstallLocal(this);
|
||||
|
@ -1348,23 +1353,21 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
}).finish();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> startRecording(TargetProcess targetObject, boolean prompt) {
|
||||
CompletableFuture<TraceRecorder> future;
|
||||
public void startRecording(TargetProcess targetObject, boolean prompt) {
|
||||
TraceRecorder rec;
|
||||
if (prompt) {
|
||||
future = modelService.recordTargetPromptOffers(targetObject);
|
||||
rec = modelService.recordTargetPromptOffers(targetObject);
|
||||
}
|
||||
else {
|
||||
future = modelService.recordTargetBestOffer(targetObject);
|
||||
rec = modelService.recordTargetBestOffer(targetObject);
|
||||
}
|
||||
return future.thenAccept(rec -> {
|
||||
if (rec == null) {
|
||||
return; // Cancelled
|
||||
}
|
||||
//this.recorder = rec;
|
||||
Trace trace = rec.getTrace();
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activateTrace(trace);
|
||||
});
|
||||
if (rec == null) {
|
||||
return; // Cancelled
|
||||
}
|
||||
//this.recorder = rec;
|
||||
Trace trace = rec.getTrace();
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activateTrace(trace);
|
||||
}
|
||||
|
||||
public void stopRecording(TargetObject targetObject) {
|
||||
|
@ -1409,22 +1412,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
DebugModelConventions.findSuitable(TargetProcess.class, obj).thenAccept(process -> {
|
||||
TargetProcess valid = DebugModelConventions.liveProcessOrNull(process);
|
||||
if (valid != null) {
|
||||
startRecording(valid, true).exceptionally(ex -> {
|
||||
Msg.showError(this, null, "Record",
|
||||
"Could not record and/or open target: " + valid, ex);
|
||||
return null;
|
||||
});
|
||||
startRecording(valid, true);
|
||||
}
|
||||
}).exceptionally(DebuggerResources.showError(getComponent(), "Couldn't record"));
|
||||
}
|
||||
else {
|
||||
TargetProcess valid = DebugModelConventions.liveProcessOrNull(obj);
|
||||
if (valid != null) {
|
||||
startRecording(valid, true).exceptionally(ex -> {
|
||||
Msg.showError(this, null, "Record",
|
||||
"Could not record and/or open target: " + valid, ex);
|
||||
return null;
|
||||
});
|
||||
startRecording(valid, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1520,7 +1515,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
public void performSetBreakpoint(ActionContext context) {
|
||||
TargetObject obj = getObjectFromContext(context);
|
||||
if (!isLocalOnly()) {
|
||||
DebugModelConventions.findSuitable(TargetBreakpointContainer.class, obj)
|
||||
DebugModelConventions.findSuitable(TargetBreakpointSpecContainer.class, obj)
|
||||
.thenAccept(suitable -> {
|
||||
breakpointDialog.setContainer(suitable);
|
||||
tool.showDialog(breakpointDialog);
|
||||
|
@ -1529,7 +1524,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
DebuggerResources.showError(getComponent(), "Couldn't set breakpoint"));
|
||||
}
|
||||
else {
|
||||
TargetBreakpointContainer container = (TargetBreakpointContainer) obj;
|
||||
TargetBreakpointSpecContainer container = (TargetBreakpointSpecContainer) obj;
|
||||
breakpointDialog.setContainer(container);
|
||||
tool.showDialog(breakpointDialog);
|
||||
}
|
||||
|
@ -1584,114 +1579,145 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accessibilityChanged(TargetAccessConditioned object, boolean accessible) {
|
||||
//this.access = accessibility.equals(TargetAccessibility.ACCESSIBLE);
|
||||
plugin.getTool().contextChanged(this);
|
||||
}
|
||||
class MyObjectListener extends AnnotatedDebuggerAttributeListener {
|
||||
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
|
||||
|
||||
@Override
|
||||
public void consoleOutput(TargetObject console, Channel channel, String out) {
|
||||
//getPlugin().showConsole((TargetInterpreter) console);
|
||||
System.err.println("consoleOutput: " + out);
|
||||
}
|
||||
public MyObjectListener() {
|
||||
super(MethodHandles.lookup());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayChanged(TargetObject object, String display) {
|
||||
//System.err.println("displayChanged: " + display);
|
||||
if (ObjectContainer.visibleByDefault(object.getName())) {
|
||||
pane.signalDataChanged(getContainerByPath(object.getPath()));
|
||||
@AttributeCallback(TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME)
|
||||
public void accessibilityChanged(TargetObject object, boolean accessible) {
|
||||
//this.access = accessibility.equals(TargetAccessibility.ACCESSIBLE);
|
||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleOutput(TargetObject console, Channel channel, String out) {
|
||||
//getPlugin().showConsole((TargetInterpreter) console);
|
||||
System.err.println("consoleOutput: " + out);
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
|
||||
public void displayChanged(TargetObject object, String display) {
|
||||
//System.err.println("displayChanged: " + display);
|
||||
if (ObjectContainer.visibleByDefault(object.getName())) {
|
||||
pane.signalDataChanged(getContainerByPath(object.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetObject.MODIFIED_ATTRIBUTE_NAME)
|
||||
public void modifiedChanged(TargetObject object, boolean modified) {
|
||||
//System.err.println("modifiedChanged: " + display);
|
||||
if (ObjectContainer.visibleByDefault(object.getName())) {
|
||||
pane.signalDataChanged(getContainerByPath(object.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
||||
public void executionStateChanged(TargetObject object, TargetExecutionState state) {
|
||||
//this.state = state;
|
||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||
public void focusChanged(TargetObject object, TargetObject focused) {
|
||||
plugin.setFocus(object, focused);
|
||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
|
||||
//System.err.println("memoryUpdated");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryReadError(TargetObject memory, AddressRange range,
|
||||
DebuggerMemoryAccessException e) {
|
||||
System.err.println("memoryReadError");
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME)
|
||||
public void promptChanged(TargetObject interpreter, String prompt) {
|
||||
//System.err.println("promptChanged: " + prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
|
||||
Map<String, ? extends TargetObject> cachedElements = bank.getCachedElements();
|
||||
for (String key : cachedElements.keySet()) {
|
||||
TargetObject ref = cachedElements.get(key);
|
||||
displayChanged(ref, "registersUpdated");
|
||||
}
|
||||
Map<String, ?> cachedAttributes = bank.getCachedAttributes();
|
||||
for (String key : cachedAttributes.keySet()) {
|
||||
Object obj = cachedAttributes.get(key);
|
||||
if (obj instanceof TargetObject) {
|
||||
displayChanged((TargetObject) obj, "registersUpdated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
//System.err.println("local EC: " + parent);
|
||||
ObjectContainer container =
|
||||
parent == null ? null : getContainerByPath(parent.getPath());
|
||||
if (container != null) {
|
||||
container.augmentElements(removed, added);
|
||||
boolean visibleChange = false;
|
||||
for (String key : removed) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
for (String key : added.keySet()) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
if (visibleChange) {
|
||||
container.propagateProvider(DebuggerObjectsProvider.this);
|
||||
update(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
super.attributesChanged(parent, removed, added);
|
||||
//System.err.println("local AC: " + parent + ":" + removed + ":" + added);
|
||||
ObjectContainer container =
|
||||
parent == null ? null : getContainerByPath(parent.getPath());
|
||||
if (container != null) {
|
||||
container.augmentAttributes(removed, added);
|
||||
boolean visibleChange = false;
|
||||
for (String key : removed) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
for (String key : added.keySet()) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
if (visibleChange) {
|
||||
container.propagateProvider(DebuggerObjectsProvider.this);
|
||||
update(container);
|
||||
}
|
||||
}
|
||||
if (parent != null && isAutorecord() &&
|
||||
parent.getCachedAttribute(TargetExecutionStateful.STATE_ATTRIBUTE_NAME) != null) {
|
||||
TargetProcess proc = DebugModelConventions.liveProcessOrNull(parent);
|
||||
if (proc != null) {
|
||||
startRecording(proc, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executionStateChanged(TargetExecutionStateful object, TargetExecutionState state) {
|
||||
//this.state = state;
|
||||
plugin.getTool().contextChanged(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusChanged(TargetFocusScope object, TargetObject focused) {
|
||||
plugin.setFocus(object, focused);
|
||||
plugin.getTool().contextChanged(this);
|
||||
}
|
||||
|
||||
public void setFocus(TargetFocusScope object, TargetObject focused) {
|
||||
public void setFocus(TargetObject object, TargetObject focused) {
|
||||
if (focused.getModel() != currentModel) {
|
||||
return;
|
||||
}
|
||||
pane.setFocus(object, focused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryUpdated(TargetMemory memory, Address address, byte[] data) {
|
||||
//System.err.println("memoryUpdated");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryReadError(TargetMemory memory, AddressRange range,
|
||||
DebuggerMemoryAccessException e) {
|
||||
System.err.println("memoryReadError");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void promptChanged(TargetInterpreter interpreter, String prompt) {
|
||||
System.err.println("promptChanged: " + prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registersUpdated(TargetRegisterBank bank, Map<String, byte[]> updates) {
|
||||
Map<String, ? extends TargetObject> cachedElements = bank.getCachedElements();
|
||||
for (String key : cachedElements.keySet()) {
|
||||
TargetObject ref = cachedElements.get(key);
|
||||
if (ref instanceof TargetObject) {
|
||||
displayChanged(ref, "registersUpdated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
//System.err.println("local EC: " + parent);
|
||||
ObjectContainer container = parent == null ? null : getContainerByPath(parent.getPath());
|
||||
if (container != null) {
|
||||
container.augmentElements(removed, added);
|
||||
boolean visibleChange = false;
|
||||
for (String key : removed) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
for (String key : added.keySet()) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
if (visibleChange) {
|
||||
container.propagateProvider(this);
|
||||
update(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
//System.err.println("local AC: " + parent + ":" + removed + ":" + added);
|
||||
ObjectContainer container = parent == null ? null : getContainerByPath(parent.getPath());
|
||||
if (container != null) {
|
||||
container.augmentAttributes(removed, added);
|
||||
boolean visibleChange = false;
|
||||
for (String key : removed) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
for (String key : added.keySet()) {
|
||||
visibleChange |= ObjectContainer.visibleByDefault(key);
|
||||
}
|
||||
if (visibleChange) {
|
||||
container.propagateProvider(this);
|
||||
update(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DebuggerTraceManagerService getTraceManager() {
|
||||
return traceManager;
|
||||
}
|
||||
|
@ -1797,4 +1823,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
|
|||
return listingService;
|
||||
}
|
||||
|
||||
public DebuggerModelListener getListener() {
|
||||
return listener.reorderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,11 +20,8 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import org.jdom.Element;
|
||||
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
|
||||
public class ObjectContainer implements Comparable<ObjectContainer> {
|
||||
|
@ -157,18 +154,16 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
|||
});
|
||||
}
|
||||
|
||||
/*
|
||||
protected void checkAutoRecord() {
|
||||
if (targetObject != null && provider.isAutorecord()) {
|
||||
TargetProcess proc = DebugModelConventions.liveProcessOrNull(targetObject);
|
||||
if (proc != null) {
|
||||
provider.startRecording(proc, false).exceptionally(ex -> {
|
||||
Msg.error("Could not record and/or open target: " + targetObject, ex);
|
||||
return null;
|
||||
});
|
||||
provider.startRecording(proc, false);
|
||||
}
|
||||
// Note that the recorder seeds its own listener with its target
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public void augmentElements(Collection<String> elementsRemoved,
|
||||
Map<String, ? extends TargetObject> elementsAdded) {
|
||||
|
@ -340,7 +335,7 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
|||
c.propagateProvider(provider);
|
||||
}
|
||||
provider.fireObjectUpdated(this);
|
||||
checkAutoRecord();
|
||||
//checkAutoRecord();
|
||||
}
|
||||
|
||||
// This should only be called once when the connection is activated
|
||||
|
|
|
@ -26,7 +26,7 @@ import javax.swing.*;
|
|||
import docking.DialogComponentProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSetBreakpointAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.util.MessageType;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -34,7 +34,7 @@ import ghidra.util.layout.PairLayout;
|
|||
|
||||
public class DebuggerBreakpointDialog extends DialogComponentProvider {
|
||||
protected DebuggerObjectsProvider provider;
|
||||
protected TargetBreakpointContainer container;
|
||||
protected TargetBreakpointSpecContainer container;
|
||||
|
||||
protected JTextField expressionField;
|
||||
|
||||
|
@ -78,7 +78,7 @@ public class DebuggerBreakpointDialog extends DialogComponentProvider {
|
|||
|
||||
setStatusText("Adding");
|
||||
Set<TargetBreakpointKind> kinds = new HashSet<>();
|
||||
kinds.add(TargetBreakpointKind.SOFTWARE);
|
||||
kinds.add(TargetBreakpointKind.SW_EXECUTE);
|
||||
container.placeBreakpoint(expression, kinds).exceptionally(e -> {
|
||||
Msg.showError(this, getComponent(), "Could not set breakpoint", e);
|
||||
setStatusText("Could not set breakpoint: " + e.getMessage(), MessageType.ERROR);
|
||||
|
@ -87,7 +87,7 @@ public class DebuggerBreakpointDialog extends DialogComponentProvider {
|
|||
close();
|
||||
}
|
||||
|
||||
public void setContainer(TargetBreakpointContainer container) {
|
||||
public void setContainer(TargetBreakpointSpecContainer container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
|
@ -172,6 +173,11 @@ public class DummyTargetObject implements TargetObject {
|
|||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ? extends TargetObject> getCallbackElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends Map<String, ?>> fetchAttributes() {
|
||||
if (!key.equals(TargetObject.DISPLAY_ATTRIBUTE_NAME)) {
|
||||
|
@ -191,7 +197,6 @@ public class DummyTargetObject implements TargetObject {
|
|||
if (type != null) {
|
||||
addAttribute(TargetObject.TYPE_ATTRIBUTE_NAME, type);
|
||||
}
|
||||
addAttribute(TargetObject.UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED);
|
||||
}
|
||||
// Why not completedFuture(attributes)?
|
||||
return CompletableFuture.supplyAsync(() -> attributes);
|
||||
|
@ -203,11 +208,16 @@ public class DummyTargetObject implements TargetObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void addListener(TargetObjectListener l) {
|
||||
public Map<String, ?> getCallbackAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(TargetObjectListener l) {
|
||||
public void addListener(DebuggerModelListener l) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(DebuggerModelListener l) {
|
||||
}
|
||||
|
||||
public String getJoinedPath() {
|
||||
|
|
|
@ -84,14 +84,16 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
|
|||
if (cf != null) {
|
||||
// NB: We're allowed to do this because we're guaranteed to be
|
||||
// in our own thread by the GTreeSlowLoadingNode
|
||||
ObjectContainer oc = cf.get(5, TimeUnit.SECONDS);
|
||||
ObjectContainer oc = cf.get(60, TimeUnit.SECONDS);
|
||||
return tree.update(oc);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
// Ignore
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
Msg.warn(this, e);
|
||||
//e.printStackTrace();
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
Msg.showWarn(this, container.getProvider().getComponent(), "Timeout Exception",
|
||||
"Request for children timed - out - try refreshing the node");
|
||||
}
|
||||
}
|
||||
List<GTreeNode> list = new ArrayList<>();
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.util.List;
|
|||
import javax.swing.JComponent;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
public interface ObjectPane {
|
||||
|
@ -45,7 +44,7 @@ public interface ObjectPane {
|
|||
|
||||
public String getName();
|
||||
|
||||
public void setFocus(TargetFocusScope object, TargetObject focused);
|
||||
public void setFocus(TargetObject object, TargetObject focused);
|
||||
|
||||
public void setRoot(ObjectContainer root, TargetObject targetObject);
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
|||
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
|
@ -269,7 +268,7 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setFocus(TargetFocusScope object, TargetObject focused) {
|
||||
public void setFocus(TargetObject object, TargetObject focused) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
setSelectedObject(focused);
|
||||
});
|
||||
|
|
|
@ -40,7 +40,8 @@ import ghidra.app.services.*;
|
|||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetAccessConditioned;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
import ghidra.util.*;
|
||||
|
@ -352,7 +353,7 @@ public class ObjectTree implements ObjectPane {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setFocus(TargetFocusScope object, TargetObject focused) {
|
||||
public void setFocus(TargetObject object, TargetObject focused) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
List<String> path = focused.getPath();
|
||||
tree.setSelectedNodeByNamePath(addRootNameToPath(path));
|
||||
|
@ -395,7 +396,7 @@ public class ObjectTree implements ObjectPane {
|
|||
DebuggerObjectsProvider provider = getProvider();
|
||||
ObjectContainer oc = node.getContainer();
|
||||
provider.deleteFromMap(oc);
|
||||
oc.getTargetObject().removeListener(provider);
|
||||
oc.getTargetObject().removeListener(provider.getListener());
|
||||
nodeMap.remove(path(node.getContainer()));
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,7 @@ import ghidra.util.Msg;
|
|||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class, //
|
||||
eventsConsumed = { TraceActivatedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
|
|
|
@ -29,6 +29,8 @@ import javax.swing.*;
|
|||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.*;
|
||||
|
@ -44,6 +46,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerProvider;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncLazyValue;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.base.widgets.table.DataTypeTableCellEditor;
|
||||
import ghidra.dbg.error.DebuggerModelAccessException;
|
||||
|
@ -285,17 +288,11 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
class RegAccessListener implements TraceRecorderListener {
|
||||
@Override
|
||||
public void registerBankMapped(TraceRecorder recorder) {
|
||||
if (readTheseCoords) {
|
||||
return;
|
||||
}
|
||||
Swing.runIfSwingOrRunLater(() -> loadValues());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAccessibilityChanged(TraceRecorder recorder) {
|
||||
if (readTheseCoords) {
|
||||
return;
|
||||
}
|
||||
Swing.runIfSwingOrRunLater(() -> loadValues());
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +360,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
|
||||
DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE;
|
||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
private boolean readTheseCoords = false; /* "read" past tense */
|
||||
private AsyncLazyValue<Void> readTheseCoords =
|
||||
new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible); /* "read" past tense */
|
||||
private Trace currentTrace; // Copy for transition
|
||||
private TraceRecorder currentRecorder; // Copy of transition
|
||||
|
||||
|
@ -707,7 +705,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
previous = current;
|
||||
current = coordinates;
|
||||
|
||||
readTheseCoords = false;
|
||||
readTheseCoords = new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible);
|
||||
doSetTrace(current.getTrace());
|
||||
doSetRecorder(current.getRecorder());
|
||||
updateSubTitle();
|
||||
|
@ -1098,7 +1096,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
return AsyncUtils.NIL;
|
||||
}
|
||||
regsTableModel.fireTableDataChanged();
|
||||
return readRegistersIfLiveAndAccessible();
|
||||
//return AsyncUtils.NIL;
|
||||
// In case we need to read a non-zero frame
|
||||
return readTheseCoords.request();
|
||||
}
|
||||
|
||||
private Set<Register> baseRegisters(Set<Register> regs) {
|
||||
|
@ -1113,10 +1113,13 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
if (recorder.getSnap() != current.getSnap()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
if (current.getFrame() == 0) {
|
||||
// Should have been pushed by model. non-zero frames are poll-only
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
TraceThread traceThread = current.getThread();
|
||||
TargetThread targetThread = recorder.getTargetThread(traceThread);
|
||||
if (targetThread == null ||
|
||||
!recorder.isRegisterBankAccessible(traceThread, current.getFrame())) {
|
||||
if (targetThread == null) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
Set<Register> toRead = new HashSet<>(baseRegisters(getSelectionFor(traceThread)));
|
||||
|
@ -1126,16 +1129,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
return AsyncUtils.NIL;
|
||||
}
|
||||
toRead.retainAll(regMapper.getRegistersOnTarget());
|
||||
TargetRegisterBank bank =
|
||||
recorder.getTargetRegisterBank(traceThread, current.getFrame());
|
||||
if (!bank.isValid()) {
|
||||
TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame());
|
||||
if (bank == null || !bank.isValid()) {
|
||||
Msg.error(this, "Current frame's bank does not exist");
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
CompletableFuture<Void> future =
|
||||
recorder.captureThreadRegisters(traceThread, current.getFrame(), toRead);
|
||||
return future.thenAccept(__ -> {
|
||||
readTheseCoords = true;
|
||||
}).exceptionally(ex -> {
|
||||
return future.exceptionally(ex -> {
|
||||
ex = AsyncUtils.unwrapThrowable(ex);
|
||||
if (ex instanceof DebuggerModelAccessException) {
|
||||
String msg =
|
||||
|
@ -1147,7 +1148,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
Msg.showError(this, getComponent(), "Read Target Registers",
|
||||
"Could not read target registers for selected thread", ex);
|
||||
}
|
||||
return null;
|
||||
return ExceptionUtils.rethrow(ex);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -221,6 +221,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
|||
}
|
||||
|
||||
private void connect(ActionEvent evt) {
|
||||
connectButton.setEnabled(false);
|
||||
for (Map.Entry<Property<?>, PropertyEditor> ent : propertyEditors.entrySet()) {
|
||||
Property<?> prop = ent.getKey();
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -232,11 +233,13 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
|||
modelService.addModel(model);
|
||||
setStatusText("");
|
||||
close();
|
||||
connectButton.setEnabled(true);
|
||||
return CompletableFuture.runAsync(() -> modelService.activateModel(model),
|
||||
SwingExecutorService.INSTANCE);
|
||||
}).exceptionally(e -> {
|
||||
Msg.showError(this, getComponent(), "Could not connect", e);
|
||||
setStatusText("Could not connect: " + e.getMessage(), MessageType.ERROR);
|
||||
connectButton.setEnabled(true);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.target;
|
||||
|
||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.showError;
|
||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
@ -290,7 +290,7 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
|
|||
// TODO: Ensure when tree is populated, correct model is selected
|
||||
}
|
||||
// Note, setSelectedNode does not take EventOrigin
|
||||
tree.setSelectionPaths(new TreePath[] { node.getTreePath() }, EventOrigin.USER_GENERATED);
|
||||
tree.setSelectionPaths(new TreePath[] { node.getTreePath() }, EventOrigin.API_GENERATED);
|
||||
}
|
||||
|
||||
protected void clearServiceCaches(DebuggerModelService service) {
|
||||
|
|
|
@ -29,8 +29,7 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
|||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
TraceOpenedPluginEvent.class, //
|
||||
eventsConsumed = { TraceOpenedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
TraceActivatedPluginEvent.class, //
|
||||
}, //
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
package ghidra.app.plugin.core.debug.mapping;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
|
@ -35,22 +34,16 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
|
|||
* @param target the target to be recorded, usually a process
|
||||
* @return a future which completes with the set of offers
|
||||
*/
|
||||
public static CompletableFuture<List<DebuggerMappingOffer>> queryOpinions(
|
||||
TargetObject target) {
|
||||
public static List<DebuggerMappingOffer> queryOpinions(TargetObject target) {
|
||||
List<DebuggerMappingOffer> result = new ArrayList<>();
|
||||
AsyncFence fence = new AsyncFence();
|
||||
for (DebuggerMappingOpinion opinion : ClassSearcher
|
||||
.getInstances(DebuggerMappingOpinion.class)) {
|
||||
fence.include(opinion.getOffers(target).thenAccept(offers -> {
|
||||
synchronized (result) {
|
||||
result.addAll(offers);
|
||||
}
|
||||
}));
|
||||
synchronized (result) {
|
||||
result.addAll(opinion.getOffers(target));
|
||||
}
|
||||
}
|
||||
return fence.ready().thenApply(__ -> {
|
||||
result.sort(HIGHEST_CONFIDENCE_FIRST);
|
||||
return result;
|
||||
});
|
||||
result.sort(HIGHEST_CONFIDENCE_FIRST);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,5 +52,18 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
|
|||
* @param target the target, usually a process
|
||||
* @return a future which completes with true if it knows, false if not
|
||||
*/
|
||||
CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target);
|
||||
public default Set<DebuggerMappingOffer> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return Set.of();
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
DebuggerObjectModel model = process.getModel();
|
||||
List<String> pathToEnv =
|
||||
model.getRootSchema().searchForSuitable(TargetEnvironment.class, process.getPath());
|
||||
TargetEnvironment env = (TargetEnvironment) model.getModelObject(pathToEnv);
|
||||
return offersForEnv(env, process);
|
||||
}
|
||||
|
||||
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process);
|
||||
|
||||
}
|
||||
|
|
|
@ -20,14 +20,13 @@ import java.util.*;
|
|||
import ghidra.app.plugin.core.debug.register.RegisterTypeInfo;
|
||||
import ghidra.dbg.target.TargetRegister;
|
||||
import ghidra.dbg.target.TargetRegisterContainer;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.data.PointerDataType;
|
||||
import ghidra.program.model.lang.*;
|
||||
|
||||
public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
|
||||
protected final Language language;
|
||||
protected final CompilerSpec cspec;
|
||||
protected final TargetRegisterContainer targetRegContainer;
|
||||
//protected final TargetRegisterContainer targetRegContainer;
|
||||
protected final boolean caseSensitive;
|
||||
|
||||
protected final Map<String, Register> languageRegs = new LinkedHashMap<>();
|
||||
|
@ -41,7 +40,7 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
|
|||
TargetRegisterContainer targetRegContainer, boolean caseSensitive) {
|
||||
this.language = cSpec.getLanguage();
|
||||
this.cspec = cSpec;
|
||||
this.targetRegContainer = targetRegContainer;
|
||||
//this.targetRegContainer = targetRegContainer;
|
||||
this.caseSensitive = caseSensitive;
|
||||
|
||||
this.instrCtrTypeInfo = new RegisterTypeInfo(PointerDataType.dataType,
|
||||
|
@ -65,6 +64,16 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
|
|||
}
|
||||
}
|
||||
|
||||
protected synchronized Register considerRegister(String index) {
|
||||
String name = normalizeName(index);
|
||||
Register lReg = filtLanguageRegs.get(name);
|
||||
if (lReg == null) {
|
||||
return null;
|
||||
}
|
||||
languageRegs.put(name, lReg);
|
||||
return lReg;
|
||||
}
|
||||
|
||||
protected synchronized Register considerRegister(TargetRegister tReg) {
|
||||
String name = normalizeName(tReg.getIndex());
|
||||
Register lReg = filtLanguageRegs.get(name);
|
||||
|
@ -132,17 +141,17 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
|
|||
|
||||
@Override
|
||||
public synchronized void targetRegisterAdded(TargetRegister register) {
|
||||
if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
|
||||
return;
|
||||
}
|
||||
//if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
|
||||
// return;
|
||||
//}
|
||||
considerRegister(register);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void targetRegisterRemoved(TargetRegister register) {
|
||||
if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
|
||||
return;
|
||||
}
|
||||
//if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
|
||||
// return;
|
||||
//}
|
||||
removeRegister(register);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,19 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized Register considerRegister(String index) {
|
||||
Register lReg = super.considerRegister(index);
|
||||
if (lReg == null) {
|
||||
return null;
|
||||
}
|
||||
//synchronized (present) {
|
||||
present.computeIfAbsent(lReg.getBaseRegister(), r -> new TreeSet<>(LENGTH_COMPARATOR))
|
||||
.add(lReg);
|
||||
//}
|
||||
return lReg;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized Register considerRegister(TargetRegister tReg) {
|
||||
Register lReg = super.considerRegister(tReg);
|
||||
|
@ -120,7 +133,10 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
|
|||
}
|
||||
Register lReg = languageRegs.get(normalizeName(tRegName));
|
||||
if (lReg == null) {
|
||||
return null;
|
||||
lReg = considerRegister(tRegName);
|
||||
if (lReg == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Register lbReg = lReg.getBaseRegister();
|
||||
TreeSet<Register> subs = present.get(lbReg);
|
||||
|
|
|
@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.platform;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
|
||||
|
@ -57,8 +55,7 @@ public class DbgengX64DebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DebuggerRegisterMapper createRegisterMapper(
|
||||
TargetRegisterContainer registers) {
|
||||
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
|
||||
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
|
||||
}
|
||||
}
|
||||
|
@ -85,19 +82,7 @@ public class DbgengX64DebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
|
||||
TargetProcess process) {
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (env == null || !env.getDebugger().toLowerCase().contains("dbg")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
|
|
@ -16,22 +16,19 @@
|
|||
package ghidra.app.plugin.core.debug.platform;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetEnvironment;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.program.model.lang.CompilerSpecID;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
|
||||
public class GdbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
||||
protected static final LanguageID LANG_ID_ARM_LE_V8 = new LanguageID("ARM:LE:32:v8");
|
||||
protected static final LanguageID LANG_ID_ARM_BE_V8 = new LanguageID("ARM:BE:32:v8");
|
||||
protected static final LanguageID LANG_ID_AARCH64_LE_V8A =
|
||||
new LanguageID("AARCH64:LE:64:v8A");
|
||||
protected static final LanguageID LANG_ID_AARCH64_BE_V8A =
|
||||
new LanguageID("AARCH64:BE:64:v8A");
|
||||
protected static final LanguageID LANG_ID_AARCH64_LE_V8A = new LanguageID("AARCH64:LE:64:v8A");
|
||||
protected static final LanguageID LANG_ID_AARCH64_BE_V8A = new LanguageID("AARCH64:BE:64:v8A");
|
||||
protected static final CompilerSpecID COMP_ID_DEFAULT = new CompilerSpecID("default");
|
||||
|
||||
protected static class GdbArmLELinuxOffer extends AbstractGdbDebuggerMappingOffer {
|
||||
|
@ -48,19 +45,7 @@ public class GdbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
|
||||
TargetProcess process) {
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (!env.getDebugger().toLowerCase().contains("gdb")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
package ghidra.app.plugin.core.debug.platform;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetEnvironment;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.program.model.lang.CompilerSpecID;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
|
||||
|
@ -77,16 +76,6 @@ public class GdbMipsDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (!env.getDebugger().toLowerCase().contains("gdb")) {
|
||||
return Set.of();
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
package ghidra.app.plugin.core.debug.platform;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetEnvironment;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.program.model.lang.CompilerSpecID;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
|
||||
|
@ -63,16 +62,6 @@ public class GdbPowerPCDebuggerMappingOpinion implements DebuggerMappingOpinion
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (!env.getDebugger().toLowerCase().contains("gdb")) {
|
||||
return Set.of();
|
||||
|
|
|
@ -16,10 +16,8 @@
|
|||
package ghidra.app.plugin.core.debug.platform;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -114,19 +112,7 @@ public class GdbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
|
||||
TargetProcess process) {
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (!env.getDebugger().toLowerCase().contains("gdb")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
|
|
@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.platform;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
|
||||
|
@ -44,8 +42,7 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DebuggerRegisterMapper createRegisterMapper(
|
||||
TargetRegisterContainer registers) {
|
||||
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
|
||||
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
|
||||
}
|
||||
}
|
||||
|
@ -66,23 +63,11 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
protected static boolean containsRecognizedJvmName(String name) {
|
||||
return DALVIK_VM_NAMES.stream().anyMatch(name::contains);
|
||||
}
|
||||
|
||||
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
|
||||
TargetProcess process) {
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (!env.getDebugger().contains("Java Debug Interface")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
|
|
@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.platform;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
|
||||
|
@ -44,8 +42,7 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DebuggerRegisterMapper createRegisterMapper(
|
||||
TargetRegisterContainer registers) {
|
||||
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
|
||||
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
|
||||
}
|
||||
}
|
||||
|
@ -66,23 +63,11 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
|
||||
if (!(target instanceof TargetProcess)) {
|
||||
return CompletableFuture.completedFuture(Set.of());
|
||||
}
|
||||
TargetProcess process = (TargetProcess) target;
|
||||
CompletableFuture<? extends TargetEnvironment> futureEnv =
|
||||
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
|
||||
return futureEnv.thenApply(env -> offersForEnv(env, process));
|
||||
}
|
||||
|
||||
protected static boolean containsRecognizedJvmName(String name) {
|
||||
return JVM_NAMES.stream().anyMatch(name::contains);
|
||||
}
|
||||
|
||||
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
|
||||
TargetProcess process) {
|
||||
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
|
||||
if (!env.getDebugger().contains("Java Debug Interface")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
|
|
@ -369,7 +369,7 @@ interface LogicalBreakpointInternal extends LogicalBreakpoint {
|
|||
Set<TargetBreakpointKind> tKinds =
|
||||
TraceRecorder.traceToTargetBreakpointKinds(kinds);
|
||||
|
||||
for (TargetBreakpointContainer cont : recorder
|
||||
for (TargetBreakpointSpecContainer cont : recorder
|
||||
.collectBreakpointContainers(null)) {
|
||||
LinkedHashSet<TargetBreakpointKind> supKinds = new LinkedHashSet<>(tKinds);
|
||||
supKinds.retainAll(cont.getSupportedBreakpointKinds());
|
||||
|
|
|
@ -18,17 +18,17 @@ package ghidra.app.plugin.core.debug.service.breakpoint;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.dbg.target.TargetBreakpointContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public class PlaceBreakpointActionItem implements BreakpointActionItem {
|
||||
private final TargetBreakpointContainer container;
|
||||
private final TargetBreakpointSpecContainer container;
|
||||
private final Address address;
|
||||
private final long length;
|
||||
private final Set<TargetBreakpointKind> kinds;
|
||||
|
||||
public PlaceBreakpointActionItem(TargetBreakpointContainer container, Address address,
|
||||
public PlaceBreakpointActionItem(TargetBreakpointSpecContainer container, Address address,
|
||||
long length, Collection<TargetBreakpointKind> kinds) {
|
||||
this.container = Objects.requireNonNull(container);
|
||||
this.address = Objects.requireNonNull(address);
|
||||
|
|
|
@ -17,7 +17,6 @@ package ghidra.app.plugin.core.debug.service.model;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.event.*;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
||||
|
@ -133,5 +132,5 @@ public interface DebuggerModelServiceInternal extends DebuggerModelService {
|
|||
* @param target the target to record
|
||||
* @return a future which completes with the resulting recorder, unless cancelled
|
||||
*/
|
||||
CompletableFuture<TraceRecorder> doRecordTargetPromptOffers(PluginTool t, TargetObject target);
|
||||
TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target);
|
||||
}
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.service.model;
|
||||
|
||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.showError;
|
||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.CharBuffer;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
@ -33,15 +34,12 @@ import docking.ActionContext;
|
|||
import docking.action.DockingAction;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.dbg.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetFocusScope.TargetFocusScopeListener;
|
||||
import ghidra.dbg.target.TargetObject.TargetObjectListener;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.FrontEndOnly;
|
||||
|
@ -59,17 +57,8 @@ import ghidra.util.classfinder.ClassSearcher;
|
|||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger models manager service",
|
||||
description = "Manage debug sessions, connections, and trace recording",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.HIDDEN,
|
||||
servicesRequired = {
|
||||
},
|
||||
servicesProvided = {
|
||||
DebuggerModelService.class,
|
||||
})
|
||||
@PluginInfo(shortDescription = "Debugger models manager service", description = "Manage debug sessions, connections, and trace recording", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.HIDDEN, servicesRequired = {}, servicesProvided = {
|
||||
DebuggerModelService.class, })
|
||||
public class DebuggerModelServicePlugin extends Plugin
|
||||
implements DebuggerModelServiceInternal, FrontEndOnly {
|
||||
|
||||
|
@ -83,7 +72,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
protected TargetObject root;
|
||||
protected TargetFocusScope focusScope;
|
||||
|
||||
protected TargetObjectListener forRemoval = new TargetObjectListener() {
|
||||
protected DebuggerModelListener forRemoval = new DebuggerModelListener() {
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
synchronized (listenersByModel) {
|
||||
|
@ -99,19 +88,20 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
}
|
||||
};
|
||||
|
||||
protected TargetFocusScopeListener forFocus = new TargetFocusScopeListener() {
|
||||
@Override
|
||||
public void focusChanged(TargetFocusScope object, TargetObject focused) {
|
||||
fireFocusEvent(focused);
|
||||
List<DebuggerModelServiceProxyPlugin> copy;
|
||||
synchronized (proxies) {
|
||||
copy = List.copyOf(proxies);
|
||||
protected DebuggerModelListener forFocus =
|
||||
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
|
||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||
public void focusChanged(TargetObject object, TargetObject focused) {
|
||||
fireFocusEvent(focused);
|
||||
List<DebuggerModelServiceProxyPlugin> copy;
|
||||
synchronized (proxies) {
|
||||
copy = List.copyOf(proxies);
|
||||
}
|
||||
for (DebuggerModelServiceProxyPlugin proxy : copy) {
|
||||
proxy.fireFocusEvent(focused);
|
||||
}
|
||||
}
|
||||
for (DebuggerModelServiceProxyPlugin proxy : copy) {
|
||||
proxy.fireFocusEvent(focused);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
protected ListenersForRemovalAndFocus(DebuggerObjectModel model) {
|
||||
this.model = model;
|
||||
|
@ -344,6 +334,10 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
}
|
||||
recorder = doBeginRecording(target, mapper);
|
||||
recorder.addListener(listenerOnRecorders);
|
||||
recorder.init().exceptionally(e -> {
|
||||
Msg.showError(this, null, "Record Trace", "Error initializing recorder", e);
|
||||
return null;
|
||||
});
|
||||
recordersByTarget.put(target, recorder);
|
||||
}
|
||||
recorderListeners.fire.elementAdded(recorder);
|
||||
|
@ -355,74 +349,64 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TraceRecorder> recordTargetBestOffer(TargetObject target) {
|
||||
public TraceRecorder recordTargetBestOffer(TargetObject target) {
|
||||
synchronized (recordersByTarget) {
|
||||
TraceRecorder recorder = recordersByTarget.get(target);
|
||||
if (recorder != null) {
|
||||
Msg.warn(this, "Target is already being recorded: " + target);
|
||||
return CompletableFuture.completedFuture(recorder);
|
||||
return recorder;
|
||||
}
|
||||
}
|
||||
return DebuggerMappingOpinion.queryOpinions(target).thenApply(offers -> {
|
||||
DebuggerTargetTraceMapper mapper = DebuggerMappingOffer.first(offers);
|
||||
if (mapper == null) {
|
||||
throw new NoSuchElementException("No mapper for target: " + target);
|
||||
}
|
||||
try {
|
||||
return recordTarget(target, mapper);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError("Could not record target: " + target, e);
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not query trace-recording opinions", ex);
|
||||
return null;
|
||||
});
|
||||
DebuggerTargetTraceMapper mapper =
|
||||
DebuggerMappingOffer.first(DebuggerMappingOpinion.queryOpinions(target));
|
||||
if (mapper == null) {
|
||||
throw new NoSuchElementException("No mapper for target: " + target);
|
||||
}
|
||||
try {
|
||||
return recordTarget(target, mapper);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError("Could not record target: " + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<TraceRecorder> doRecordTargetPromptOffers(PluginTool t,
|
||||
TargetObject target) {
|
||||
public TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target) {
|
||||
synchronized (recordersByTarget) {
|
||||
TraceRecorder recorder = recordersByTarget.get(target);
|
||||
if (recorder != null) {
|
||||
Msg.warn(this, "Target is already being recorded: " + target);
|
||||
return CompletableFuture.completedFuture(recorder);
|
||||
return recorder;
|
||||
}
|
||||
}
|
||||
return DebuggerMappingOpinion.queryOpinions(target).thenApply(offers -> {
|
||||
DebuggerMappingOffer selected;
|
||||
if (offers.size() == 1) {
|
||||
selected = offers.get(0);
|
||||
List<DebuggerMappingOffer> offers = DebuggerMappingOpinion.queryOpinions(target);
|
||||
DebuggerMappingOffer selected;
|
||||
if (offers.size() == 1) {
|
||||
selected = offers.get(0);
|
||||
}
|
||||
else {
|
||||
offerDialog.setOffers(offers);
|
||||
t.showDialog(offerDialog);
|
||||
// TODO: Is cancelled?
|
||||
if (offerDialog.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
offerDialog.setOffers(offers);
|
||||
t.showDialog(offerDialog);
|
||||
// TODO: Is cancelled?
|
||||
if (offerDialog.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
selected = offerDialog.getSelectedOffer();
|
||||
}
|
||||
assert selected != null;
|
||||
DebuggerTargetTraceMapper mapper = selected.take();
|
||||
try {
|
||||
return recordTarget(target, mapper);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError("Could not record target: " + target, e);
|
||||
// TODO: For certain errors, It may not be appropriate to close the dialog.
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
Msg.showError(this, null, DebuggerResources.AbstractRecordAction.NAME,
|
||||
"Could not query trace-recording opinions", ex);
|
||||
return null;
|
||||
});
|
||||
selected = offerDialog.getSelectedOffer();
|
||||
}
|
||||
assert selected != null;
|
||||
DebuggerTargetTraceMapper mapper = selected.take();
|
||||
try {
|
||||
return recordTarget(target, mapper);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError("Could not record target: " + target, e);
|
||||
// TODO: For certain errors, It may not be appropriate to close the dialog.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TraceRecorder> recordTargetPromptOffers(TargetObject target) {
|
||||
public TraceRecorder recordTargetPromptOffers(TargetObject target) {
|
||||
return doRecordTargetPromptOffers(tool, target);
|
||||
}
|
||||
|
||||
|
@ -546,10 +530,6 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
//DefaultTraceRecorder recorder = new DefaultTraceRecorder(this, trace, target, mapper);
|
||||
TraceRecorder recorder = mapper.startRecording(this, trace);
|
||||
trace.release(this); // The recorder now owns it (on behalf of the service)
|
||||
recorder.init().exceptionally(e -> {
|
||||
Msg.showError(this, null, "Record Trace", "Error initializing recorder", e);
|
||||
return null;
|
||||
});
|
||||
return recorder;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,21 +48,20 @@ import ghidra.util.datastruct.ListenerSet;
|
|||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger models manager service (proxy to front-end)", //
|
||||
description = "Manage debug sessions, connections, and trace recording", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
ProgramActivatedPluginEvent.class, //
|
||||
ProgramClosedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerTraceManagerService.class, //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerModelService.class, //
|
||||
} //
|
||||
shortDescription = "Debugger models manager service (proxy to front-end)", //
|
||||
description = "Manage debug sessions, connections, and trace recording", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = { ProgramActivatedPluginEvent.class, //
|
||||
ProgramClosedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerTraceManagerService.class, //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerModelService.class, //
|
||||
} //
|
||||
)
|
||||
public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||
implements DebuggerModelServiceInternal {
|
||||
|
@ -362,18 +361,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TraceRecorder> recordTargetBestOffer(TargetObject target) {
|
||||
public TraceRecorder recordTargetBestOffer(TargetObject target) {
|
||||
return delegate.recordTargetBestOffer(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TraceRecorder> doRecordTargetPromptOffers(PluginTool t,
|
||||
TargetObject target) {
|
||||
public TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target) {
|
||||
return delegate.doRecordTargetPromptOffers(t, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TraceRecorder> recordTargetPromptOffers(TargetObject target) {
|
||||
public TraceRecorder recordTargetPromptOffers(TargetObject target) {
|
||||
return doRecordTargetPromptOffers(tool, target);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/* ###
|
||||
* 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.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedBreakpointRecorder;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder {
|
||||
|
||||
public 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();
|
||||
}
|
||||
|
||||
private final DefaultTraceRecorder recorder;
|
||||
private final Trace trace;
|
||||
private final TraceBreakpointManager breakpointManager;
|
||||
final PermanentTransactionExecutor tx;
|
||||
|
||||
protected TargetBreakpointSpecContainer breakpointContainer;
|
||||
|
||||
public DefaultBreakpointRecorder(DefaultTraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
this.breakpointManager = trace.getBreakpointManager();
|
||||
this.tx = new PermanentTransactionExecutor(trace,
|
||||
"BreakpointRecorder:" + recorder.target.getJoinedPath("."),
|
||||
Executors::newCachedThreadPool, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerBreakpointContainer(TargetBreakpointSpecContainer bc) {
|
||||
if (breakpointContainer != null) {
|
||||
Msg.warn(this, "Already have a breakpoint container for this process");
|
||||
}
|
||||
breakpointContainer = bc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerBreakpointLocation(TargetObject containerParent,
|
||||
TargetBreakpointLocation bpt) {
|
||||
synchronized (this) {
|
||||
if (recorder.getMemoryMapper() == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
RecorderBreakpointLocationResolver resolver =
|
||||
new RecorderBreakpointLocationResolver(recorder, bpt);
|
||||
resolver.updateBreakpoint(containerParent, bpt);
|
||||
}
|
||||
|
||||
protected void doRecordBreakpoint(long snap, TargetBreakpointLocation loc,
|
||||
Set<TraceThread> traceThreads) {
|
||||
synchronized (this) {
|
||||
if (recorder.getMemoryMapper() == null) {
|
||||
throw new IllegalStateException(
|
||||
"No memory mapper! Have not recorded a region, yet.");
|
||||
}
|
||||
}
|
||||
String path = PathUtils.toString(loc.getPath());
|
||||
String name = nameBreakpoint(loc);
|
||||
Address traceAddr = recorder.getMemoryMapper().targetToTrace(loc.getAddress());
|
||||
AddressRange traceRange = range(traceAddr, loc.getLength());
|
||||
try {
|
||||
TargetBreakpointSpec spec = loc.getSpecification();
|
||||
boolean enabled = spec.isEnabled();
|
||||
Set<TraceBreakpointKind> traceKinds =
|
||||
TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds());
|
||||
TraceBreakpoint traceBpt =
|
||||
breakpointManager.placeBreakpoint(path, snap,
|
||||
traceRange, traceThreads, traceKinds, enabled, spec.getExpression());
|
||||
traceBpt.setName(name);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
Msg.error(this, "Could not record placed breakpoint: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordBreakpoint(TargetBreakpointLocation loc,
|
||||
Set<TraceThread> traceThreads) {
|
||||
String path = loc.getJoinedPath(".");
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Breakpoint " + path + " placed", () -> {
|
||||
doRecordBreakpoint(snap, loc, traceThreads);
|
||||
});
|
||||
}
|
||||
|
||||
protected void doRemoveBreakpointLocation(long snap, TargetBreakpointLocation loc) {
|
||||
String path = loc.getJoinedPath(".");
|
||||
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
|
||||
try {
|
||||
if (traceBpt.getPlacedSnap() > snap) {
|
||||
Msg.error(this,
|
||||
"Tracked, now removed breakpoint was placed in the future? " + path);
|
||||
}
|
||||
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) {
|
||||
Msg.error(this, "Could not record breakpoint removal: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBreakpointLocation(TargetBreakpointLocation loc) {
|
||||
String path = loc.getJoinedPath(".");
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Breakpoint " + path + " deleted", () -> {
|
||||
doRemoveBreakpointLocation(snap, loc);
|
||||
});
|
||||
}
|
||||
|
||||
protected void doBreakpointLengthChanged(long snap, int length, Address traceAddr,
|
||||
String path) {
|
||||
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 {
|
||||
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) {
|
||||
// Split, and length matters not
|
||||
Msg.error(this, "Could not record breakpoint length change: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void breakpointLengthChanged(int length, Address traceAddr, String path)
|
||||
throws AssertionError {
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Breakpoint length changed", () -> {
|
||||
doBreakpointLengthChanged(snap, length, traceAddr, path);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
|
||||
String path = PathUtils.toString(bpt.getPath());
|
||||
return breakpointManager.getPlacedBreakpointByPath(recorder.getSnap(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetBreakpointSpecContainer getBreakpointContainer() {
|
||||
return breakpointContainer;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DefaultDataTypeRecorder {
|
||||
|
||||
//private DefaultTraceRecorder recorder;
|
||||
private Trace trace;
|
||||
private final TargetDataTypeConverter typeConverter;
|
||||
|
||||
public DefaultDataTypeRecorder(DefaultTraceRecorder recorder) {
|
||||
//this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager());
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
|
||||
TaskMonitor monitor) {
|
||||
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 (RecorderPermanentTransaction tid =
|
||||
RecorderPermanentTransaction.start(trace, "Capture data types for " + path)) {
|
||||
// NOTE: createCategory is actually getOrCreate
|
||||
Category category =
|
||||
trace.getDataTypeManager().createCategory(new CategoryPath("/" + path));
|
||||
for (DataType dataType : converted) {
|
||||
category.addDataType(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> captureDataTypes(TargetModule targetModule,
|
||||
TaskMonitor monitor) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/* ###
|
||||
* 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.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.target.TargetMemory;
|
||||
import ghidra.dbg.target.TargetMemoryRegion;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
|
||||
|
||||
// For large memory captures
|
||||
private static final int BLOCK_SIZE = 4096;
|
||||
private static final long BLOCK_MASK = -1L << 12;
|
||||
|
||||
protected static AddressSetView expandToBlocks(AddressSetView asv) {
|
||||
AddressSet result = new AddressSet();
|
||||
// Not terribly efficient, but this is one range most of the time
|
||||
for (AddressRange range : asv) {
|
||||
AddressSpace space = range.getAddressSpace();
|
||||
Address min = space.getAddress(range.getMinAddress().getOffset() & BLOCK_MASK);
|
||||
Address max = space.getAddress(range.getMaxAddress().getOffset() | ~BLOCK_MASK);
|
||||
result.add(new AddressRangeImpl(min, max));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final DefaultTraceRecorder recorder;
|
||||
private final Trace trace;
|
||||
private final TraceMemoryManager memoryManager;
|
||||
final PermanentTransactionExecutor tx;
|
||||
|
||||
public DefaultMemoryRecorder(DefaultTraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
this.memoryManager = trace.getMemoryManager();
|
||||
this.tx = new PermanentTransactionExecutor(trace,
|
||||
"MemoryRecorder:" + recorder.target.getJoinedPath("."),
|
||||
Executors::newSingleThreadExecutor, 100);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> captureProcessMemory(AddressSetView set, TaskMonitor monitor) {
|
||||
// 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(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.
|
||||
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 = recorder.getMemoryMapper().traceToTarget(vRng);
|
||||
recorder.getProcessMemory()
|
||||
.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 void offerProcessRegion(TargetMemoryRegion region) {
|
||||
TargetMemory mem = region.getMemory();
|
||||
recorder.getProcessMemory().addRegion(region, mem);
|
||||
//recorder.objectManager.addMemory(mem);
|
||||
String path = PathUtils.toString(region.getPath());
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Memory region " + path + " added", () -> {
|
||||
try {
|
||||
TraceMemoryRegion traceRegion =
|
||||
memoryManager.getLiveRegionByPath(snap, path);
|
||||
if (traceRegion != null) {
|
||||
Msg.warn(this, "Region " + path + " already recorded");
|
||||
return;
|
||||
}
|
||||
traceRegion = memoryManager.addRegion(path, Range.atLeast(snap),
|
||||
recorder.getMemoryMapper().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) {
|
||||
Msg.error(this, "Failed to create region due to duplicate: " + e);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProcessRegion(TargetMemoryRegion region) {
|
||||
// Already removed from processMemory. That's how we knew to go here.
|
||||
String path = PathUtils.toString(region.getPath());
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Memory region " + path + " removed", () -> {
|
||||
try {
|
||||
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) {
|
||||
// Region is shrinking in time
|
||||
Msg.error(this, "Failed to record region removal: " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
|
||||
String path = PathUtils.toString(region.getPath());
|
||||
return memoryManager.getLiveRegionByPath(recorder.getSnap(), path);
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/* ###
|
||||
* 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.util.concurrent.Executors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedModuleRecorder;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetSection;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class DefaultModuleRecorder implements ManagedModuleRecorder {
|
||||
|
||||
private final DefaultTraceRecorder recorder;
|
||||
private final Trace trace;
|
||||
private final TraceModuleManager moduleManager;
|
||||
final PermanentTransactionExecutor tx;
|
||||
|
||||
public DefaultModuleRecorder(DefaultTraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
this.moduleManager = trace.getModuleManager();
|
||||
this.tx = new PermanentTransactionExecutor(trace,
|
||||
"ModuleRecorder:" + recorder.target.getJoinedPath("."),
|
||||
f -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), f), 500);
|
||||
}
|
||||
|
||||
protected TraceModule doRecordProcessModule(long snap, TargetModule module) {
|
||||
String path = module.getJoinedPath(".");
|
||||
if (recorder.getMemoryMapper() == null) {
|
||||
Msg.error(this, "Got module before memory mapper: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Short-circuit the DuplicateNameException for efficiency?
|
||||
TraceModule exists = moduleManager.getLoadedModuleByPath(snap, path);
|
||||
if (exists != null) {
|
||||
return exists;
|
||||
}
|
||||
|
||||
try {
|
||||
AddressRange targetRange = module.getRange();
|
||||
if (targetRange == null) {
|
||||
Msg.error(this, "Range not found for " + module);
|
||||
return null;
|
||||
}
|
||||
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(targetRange);
|
||||
return moduleManager.addLoadedModule(path, module.getModuleName(), traceRange, snap);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
// This resolves the race condition, since DB access is synchronized
|
||||
return moduleManager.getLoadedModuleByPath(snap, path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerProcessModule(TargetModule module) {
|
||||
long snap = recorder.getSnap();
|
||||
String path = module.getJoinedPath(".");
|
||||
tx.execute("Module " + path + " loaded", () -> {
|
||||
doRecordProcessModule(snap, module);
|
||||
});
|
||||
}
|
||||
|
||||
protected TraceSection doRecordProcessModuleSection(long snap, TargetSection section) {
|
||||
String path = section.getJoinedPath(".");
|
||||
if (recorder.getMemoryMapper() == null) {
|
||||
Msg.error(this, "Got module section before memory mapper: " + path);
|
||||
return null;
|
||||
}
|
||||
TraceModule traceModule = doRecordProcessModule(snap, section.getModule());
|
||||
if (traceModule == null) {
|
||||
return null; // Failure should already be logged
|
||||
}
|
||||
try {
|
||||
AddressRange targetRange = section.getRange();
|
||||
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(targetRange);
|
||||
return traceModule.addSection(path, section.getIndex(), traceRange);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
Msg.warn(this, path + " already recorded");
|
||||
return moduleManager.getLoadedSectionByPath(snap, path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerProcessModuleSection(TargetSection section) {
|
||||
long snap = recorder.getSnap();
|
||||
String path = section.getJoinedPath(".");
|
||||
tx.execute("Section " + path + " added", () -> {
|
||||
doRecordProcessModuleSection(snap, section);
|
||||
});
|
||||
}
|
||||
|
||||
protected void doRemoveProcessModule(long snap, TargetModule module) {
|
||||
String path = PathUtils.toString(module.getPath());
|
||||
//TraceThread eventThread = recorder.getSnapshot().getEventThread();
|
||||
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snap, path);
|
||||
if (traceModule == null) {
|
||||
Msg.warn(this, "unloaded " + path + " is not in the trace");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (traceModule.getLoadedSnap() == snap) {
|
||||
Msg.warn(this, "Observed module unload in the same snap as its load");
|
||||
//recorder.createSnapshot("WARN: Module removed", eventThread, tid);
|
||||
}
|
||||
traceModule.setUnloadedSnap(snap);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
Msg.error(this, "Could not record process module removed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProcessModule(TargetModule module) {
|
||||
long snap = recorder.getSnap();
|
||||
String path = PathUtils.toString(module.getPath());
|
||||
tx.execute("Module " + path + " unloaded", () -> {
|
||||
doRemoveProcessModule(snap, module);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceModule getTraceModule(TargetModule module) {
|
||||
String path = PathUtils.toString(module.getPath());
|
||||
return moduleManager.getLoadedModuleByPath(recorder.getSnap(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceSection getTraceSection(TargetSection section) {
|
||||
String path = PathUtils.toString(section.getPath());
|
||||
return moduleManager.getLoadedSectionByPath(recorder.getSnap(), path);
|
||||
}
|
||||
}
|
|
@ -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.service.model;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.TriConsumer;
|
||||
|
||||
public class DefaultProcessRecorder implements ManagedProcessRecorder {
|
||||
|
||||
private final AbstractRecorderMemory processMemory;
|
||||
protected final TriConsumer<Boolean, Boolean, Void> listenerProcMemAccChanged =
|
||||
this::processMemoryAccessibilityChanged;
|
||||
|
||||
private DefaultBreakpointRecorder breakpointRecorder;
|
||||
private DefaultTraceRecorder recorder;
|
||||
|
||||
public DefaultProcessRecorder(DefaultTraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
this.processMemory = new RecorderSimpleMemory();
|
||||
//this.processMemory = new RecorderComposedMemory(this.getProcessMemory());
|
||||
|
||||
//getProcessMemory().getMemAccListeners().add(listenerProcMemAccChanged);
|
||||
|
||||
this.breakpointRecorder = new DefaultBreakpointRecorder(recorder);
|
||||
|
||||
}
|
||||
|
||||
protected void processMemoryAccessibilityChanged(boolean old,
|
||||
boolean acc, Void __) {
|
||||
recorder.getListeners().fire.processMemoryAccessibilityChanged(recorder);
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {
|
||||
Address tStart = recorder.getMemoryMapper().traceToTarget(start);
|
||||
return getProcessMemory().readMemory(tStart, length);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) {
|
||||
Address tStart = recorder.getMemoryMapper().traceToTarget(start);
|
||||
return getProcessMemory().writeMemory(tStart, data);
|
||||
}
|
||||
|
||||
public AddressSetView getAccessibleProcessMemory() {
|
||||
// TODO: Efficiently distinguish which memory is process vs. thread
|
||||
///TODO Is this correct?
|
||||
return getProcessMemory().getAccessibleMemory(mem -> true, recorder.getMemoryMapper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractRecorderMemory getProcessMemory() {
|
||||
return processMemory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedBreakpointRecorder getBreakpointRecorder() {
|
||||
return breakpointRecorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return recorder.trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return recorder.getSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerMemoryMapper getMemoryMapper() {
|
||||
return recorder.getMemoryMapper();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.stack.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public class DefaultStackRecorder implements ManagedStackRecorder {
|
||||
|
||||
protected static int getFrameLevel(TargetStackFrame frame) {
|
||||
// TODO: A fair assumption? frames are elements with numeric base-10 indices
|
||||
return Integer.decode(frame.getIndex());
|
||||
}
|
||||
|
||||
private NavigableMap<Integer, TargetStackFrame> stack =
|
||||
Collections.synchronizedNavigableMap(new TreeMap<>());
|
||||
|
||||
private final TraceThread thread;
|
||||
private final DefaultTraceRecorder recorder;
|
||||
private final Trace trace;
|
||||
private final TraceStackManager stackManager;
|
||||
final PermanentTransactionExecutor tx;
|
||||
|
||||
public DefaultStackRecorder(TraceThread thread, DefaultTraceRecorder recorder) {
|
||||
this.thread = thread;
|
||||
this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
this.stackManager = trace.getStackManager();
|
||||
this.tx = new PermanentTransactionExecutor(trace,
|
||||
"ModuleRecorder:" + recorder.target.getJoinedPath("."),
|
||||
Executors::newSingleThreadExecutor, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerStackFrame(TargetStackFrame frame) {
|
||||
recordFrame(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStack() {
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Stack changed", () -> {
|
||||
TraceStack traceStack = stackManager.getStack(thread, snap, true);
|
||||
traceStack.setDepth(stackDepth(), false);
|
||||
for (Map.Entry<Integer, TargetStackFrame> ent : stack.entrySet()) {
|
||||
Address tracePc = recorder.getMemoryMapper()
|
||||
.targetToTrace(ent.getValue().getProgramCounter());
|
||||
doRecordFrame(traceStack, ent.getKey(), tracePc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void popStack() {
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Stack popped", () -> {
|
||||
TraceStack traceStack = stackManager.getStack(thread, snap, true);
|
||||
traceStack.setDepth(stackDepth(), false);
|
||||
});
|
||||
}
|
||||
|
||||
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
|
||||
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
|
||||
traceFrame.setProgramCounter(pc);
|
||||
}
|
||||
|
||||
public void recordFrame(TargetStackFrame frame) {
|
||||
tx.execute("Stack frame added", () -> {
|
||||
stack.put(getFrameLevel(frame), frame);
|
||||
DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper();
|
||||
if (memoryMapper == null) {
|
||||
return;
|
||||
}
|
||||
Address pc = frame.getProgramCounter();
|
||||
Address tracePc = pc == null ? null : memoryMapper.targetToTrace(pc);
|
||||
TraceStack traceStack = stackManager.getStack(thread, recorder.getSnap(), true);
|
||||
doRecordFrame(traceStack, getFrameLevel(frame), tracePc);
|
||||
});
|
||||
}
|
||||
|
||||
protected int stackDepth() {
|
||||
return stack.isEmpty() ? 0 : stack.lastKey() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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;
|
||||
}
|
||||
|
||||
protected boolean checkStackFrameRemoved(TargetObject invalid) {
|
||||
if (stack.values().remove(invalid)) {
|
||||
popStack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Address pcFromStack() {
|
||||
TargetStackFrame frame = stack.get(0);
|
||||
if (frame == null) {
|
||||
return null;
|
||||
}
|
||||
return frame.getProgramCounter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceStackFrame getTraceStackFrame(TraceThread thread, int level) {
|
||||
TraceStack latest = stackManager.getLatestStack(thread, recorder.getSnap());
|
||||
if (latest == null) {
|
||||
return null;
|
||||
}
|
||||
return latest.getFrame(level, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetStackFrame getTargetStackFrame(int frameLevel) {
|
||||
return stack.get(frameLevel);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/* ###
|
||||
* 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.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.symbol.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.InvalidInputException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DefaultSymbolRecorder {
|
||||
|
||||
private DefaultTraceRecorder recorder;
|
||||
private Trace trace;
|
||||
|
||||
public DefaultSymbolRecorder(DefaultTraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
|
||||
TaskMonitor monitor) {
|
||||
String path = PathUtils.toString(namespace.getPath());
|
||||
monitor.setMessage("Capturing symbols for " + path);
|
||||
return namespace.getSymbols().thenAccept(symbols -> {
|
||||
try (RecorderPermanentTransaction tid = RecorderPermanentTransaction.start(trace,
|
||||
"Capture types and symbols for " + path)) {
|
||||
TraceNamespaceSymbol ns = createNamespaceIfAbsent(path);
|
||||
monitor.setMessage("Capturing symbols for " + path);
|
||||
monitor.initialize(symbols.size());
|
||||
TraceEquateManager equateManager = trace.getEquateManager();
|
||||
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 = recorder.getMemoryMapper().targetToTrace(sym.getValue());
|
||||
try {
|
||||
trace.getSymbolManager()
|
||||
.labels()
|
||||
.create(recorder.getSnap(), 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.
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> captureSymbols(TargetModule targetModule,
|
||||
TaskMonitor monitor) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
private TraceNamespaceSymbol createNamespaceIfAbsent(String path) {
|
||||
|
||||
TraceSymbolManager symbolManager = trace.getSymbolManager();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,547 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.data.Pointer;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.listing.*;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.TimedMsg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||
|
||||
//private static final boolean LOG_STACK_TRACE = false;
|
||||
|
||||
private final TargetThread targetThread;
|
||||
private final TraceThread traceThread;
|
||||
|
||||
protected final AbstractRecorderMemory threadMemory;
|
||||
//private AbstractRecorderRegisterSet threadRegisters;
|
||||
protected TargetBreakpointSpecContainer threadBreakpointContainer;
|
||||
|
||||
protected Map<Integer, TargetRegisterBank> regs = new HashMap<>();
|
||||
protected Collection<TargetRegister> extraRegs;
|
||||
|
||||
protected TargetExecutionState state = TargetExecutionState.ALIVE;
|
||||
|
||||
private final DefaultTraceRecorder recorder;
|
||||
private final Trace trace;
|
||||
private final TraceObjectManager objectManager;
|
||||
|
||||
private final TraceMemoryManager memoryManager;
|
||||
|
||||
private DebuggerRegisterMapper regMapper;
|
||||
private final AbstractDebuggerTargetTraceMapper mapper;
|
||||
|
||||
private final DefaultStackRecorder stackRecorder;
|
||||
private final DefaultBreakpointRecorder breakpointRecorder;
|
||||
final PermanentTransactionExecutor tx;
|
||||
|
||||
protected static int getFrameLevel(TargetStackFrame frame) {
|
||||
// TODO: A fair assumption? frames are elements with numeric base-10 indices
|
||||
return Integer.decode(frame.getIndex());
|
||||
}
|
||||
|
||||
public DefaultThreadRecorder(DefaultTraceRecorder recorder,
|
||||
AbstractDebuggerTargetTraceMapper mapper, TargetThread targetThread,
|
||||
TraceThread traceThread) {
|
||||
this.recorder = recorder;
|
||||
this.mapper = mapper;
|
||||
this.trace = recorder.getTrace();
|
||||
this.objectManager = recorder.objectManager;
|
||||
|
||||
this.targetThread = targetThread;
|
||||
this.traceThread = traceThread;
|
||||
|
||||
this.memoryManager = trace.getMemoryManager();
|
||||
|
||||
this.tx = new PermanentTransactionExecutor(trace,
|
||||
"ThreadRecorder:" + recorder.target.getJoinedPath("."),
|
||||
f -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), f), 100);
|
||||
|
||||
//this.threadMemory = new RecorderComposedMemory(recorder.getProcessMemory());
|
||||
this.threadMemory = recorder.getProcessMemory();
|
||||
//this.threadRegisters = recorder.getThreadRegisters();
|
||||
|
||||
if (targetThread instanceof TargetExecutionStateful) {
|
||||
TargetExecutionStateful stateful = (TargetExecutionStateful) targetThread;
|
||||
state = stateful.getExecutionState();
|
||||
}
|
||||
|
||||
this.stackRecorder = new DefaultStackRecorder(traceThread, recorder);
|
||||
this.breakpointRecorder = new DefaultBreakpointRecorder(recorder);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
return objectManager.getRegMappers().get(registers).thenAccept(rm -> {
|
||||
synchronized (this) {
|
||||
regMapper = rm;
|
||||
Language language = trace.getBaseLanguage();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not intialize register mapper", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank 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(__ -> {
|
||||
recorder.getListeners().fire.registerBankMapped(recorder);
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not intialize register mapper", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> captureThreadRegisters(TraceThread thread, int frameLevel,
|
||||
Set<Register> registers) {
|
||||
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
|
||||
System.err.println("captureThreadRegisters " + thread + ":" + bank);
|
||||
return bank.readRegisters(tRegs).thenApply(__ -> null);
|
||||
}
|
||||
|
||||
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
|
||||
return regs.get(frameLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg, boolean removed) {
|
||||
String name = reg.getIndex();
|
||||
synchronized (this) {
|
||||
if (regMapper != rm) {
|
||||
return;
|
||||
}
|
||||
if (mapper.getExtraRegNames().contains(name)) {
|
||||
if (removed) {
|
||||
extraRegs.remove(reg);
|
||||
}
|
||||
else {
|
||||
extraRegs.add(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerRegisters(TargetRegisterBank bank) {
|
||||
if (regMapper == null) {
|
||||
doFetchAndInitRegMapper(bank);
|
||||
}
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
TargetRegisterBank old = regs.put(frameLevel, bank);
|
||||
if (null != old) {
|
||||
Msg.warn(this, "Unexpected register bank replacement");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRegisters(TargetRegisterBank bank) {
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
TargetRegisterBank old = regs.remove(frameLevel);
|
||||
if (bank != old) {
|
||||
Msg.warn(this, "Unexpected register bank upon removal");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerThreadRegion(TargetMemoryRegion region) {
|
||||
TargetMemory mem = region.getMemory();
|
||||
threadMemory.addRegion(region, mem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(final TargetExecutionState newState) {
|
||||
state = newState;
|
||||
}
|
||||
|
||||
public void threadDestroyed() {
|
||||
String path = getTargetThread().getJoinedPath(".");
|
||||
long snap = recorder.getSnap();
|
||||
tx.execute("Thread " + path + " destroyed", () -> {
|
||||
// TODO: Should it be key - 1
|
||||
// Perhaps, since the thread should not exist
|
||||
// But it could imply earlier destruction than actually observed
|
||||
try {
|
||||
getTraceThread().setDestructionSnap(snap);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
throw new AssertionError(e); // Should be shrinking
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordRegisterValues(TargetRegisterBank bank, Map<String, byte[]> updates) {
|
||||
synchronized (recorder) {
|
||||
if (regMapper == null) {
|
||||
doFetchAndInitRegMapper(bank);
|
||||
}
|
||||
}
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
long snap = recorder.getSnap();
|
||||
String path = bank.getJoinedPath(".");
|
||||
TimedMsg.info(this, "Reg values changed: " + updates.keySet());
|
||||
tx.execute("Registers " + path + " changed", () -> {
|
||||
TraceCodeManager codeManager = trace.getCodeManager();
|
||||
TraceCodeRegisterSpace codeRegisterSpace =
|
||||
codeManager.getCodeRegisterSpace(traceThread, false);
|
||||
TraceDefinedDataRegisterView definedData =
|
||||
codeRegisterSpace == null ? null : codeRegisterSpace.definedData();
|
||||
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(snap, rv);
|
||||
Register register = rv.getRegister();
|
||||
if (definedData != null) {
|
||||
TraceData td = definedData.getForRegister(snap, register);
|
||||
if (td != null && td.getDataType() instanceof Pointer) {
|
||||
Address addr = registerValueToTargetAddress(rv, ent.getValue());
|
||||
readAlignedConditionally(ent.getKey(), addr); // NB: Reports errors
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordRegisterValue(TargetRegister targetRegister, byte[] value) {
|
||||
TargetRegisterBank bank = (TargetRegisterBank) targetRegister.getParent();
|
||||
synchronized (recorder) {
|
||||
if (regMapper == null) {
|
||||
doFetchAndInitRegMapper(bank);
|
||||
}
|
||||
}
|
||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||
long snap = recorder.getSnap();
|
||||
String path = targetRegister.getJoinedPath(".");
|
||||
//TimedMsg.info(this, "Register value changed: " + targetRegister);
|
||||
tx.execute("Register " + path + " changed", () -> {
|
||||
TraceCodeManager codeManager = trace.getCodeManager();
|
||||
TraceCodeRegisterSpace codeRegisterSpace =
|
||||
codeManager.getCodeRegisterSpace(traceThread, false);
|
||||
TraceDefinedDataRegisterView definedData =
|
||||
codeRegisterSpace == null ? null : codeRegisterSpace.definedData();
|
||||
TraceMemoryRegisterSpace regSpace =
|
||||
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, true);
|
||||
String key = targetRegister.getName();
|
||||
if (PathUtils.isIndex(key)) {
|
||||
key = key.substring(1, key.length() - 1);
|
||||
}
|
||||
RegisterValue rv = regMapper.targetToTrace(key, value);
|
||||
if (rv == null) {
|
||||
return; // mapper does not know this register....
|
||||
}
|
||||
regSpace.setValue(snap, rv);
|
||||
Register register = rv.getRegister();
|
||||
if (definedData != null) {
|
||||
TraceData td = definedData.getForRegister(snap, register);
|
||||
if (td != null && td.getDataType() instanceof Pointer) {
|
||||
Address addr = registerValueToTargetAddress(rv, value);
|
||||
readAlignedConditionally(key, addr); // NB: Reports errors
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> writeThreadRegisters(int frameLevel,
|
||||
Map<Register, RegisterValue> values) {
|
||||
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(traceThread, 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);
|
||||
}
|
||||
|
||||
Address registerValueToTargetAddress(RegisterValue rv, byte[] value) {
|
||||
Address traceAddress =
|
||||
trace.getBaseLanguage().getDefaultSpace().getAddress(rv.getUnsignedValue().longValue());
|
||||
return objectManager.getMemoryMapper().traceToTarget(traceAddress);
|
||||
}
|
||||
|
||||
protected CompletableFuture<?> readAlignedConditionally(String name, Address targetAddress) {
|
||||
if (targetAddress == null) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
Address traceAddress = objectManager.getMemoryMapper().targetToTrace(targetAddress);
|
||||
if (traceAddress == null) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
if (!checkReadCondition(traceAddress)) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
AddressRange targetRange = threadMemory.alignAndLimitToFloor(targetAddress, 1);
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
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(recorder.getSnap(), traceAddress);
|
||||
if (region == null) {
|
||||
return false;
|
||||
}
|
||||
if (region.isWrite()) {
|
||||
return true;
|
||||
}
|
||||
Entry<TraceAddressSnapRange, TraceMemoryState> ent =
|
||||
memoryManager.getMostRecentStateEntry(recorder.getSnap(), traceAddress);
|
||||
if (ent == null) {
|
||||
return true;
|
||||
}
|
||||
if (ent.getValue() == TraceMemoryState.KNOWN) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetThread getTargetThread() {
|
||||
return targetThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceThread getTraceThread() {
|
||||
return traceThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return recorder.getSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return recorder.getTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerMemoryMapper getMemoryMapper() {
|
||||
return recorder.objectManager.getMemoryMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedStackRecorder getStackRecorder() {
|
||||
return stackRecorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedBreakpointRecorder getBreakpointRecorder() {
|
||||
return breakpointRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the recorder the given object is no longer valid
|
||||
*
|
||||
* @param invalid the invalidated object
|
||||
* @return true if this recorder should be invalidated, too
|
||||
*/
|
||||
// UNUSED?
|
||||
@Override
|
||||
public synchronized boolean objectRemoved(TargetObject invalid) {
|
||||
if (checkThreadRemoved(invalid)) {
|
||||
return true;
|
||||
}
|
||||
if (stackRecorder.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 (getTargetThread() == invalid) {
|
||||
threadDestroyed();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public DebuggerRegisterMapper getRegisterMapper() {
|
||||
return regMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
public CompletableFuture<Void> updateRegsMem(TargetMemoryRegion limit) {
|
||||
TargetRegisterBank bank;
|
||||
TargetRegister pc;
|
||||
TargetRegister sp;
|
||||
Set<TargetRegister> toRead = new LinkedHashSet<>();
|
||||
synchronized (recorder) {
|
||||
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;
|
||||
}
|
||||
System.err.println("URM:" + getTargetThread());
|
||||
TimedMsg.info(this, "Reading " + toRead + " of " + getTargetThread());
|
||||
return bank.readRegisters(toRead).thenCompose(vals -> {
|
||||
synchronized (recorder) {
|
||||
if (memoryManager == null) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
}
|
||||
if (threadMemory == null) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
AsyncFence fence = new AsyncFence();
|
||||
|
||||
Address pcTargetAddr = stackRecorder.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;
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
|
||||
public class DefaultTimeRecorder {
|
||||
|
||||
private DefaultTraceRecorder recorder;
|
||||
private Trace trace;
|
||||
private TraceSnapshot snapshot = null;
|
||||
|
||||
public DefaultTimeRecorder(DefaultTraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
this.trace = recorder.getTrace();
|
||||
}
|
||||
|
||||
public TraceSnapshot getSnapshot() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public long getSnap() {
|
||||
return snapshot.getKey();
|
||||
}
|
||||
|
||||
protected synchronized void doAdvanceSnap(String description, TraceThread eventThread) {
|
||||
snapshot = trace.getTimeManager().createSnapshot(description);
|
||||
snapshot.setEventThread(eventThread);
|
||||
}
|
||||
|
||||
public TraceSnapshot forceSnapshot() {
|
||||
createSnapshot("User-forced snapshot", null, null);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public void createSnapshot(String description, TraceThread eventThread,
|
||||
RecorderPermanentTransaction tid) {
|
||||
if (tid != null) {
|
||||
doAdvanceSnap(description, eventThread);
|
||||
recorder.getListeners().fire.snapAdvanced(recorder, getSnap());
|
||||
return;
|
||||
}
|
||||
// NB. The also serves as the snap counter, so it must be on the service thread
|
||||
try (RecorderPermanentTransaction tid2 =
|
||||
RecorderPermanentTransaction.start(trace, description)) {
|
||||
doAdvanceSnap(description, eventThread);
|
||||
}
|
||||
recorder.getListeners().fire.snapAdvanced(recorder, getSnap());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,51 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.model;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
|
||||
import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer;
|
||||
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer;
|
||||
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx;
|
||||
import ghidra.framework.model.UndoableDomainObject;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class PermanentTransactionExecutor {
|
||||
|
||||
private final TransactionCoalescer txc;
|
||||
private final Executor executor;
|
||||
|
||||
public PermanentTransactionExecutor(UndoableDomainObject obj, String name,
|
||||
Function<ThreadFactory, Executor> executorFactory, int delayMs) {
|
||||
txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs);
|
||||
this.executor = executorFactory.apply(
|
||||
new BasicThreadFactory.Builder().namingPattern(name + "-thread-%d").build());
|
||||
}
|
||||
|
||||
public void execute(String description, Runnable runnable) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try (CoalescedTx tx = txc.start(description)) {
|
||||
runnable.run();
|
||||
}
|
||||
}, executor).exceptionally(e -> {
|
||||
Msg.error(this, "Trouble recording " + description, e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class RecorderBreakpointLocationResolver {
|
||||
// TODO: I'm not sure this class really offers anything anymore
|
||||
|
||||
private DefaultTraceRecorder recorder;
|
||||
private final TargetBreakpointLocation bpt;
|
||||
private final TargetBreakpointSpec spec;
|
||||
private boolean affectsProcess = false;
|
||||
private final Set<TraceThread> threadsAffected = new LinkedHashSet<>();
|
||||
|
||||
public RecorderBreakpointLocationResolver(DefaultTraceRecorder recorder,
|
||||
TargetBreakpointLocation bpt) {
|
||||
this.recorder = recorder;
|
||||
this.bpt = bpt;
|
||||
this.spec = bpt.getSpecification();
|
||||
}
|
||||
|
||||
// TODO: This is a stopgap, since Location.getAffects is removed
|
||||
// Do we really need to worry about per-thread breakpoints?
|
||||
static Collection<TargetObject> getAffects(TargetBreakpointLocation bpt) {
|
||||
TargetObject findProc = bpt;
|
||||
while (!(findProc instanceof TargetProcess)) {
|
||||
findProc = findProc.getParent();
|
||||
}
|
||||
return List.of(findProc);
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> resolve(TargetObject obj) {
|
||||
AsyncFence fence = new AsyncFence();
|
||||
if (obj.equals(recorder.getTarget())) {
|
||||
affectsProcess = true;
|
||||
}
|
||||
else {
|
||||
fence.include(resolveThread(obj));
|
||||
}
|
||||
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(recorder.getTarget().getPath(), thread.getPath())) {
|
||||
/**
|
||||
* Perfectly normal if the breakpoint container is outside the process container.
|
||||
* Don't record such in this trace, though.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorder(thread); //listenerForRecord.getOrCreateThreadRecorder(thread);
|
||||
synchronized (threadsAffected) {
|
||||
threadsAffected.add(rec.getTraceThread());
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Error resolving thread from breakpoint-affected object: " + ref);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public void updateBreakpoint(TargetObject containerParent, TargetBreakpointLocation loc) {
|
||||
resolve(containerParent).thenAccept(__ -> {
|
||||
if (affectsProcess || !threadsAffected.isEmpty()) {
|
||||
recorder.breakpointRecorder.recordBreakpoint(loc, threadsAffected);
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could record target breakpoint: " + loc, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
|
||||
import ghidra.async.AsyncLazyMap;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.TriConsumer;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public class RecorderComposedMemory implements AbstractRecorderMemory {
|
||||
|
||||
private static final int BLOCK_SIZE = 4096;
|
||||
private static final long BLOCK_MASK = -1L << 12;
|
||||
|
||||
protected final RecorderComposedMemory chain;
|
||||
|
||||
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
|
||||
|
||||
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(getMemAccListeners().fire);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
};
|
||||
|
||||
protected CompletableFuture<AllRequiredAccess> fetchMemAccessibility(TargetMemory mem) {
|
||||
return DebugModelConventions.trackAccessibility(mem).thenApply(acc -> {
|
||||
acc.addChangeListener(getMemAccListeners().fire);
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accessible memory, as viewed in the trace
|
||||
*
|
||||
* @param pred an additional predicate applied via "AND" with accessibility
|
||||
* @param memMapper target-to-trace mapping utility
|
||||
* @return the computed set
|
||||
*/
|
||||
@Override
|
||||
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
|
||||
DebuggerMemoryMapper memMapper) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private final ListenerSet<TriConsumer<Boolean, Boolean, Void>> memAccListeners =
|
||||
new ListenerSet(TriConsumer.class);
|
||||
|
||||
public RecorderComposedMemory(AbstractRecorderMemory memory) {
|
||||
this.chain = (RecorderComposedMemory) memory;
|
||||
}
|
||||
|
||||
protected TargetMemory getMemory(Address address, int length) {
|
||||
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");
|
||||
}
|
||||
return byRegion.get(floor.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public 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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange alignAndLimitToFloor(Address address, int length) {
|
||||
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
|
||||
if (floor == null) {
|
||||
return null;
|
||||
}
|
||||
return alignWithLimit(address, length, floor.getValue());
|
||||
}
|
||||
|
||||
public AddressRange alignWithOptionalLimit(Address address, int length,
|
||||
TargetMemoryRegion limit) {
|
||||
if (limit == null) {
|
||||
return alignAndLimitToFloor(address, length);
|
||||
}
|
||||
return alignWithLimit(address, length, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<byte[]> readMemory(Address address, int length) {
|
||||
synchronized (accessibilityByMemory) {
|
||||
TargetMemory mem = getMemory(address, length);
|
||||
if (mem != null) {
|
||||
return mem.readMemory(address, length);
|
||||
}
|
||||
return CompletableFuture.completedFuture(new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
|
||||
synchronized (accessibilityByMemory) {
|
||||
TargetMemory mem = getMemory(address, data.length);
|
||||
if (mem != null) {
|
||||
return mem.writeMemory(address, data);
|
||||
}
|
||||
throw new IllegalArgumentException("read starts outside any address space");
|
||||
}
|
||||
}
|
||||
|
||||
public ListenerSet<TriConsumer<Boolean, Boolean, Void>> getMemAccListeners() {
|
||||
return memAccListeners;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/* ###
|
||||
* 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.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.async.AsyncLazyMap;
|
||||
import ghidra.async.AsyncLazyMap.KeyedFuture;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.TriConsumer;
|
||||
|
||||
public class RecorderComposedRegisterSet {
|
||||
|
||||
private TraceRecorder recorder;
|
||||
|
||||
protected final TriConsumer<Boolean, Boolean, Void> listenerRegAccChanged =
|
||||
this::registerAccessibilityChanged;
|
||||
|
||||
protected void registerAccessibilityChanged(boolean old, boolean acc,
|
||||
Void __) {
|
||||
recorder.getListeners().fire.registerAccessibilityChanged(recorder);
|
||||
}
|
||||
|
||||
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 CompletableFuture<AllRequiredAccess> fetchRegAccessibility(
|
||||
TargetRegisterBank bank) {
|
||||
return DebugModelConventions.trackAccessibility(bank).thenApply(acc -> {
|
||||
acc.addChangeListener(listenerRegAccChanged);
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
public RecorderComposedRegisterSet(TraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
public void updateRegisters(TargetRegisterBank newRegs, TargetRegisterBank oldRegs) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkRegistersRemoved(Map<Integer, TargetRegisterBank> regs,
|
||||
TargetObject invalid) {
|
||||
synchronized (accessibilityByRegBank) {
|
||||
if (regs.values().remove(invalid)) {
|
||||
accessibilityByRegBank.remove((TargetRegisterBank) invalid);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* ###
|
||||
* 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 ghidra.framework.model.UndoableDomainObject;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class RecorderPermanentTransaction implements AutoCloseable {
|
||||
|
||||
static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) {
|
||||
UndoableTransaction tid = UndoableTransaction.start(obj, description, true);
|
||||
return new RecorderPermanentTransaction(obj, tid);
|
||||
}
|
||||
|
||||
private final UndoableDomainObject obj;
|
||||
private final UndoableTransaction tid;
|
||||
|
||||
public RecorderPermanentTransaction(UndoableDomainObject obj, UndoableTransaction tid) {
|
||||
this.obj = obj;
|
||||
this.tid = tid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tid.close();
|
||||
obj.clearUndo();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/* ###
|
||||
* 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.util.Map.Entry;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public class RecorderSimpleMemory implements AbstractRecorderMemory {
|
||||
|
||||
private static final int BLOCK_SIZE = 4096;
|
||||
private static final long BLOCK_MASK = -1L << 12;
|
||||
|
||||
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
|
||||
protected TargetMemory memory;
|
||||
|
||||
public RecorderSimpleMemory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRegion(TargetMemoryRegion region, TargetMemory memory) {
|
||||
synchronized (this) {
|
||||
if (this.memory == null) {
|
||||
this.memory = memory;
|
||||
}
|
||||
byMin.put(region.getRange().getMinAddress(), region);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRegion(TargetObject invalid) {
|
||||
if (!(invalid instanceof TargetMemoryRegion)) {
|
||||
return false;
|
||||
}
|
||||
synchronized (this) {
|
||||
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
|
||||
byMin.remove(invRegion.getRange().getMinAddress());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<byte[]> readMemory(Address address, int length) {
|
||||
synchronized (this) {
|
||||
if (memory != null) {
|
||||
return memory.readMemory(address, length);
|
||||
}
|
||||
return CompletableFuture.completedFuture(new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
|
||||
synchronized (this) {
|
||||
if (memory != null) {
|
||||
return memory.writeMemory(address, data);
|
||||
}
|
||||
throw new IllegalArgumentException("read starts outside any address space");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accessible memory, as viewed in the trace
|
||||
*
|
||||
* @param pred an additional predicate applied via "AND" with accessibility
|
||||
* @param memMapper target-to-trace mapping utility
|
||||
* @return the computed set
|
||||
*/
|
||||
@Override
|
||||
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
|
||||
DebuggerMemoryMapper memMapper) {
|
||||
synchronized (this) {
|
||||
// TODO: Might accomplish by using listeners and tracking the accessible set
|
||||
AddressSet accessible = new AddressSet();
|
||||
if (memMapper != null) {
|
||||
for (Entry<Address, TargetMemoryRegion> ent : byMin.entrySet()) {
|
||||
accessible.add(memMapper.targetToTrace(ent.getValue().getRange()));
|
||||
}
|
||||
}
|
||||
return accessible;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange alignAndLimitToFloor(Address address, int length) {
|
||||
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
|
||||
if (floor == null) {
|
||||
return null;
|
||||
}
|
||||
return align(address, length).intersect(floor.getValue().getRange());
|
||||
}
|
||||
|
||||
protected Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
|
||||
synchronized (this) {
|
||||
return byMin.floorEntry(address);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* ###
|
||||
* 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 ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
|
||||
public class RecorderSimpleRegisterSet {
|
||||
|
||||
private TraceRecorder recorder;
|
||||
private TargetRegisterBank bank;
|
||||
|
||||
public RecorderSimpleRegisterSet(TraceRecorder recorder) {
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
public void updateRegisters(TargetRegisterBank newRegs, TargetRegisterBank oldRegs) {
|
||||
this.bank = newRegs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetThread;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public class RecorderThreadMap {
|
||||
|
||||
protected final NavigableSet<Integer> observedThreadPathLengths = new TreeSet<>();
|
||||
protected final Map<TargetThread, ManagedThreadRecorder> byTargetThread = new HashMap<>();
|
||||
protected final Map<TraceThread, ManagedThreadRecorder> byTraceThread = new HashMap<>();
|
||||
|
||||
public void put(ManagedThreadRecorder rec) {
|
||||
observedThreadPathLengths.add(rec.getTargetThread().getPath().size());
|
||||
byTargetThread.put(rec.getTargetThread(), rec);
|
||||
byTraceThread.put(rec.getTraceThread(), rec);
|
||||
}
|
||||
|
||||
/*
|
||||
public ManagedThreadRecorder getForSuccessor(TargetObject successor) {
|
||||
while (successor != null) {
|
||||
ManagedThreadRecorder rec = byTargetThread.get(successor);
|
||||
if (rec != null) {
|
||||
return rec;
|
||||
}
|
||||
successor = successor.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
|
||||
public ManagedThreadRecorder get(TargetThread thread) {
|
||||
return byTargetThread.get(thread);
|
||||
}
|
||||
|
||||
public ManagedThreadRecorder get(TargetObject maybeThread) {
|
||||
return byTargetThread.get(maybeThread);
|
||||
}
|
||||
|
||||
public ManagedThreadRecorder get(TraceThread thread) {
|
||||
return byTraceThread.get(thread);
|
||||
}
|
||||
|
||||
public void remove(ManagedThreadRecorder rec) {
|
||||
ManagedThreadRecorder rByTarget = byTargetThread.remove(rec.getTargetThread());
|
||||
ManagedThreadRecorder rByTrace = byTraceThread.remove(rec.getTraceThread());
|
||||
assert rec == rByTarget;
|
||||
assert rec == rByTrace;
|
||||
}
|
||||
|
||||
public Collection<ManagedThreadRecorder> recorders() {
|
||||
return byTargetThread.values();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
/* ###
|
||||
* 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.lang.invoke.MethodHandles;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
|
||||
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetEventScope.TargetEventType;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.util.DebuggerCallbackReorderer;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.TimedMsg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
|
||||
|
||||
private final DefaultTraceRecorder recorder;
|
||||
private final TargetObject target;
|
||||
private final Trace trace;
|
||||
private final TraceMemoryManager memoryManager;
|
||||
|
||||
private boolean valid = true;
|
||||
protected TargetObject curFocus;
|
||||
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
|
||||
|
||||
public TraceEventListener(TraceObjectManager collection) {
|
||||
super(MethodHandles.lookup());
|
||||
this.recorder = collection.getRecorder();
|
||||
this.target = recorder.getTarget();
|
||||
this.trace = recorder.getTrace();
|
||||
this.memoryManager = trace.getMemoryManager();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
DebuggerObjectModel model = target.getModel();
|
||||
model.addModelListener(reorderer, true);
|
||||
}
|
||||
|
||||
private boolean successor(TargetObject ref) {
|
||||
return PathUtils.isAncestor(target.getPath(), ref.getPath());
|
||||
}
|
||||
|
||||
private boolean anyRef(Collection<Object> parameters) {
|
||||
for (Object p : parameters) {
|
||||
if (!(p instanceof TargetObject)) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private 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 (eventThread == null) {
|
||||
if (!type.equals(TargetEventType.PROCESS_CREATED)) {
|
||||
Msg.error(this, "Null eventThread for " + type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!eventApplies(eventThread, type, parameters)) {
|
||||
return;
|
||||
}
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorder(eventThread);
|
||||
recorder.createSnapshot(description, rec == null ? null : rec.getTraceThread(), null);
|
||||
|
||||
if (type == TargetEventType.MODULE_LOADED) {
|
||||
long snap = recorder.getSnap();
|
||||
Object p0 = parameters.get(0);
|
||||
if (!(p0 instanceof TargetModule)) {
|
||||
return;
|
||||
}
|
||||
TargetModule mod = (TargetModule) p0;
|
||||
recorder.moduleRecorder.tx.execute("Adjust module load", () -> {
|
||||
TraceModule traceModule = recorder.getTraceModule(mod);
|
||||
if (traceModule == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
traceModule.setLoadedSnap(snap);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
Msg.error(this, "Could not set module loaded snap", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
||||
public void executionStateChanged(TargetObject stateful, TargetExecutionState state) {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
TimedMsg.info(this, "State " + state + " for " + stateful);
|
||||
TargetObject x = recorder.objectManager.findThreadOrProcess(stateful);
|
||||
if (x != null) {
|
||||
if (x == target && state == TargetExecutionState.TERMINATED) {
|
||||
recorder.stopRecording();
|
||||
return;
|
||||
}
|
||||
ManagedThreadRecorder rec = null;
|
||||
if (x instanceof TargetThread) {
|
||||
rec = recorder.getThreadRecorder((TargetThread) x);
|
||||
}
|
||||
if (rec != null) {
|
||||
rec.stateChanged(state);
|
||||
}
|
||||
// Else we'll discover it and sync state later
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(bank);
|
||||
if (rec != null) {
|
||||
rec.recordRegisterValues((TargetRegisterBank) bank, updates);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
synchronized (recorder) {
|
||||
if (recorder.getMemoryMapper() == null) {
|
||||
Msg.warn(this, "Received memory write before a region has been added");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Address traceAddr = recorder.getMemoryMapper().targetToTrace(address);
|
||||
long snap = recorder.getSnap();
|
||||
TimedMsg.info(this, "Memory updated: " + address + " (" + data.length + ")");
|
||||
recorder.memoryRecorder.tx.execute("Memory observed", () -> {
|
||||
memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memoryReadError(TargetObject memory, AddressRange range,
|
||||
DebuggerMemoryAccessException e) {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
Msg.error(this, "Error reading range " + range, e);
|
||||
Address traceMin = recorder.getMemoryMapper().targetToTrace(range.getMinAddress());
|
||||
long snap = recorder.getSnap();
|
||||
recorder.memoryRecorder.tx.execute("Memory read error", () -> {
|
||||
memoryManager.setState(snap, 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;
|
||||
long snap = recorder.getSnap();
|
||||
spec.getLocations().thenAccept(bpts -> {
|
||||
recorder.breakpointRecorder.tx.execute("Breakpoint toggled", () -> {
|
||||
for (TargetBreakpointLocation eb : bpts) {
|
||||
TraceBreakpoint traceBpt = recorder.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(snap, enabled);
|
||||
}
|
||||
});
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Error recording toggled breakpoint spec: " + spec, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected void stackUpdated(TargetStack stack) {
|
||||
ManagedStackRecorder rec = recorder.getThreadRecorderForSuccessor(stack).getStackRecorder();
|
||||
rec.recordStack();
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||
public void focusChanged(TargetObject scope, TargetObject focused) {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (PathUtils.isAncestor(target.getPath(), focused.getPath())) {
|
||||
curFocus = focused;
|
||||
}
|
||||
}
|
||||
|
||||
public RecorderThreadMap getThreadMap() {
|
||||
return recorder.getThreadMap();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.dbg.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.DebuggerCallbackReorderer;
|
||||
import ghidra.dbg.util.PathUtils.PathComparator;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class TraceObjectListener implements DebuggerModelListener {
|
||||
|
||||
private TraceObjectManager objectManager;
|
||||
private TargetObject target;
|
||||
|
||||
protected boolean disposed = false;
|
||||
protected final NavigableMap<List<String>, TargetObject> initialized =
|
||||
new TreeMap<>(PathComparator.KEYED);
|
||||
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
|
||||
|
||||
public TraceObjectListener(TraceObjectManager manager) {
|
||||
this.objectManager = manager;
|
||||
this.target = objectManager.getTarget();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
findInitialObjects(target).thenAccept(adds -> {
|
||||
for (TargetObject added : adds) {
|
||||
processInit(added);
|
||||
}
|
||||
DebuggerObjectModel model = target.getModel();
|
||||
model.addModelListener(reorderer, true);
|
||||
});
|
||||
}
|
||||
|
||||
boolean matchesTarget(TargetObject object) {
|
||||
TargetObject proc = object;
|
||||
while (proc != null) {
|
||||
if (proc == target)
|
||||
return true;
|
||||
if (proc.getClass().equals(target.getClass()))
|
||||
return false;
|
||||
proc = proc.getParent();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void processCreate(TargetObject added) {
|
||||
if (!objectManager.hasObject(added) && matchesTarget(added)) {
|
||||
objectManager.addObject(added);
|
||||
objectManager.createObject(added);
|
||||
}
|
||||
/*
|
||||
else {
|
||||
Msg.info(this, "processCreate dropped " + added);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
protected void processInit(TargetObject added) {
|
||||
if (objectManager.hasObject(added)) {
|
||||
if (!initialized.containsKey(added.getPath())) {
|
||||
initialized.put(added.getPath(), added);
|
||||
objectManager.initObject(added);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processRemove(TargetObject removed) {
|
||||
if (objectManager.hasObject(removed)) {
|
||||
objectManager.removeObject(removed);
|
||||
objectManager.removeObject(removed.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
protected void processAttributesChanged(TargetObject changed, Map<String, ?> added) {
|
||||
if (objectManager.hasObject(changed)) {
|
||||
objectManager.attributesChanged(changed, added);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processElementsChanged(TargetObject changed, Map<String, ?> added) {
|
||||
if (objectManager.hasObject(changed)) {
|
||||
objectManager.elementsChanged(changed, added);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(TargetObject object) {
|
||||
//System.err.println("CR:" + object);
|
||||
processCreate(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
processRemove(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
//System.err.println("AC:" + added + ":" + parent);
|
||||
if (parent.isValid()) {
|
||||
processInit(parent);
|
||||
processAttributesChanged(parent, added);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
//System.err.println("EC:" + added + ":" + parent);
|
||||
if (parent.isValid()) {
|
||||
processElementsChanged(parent, added);
|
||||
}
|
||||
}
|
||||
|
||||
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
|
||||
synchronized (objectManager.objects) {
|
||||
return objectManager.collectBreakpoints(thread);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onProcessBreakpointContainers(
|
||||
Consumer<? super TargetBreakpointSpecContainer> action) {
|
||||
synchronized (objectManager.objects) {
|
||||
objectManager.onProcessBreakpointContainers(action);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onThreadBreakpointContainers(TargetThread thread,
|
||||
Consumer<? super TargetBreakpointSpecContainer> action) {
|
||||
synchronized (objectManager.objects) {
|
||||
objectManager.onThreadBreakpointContainers(thread, action);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public boolean addListener(TargetObject obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
obj.addListener(this);
|
||||
synchronized (objects) {
|
||||
if (objects.put(obj.getPath(), obj) == obj) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
synchronized (objects) {
|
||||
disposed = true;
|
||||
for (Iterator<TargetObject> it = objects.values().iterator(); it.hasNext();) {
|
||||
TargetObject obj = it.next();
|
||||
obj.removeListener(this);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private CompletableFuture<List<TargetObject>> findInitialObjects(TargetObject target) {
|
||||
List<TargetObject> result = new ArrayList<>();
|
||||
result.add(target);
|
||||
AsyncFence fence = new AsyncFence();
|
||||
CompletableFuture<? extends TargetEventScope> futureEvents =
|
||||
DebugModelConventions.findSuitable(TargetEventScope.class, target);
|
||||
fence.include(futureEvents.thenAccept(events -> {
|
||||
if (events != null) {
|
||||
result.add(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) {
|
||||
// Don't descend. Scope may be the entire session.
|
||||
result.add(focus);
|
||||
}
|
||||
}).exceptionally(e -> {
|
||||
Msg.error(this, "Could not search for focus scope", e);
|
||||
return null;
|
||||
}));
|
||||
return fence.ready().thenApply(__ -> {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
private CompletableFuture<List<TargetObject>> findDependenciesTop(TargetObject added) {
|
||||
List<TargetObject> result = new ArrayList<>();
|
||||
result.add(added);
|
||||
return findDependencies(added, result);
|
||||
}
|
||||
|
||||
private CompletableFuture<List<TargetObject>> findDependencies(TargetObject added,
|
||||
List<TargetObject> result) {
|
||||
//System.err.println("findDependencies " + added);
|
||||
AsyncFence fence = new AsyncFence();
|
||||
fence.include(added.fetchAttributes(false).thenCompose(attrs -> {
|
||||
AsyncFence af = new AsyncFence();
|
||||
for (String key : attrs.keySet()) { //requiredObjKeys) {
|
||||
Object object = attrs.get(key);
|
||||
if (!(object instanceof TargetObject)) {
|
||||
continue;
|
||||
}
|
||||
TargetObject ref = (TargetObject) object;
|
||||
if (PathUtils.isLink(added.getPath(), key, ref.getPath())) {
|
||||
continue;
|
||||
}
|
||||
af.include(ref.fetch().thenCompose(obj -> {
|
||||
if (!objectManager.isRequired(obj)) {
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
synchronized (result) {
|
||||
result.add(obj);
|
||||
}
|
||||
return findDependencies(obj, result);
|
||||
}));
|
||||
}
|
||||
return af.ready();
|
||||
}));
|
||||
fence.include(added.fetchElements(false).thenCompose(elems -> {
|
||||
AsyncFence ef = new AsyncFence();
|
||||
for (TargetObject ref : elems.values()) {
|
||||
ef.include(ref.fetch().thenCompose(obj -> {
|
||||
synchronized (result) {
|
||||
result.add(obj);
|
||||
}
|
||||
return findDependencies(obj, result);
|
||||
}));
|
||||
}
|
||||
return ef.ready();
|
||||
}));
|
||||
return fence.ready().thenApply(__ -> {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,655 @@
|
|||
/* ###
|
||||
* 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.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
|
||||
import ghidra.app.services.TraceRecorderListener;
|
||||
import ghidra.async.AsyncLazyMap;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.dbg.util.PathUtils.PathComparator;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class TraceObjectManager {
|
||||
|
||||
private final TargetObject target;
|
||||
private final TraceEventListener eventListener;
|
||||
final TraceObjectListener objectListener;
|
||||
|
||||
protected final NavigableMap<List<String>, TargetObject> objects =
|
||||
new TreeMap<>(PathComparator.KEYED);
|
||||
|
||||
private DefaultTraceRecorder recorder;
|
||||
|
||||
private AbstractDebuggerTargetTraceMapper mapper;
|
||||
protected DebuggerMemoryMapper memMapper;
|
||||
protected AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> regMappers;
|
||||
//private AbstractRecorderRegisterSet threadRegisters;
|
||||
|
||||
private final ListenerSet<TraceRecorderListener> listeners =
|
||||
new ListenerSet<>(TraceRecorderListener.class);
|
||||
|
||||
protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>();
|
||||
|
||||
// NB: We add the objects in top-down order and initialize them bottom-up
|
||||
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapCreate =
|
||||
new LinkedHashMap<>();
|
||||
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapInit =
|
||||
new LinkedHashMap<>();
|
||||
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapRemove =
|
||||
new LinkedHashMap<>();
|
||||
private LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMapElements =
|
||||
new LinkedHashMap<>();
|
||||
private LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMapAttributes =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
public TraceObjectManager(TargetObject target, AbstractDebuggerTargetTraceMapper mapper,
|
||||
DefaultTraceRecorder recorder) {
|
||||
this.target = target;
|
||||
this.mapper = mapper;
|
||||
this.recorder = recorder;
|
||||
this.regMappers = new AsyncLazyMap<>(new HashMap<>(), ref -> mapper.offerRegisters(ref));
|
||||
//this.threadRegisters = new RecorderComposedRegisterSet(recorder);
|
||||
defaultHandlers();
|
||||
this.eventListener = new TraceEventListener(this);
|
||||
this.objectListener = new TraceObjectListener(this);
|
||||
//objectListener.addListenerAndConsiderSuccessors(target);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
objectListener.init();
|
||||
eventListener.init();
|
||||
}
|
||||
|
||||
private void defaultHandlers() {
|
||||
putCreateHandler(TargetThread.class, this::createThread);
|
||||
putCreateHandler(TargetMemory.class, this::createMemory);
|
||||
putCreateHandler(TargetRegister.class, this::createRegister);
|
||||
|
||||
putInitHandler(TargetStack.class, this::addStack);
|
||||
putInitHandler(TargetStackFrame.class, this::addStackFrame);
|
||||
putInitHandler(TargetRegisterBank.class, this::addRegisterBank);
|
||||
putInitHandler(TargetRegisterContainer.class, this::addRegisterContainer);
|
||||
//putInitHandler(TargetMemoryRegion.class, this::addMemoryRegion);
|
||||
putInitHandler(TargetModule.class, this::addModule);
|
||||
//putInitHandler(TargetSection.class, this::addSection); // This is brutally expensive
|
||||
putInitHandler(TargetBreakpointSpecContainer.class, this::addBreakpointContainer);
|
||||
putInitHandler(TargetBreakpointSpec.class, this::addBreakpointSpec);
|
||||
putInitHandler(TargetBreakpointLocation.class, this::addBreakpointLocation);
|
||||
|
||||
putElementsHandler(TargetBreakpointLocationContainer.class,
|
||||
this::elementsChangedBreakpointLocationContainer);
|
||||
putElementsHandler(TargetMemory.class, this::elementsChangedMemory);
|
||||
putElementsHandler(TargetSectionContainer.class, this::elementsChangedSectionContainer);
|
||||
putElementsHandler(TargetStack.class, this::elementsChangedStack);
|
||||
|
||||
putAttributesHandler(TargetBreakpointLocation.class,
|
||||
this::attributesChangedBreakpointLocation);
|
||||
putAttributesHandler(TargetRegister.class, this::attributesChangedRegister);
|
||||
putAttributesHandler(TargetStackFrame.class, this::attributesChangedStackFrame);
|
||||
|
||||
putRemHandler(TargetProcess.class, this::removeProcess);
|
||||
putRemHandler(TargetThread.class, this::removeThread);
|
||||
putRemHandler(TargetStack.class, this::removeStack);
|
||||
putRemHandler(TargetStackFrame.class, this::removeStackFrame);
|
||||
putRemHandler(TargetStack.class, this::removeRegisterBank);
|
||||
putRemHandler(TargetRegisterContainer.class, this::removeRegisterContainer);
|
||||
putRemHandler(TargetRegister.class, this::removeRegister);
|
||||
putRemHandler(TargetMemory.class, this::removeMemory);
|
||||
putRemHandler(TargetMemoryRegion.class, this::removeMemoryRegion);
|
||||
putRemHandler(TargetModule.class, this::removeModule);
|
||||
putRemHandler(TargetSection.class, this::removeSection);
|
||||
putRemHandler(TargetBreakpointSpecContainer.class, this::removeBreakpointContainer);
|
||||
putRemHandler(TargetBreakpointSpec.class, this::removeBreakpointSpec);
|
||||
putRemHandler(TargetBreakpointLocation.class, this::removeBreakpointLocation);
|
||||
}
|
||||
|
||||
private <U extends TargetObject> Function<TargetObject, Void> putHandler(Class<?> key,
|
||||
Consumer<TargetObject> handler,
|
||||
LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMap) {
|
||||
return handlerMap.put(key, (u) -> {
|
||||
handler.accept(u);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putHandler(
|
||||
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler,
|
||||
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
|
||||
return handlerMap.put(key, (u, v) -> {
|
||||
handler.accept(u, v);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public <U extends TargetObject> Function<TargetObject, Void> putCreateHandler(Class<?> key,
|
||||
Consumer<TargetObject> handler) {
|
||||
return putHandler(key, handler, handlerMapCreate);
|
||||
}
|
||||
|
||||
public <U extends TargetObject> Function<TargetObject, Void> putInitHandler(Class<?> key,
|
||||
Consumer<TargetObject> handler) {
|
||||
return putHandler(key, handler, handlerMapInit);
|
||||
}
|
||||
|
||||
public <U extends TargetObject> Function<TargetObject, Void> putRemHandler(Class<?> key,
|
||||
Consumer<TargetObject> handler) {
|
||||
return putHandler(key, handler, handlerMapRemove);
|
||||
}
|
||||
|
||||
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putAttributesHandler(
|
||||
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler) {
|
||||
return putHandler(key, handler, handlerMapAttributes);
|
||||
}
|
||||
|
||||
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putElementsHandler(
|
||||
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler) {
|
||||
return putHandler(key, handler, handlerMapElements);
|
||||
}
|
||||
|
||||
private void processObject(TargetObject targetObject,
|
||||
LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMap) {
|
||||
Set<Class<? extends TargetObject>> interfaces = targetObject.getSchema().getInterfaces();
|
||||
for (Class<? extends TargetObject> ifc : interfaces) {
|
||||
Function<TargetObject, ? extends Void> function = handlerMap.get(ifc);
|
||||
if (function != null) {
|
||||
function.apply(targetObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processObject(TargetObject targetObject, Map<String, ?> map,
|
||||
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
|
||||
Set<Class<? extends TargetObject>> interfaces = targetObject.getSchema().getInterfaces();
|
||||
for (Class<? extends TargetObject> ifc : interfaces) {
|
||||
BiFunction<TargetObject, Map<String, ?>, ? extends Void> function = handlerMap.get(ifc);
|
||||
if (function != null) {
|
||||
function.apply(targetObject, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createObject(TargetObject toInit) {
|
||||
processObject(toInit, handlerMapCreate);
|
||||
}
|
||||
|
||||
public void initObject(TargetObject added) {
|
||||
//System.err.println("initObject " + added);
|
||||
processObject(added, handlerMapInit);
|
||||
}
|
||||
|
||||
public void removeObject(TargetObject removed) {
|
||||
processObject(removed, handlerMapRemove);
|
||||
}
|
||||
|
||||
public void attributesChanged(TargetObject changed, Map<String, ?> added) {
|
||||
processObject(changed, added, handlerMapAttributes);
|
||||
}
|
||||
|
||||
public void elementsChanged(TargetObject changed, Map<String, ?> added) {
|
||||
processObject(changed, added, handlerMapElements);
|
||||
}
|
||||
|
||||
public boolean isRequired(TargetObject obj) {
|
||||
if (obj.getName().equals("Debug"))
|
||||
return true;
|
||||
if (obj.getName().equals("Stack"))
|
||||
return true;
|
||||
|
||||
Set<Class<? extends TargetObject>> interfaces = obj.getSchema().getInterfaces();
|
||||
for (Class<? extends TargetObject> ifc : interfaces) {
|
||||
if (handlerMapInit.keySet().contains(ifc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addProcess(TargetObject added) {
|
||||
// Create a new processRecorder
|
||||
recorder.init();
|
||||
}
|
||||
|
||||
public void removeProcess(TargetObject removed) {
|
||||
recorder.stopRecording();
|
||||
}
|
||||
|
||||
public void createThread(TargetObject added) {
|
||||
//System.err.println("createThread " + added + ":" + this);
|
||||
synchronized (recorder.threadMap) {
|
||||
ManagedThreadRecorder threadRecorder = recorder.getThreadRecorder((TargetThread) added);
|
||||
TraceThread traceThread = threadRecorder.getTraceThread();
|
||||
recorder.createSnapshot(traceThread + " started", traceThread, null);
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(recorder.getTrace(), "Adjust thread creation", true)) {
|
||||
traceThread.setCreationSnap(recorder.getSnap());
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
throw new AssertionError(e); // Should be shrinking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeThread(TargetObject removed) {
|
||||
synchronized (recorder.threadMap) {
|
||||
ManagedThreadRecorder threadRecorder =
|
||||
recorder.getThreadRecorder((TargetThread) removed);
|
||||
threadRecorder.objectRemoved(removed);
|
||||
}
|
||||
}
|
||||
|
||||
public void addStack(TargetObject added) {
|
||||
//addEventListener(added);
|
||||
}
|
||||
|
||||
public void removeStack(TargetObject removed) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
public void addStackFrame(TargetObject added) {
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
|
||||
if (rec == null) {
|
||||
Msg.error(this, "Frame without thread?: " + added);
|
||||
}
|
||||
else {
|
||||
rec.getStackRecorder().offerStackFrame((TargetStackFrame) added);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeStackFrame(TargetObject removed) {
|
||||
synchronized (recorder.threadMap) {
|
||||
ManagedThreadRecorder threadRecorder = recorder.getThreadRecorderForSuccessor(removed);
|
||||
threadRecorder.objectRemoved(removed);
|
||||
}
|
||||
}
|
||||
|
||||
public void addRegisterBank(TargetObject added) {
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
|
||||
rec.offerRegisters((TargetRegisterBank) added);
|
||||
}
|
||||
|
||||
public void removeRegisterBank(TargetObject removed) {
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(removed);
|
||||
rec.removeRegisters((TargetRegisterBank) removed);
|
||||
}
|
||||
|
||||
public void addRegisterContainer(TargetObject added) {
|
||||
// These are picked up when a bank is added with these descriptions
|
||||
}
|
||||
|
||||
public void removeRegisterContainer(TargetObject removed) {
|
||||
regMappers.remove((TargetRegisterContainer) removed);
|
||||
}
|
||||
|
||||
public void createRegister(TargetObject added) {
|
||||
if (added.getCachedAttribute(TargetRegister.CONTAINER_ATTRIBUTE_NAME) != null) {
|
||||
TargetRegister register = (TargetRegister) added;
|
||||
regMappers.get(register.getContainer()).thenAccept(rm -> {
|
||||
if (rm != null) {
|
||||
rm.targetRegisterAdded(register);
|
||||
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
|
||||
rec.regMapperAmended(rm, register, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void removeRegister(TargetObject removed) {
|
||||
TargetRegister register = (TargetRegister) removed;
|
||||
TargetRegisterContainer cont = register.getContainer();
|
||||
DebuggerRegisterMapper rm = regMappers.getCompletedMap().get(cont);
|
||||
if (rm == null) {
|
||||
return;
|
||||
}
|
||||
rm.targetRegisterRemoved(register);
|
||||
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
|
||||
rec.regMapperAmended(rm, register, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void createMemory(TargetObject added) {
|
||||
if (memMapper != null) {
|
||||
return;
|
||||
}
|
||||
mapper.offerMemory((TargetMemory) added).thenAccept(mm -> {
|
||||
synchronized (this) {
|
||||
memMapper = mm;
|
||||
//addEventListener(added);
|
||||
}
|
||||
//listenerForRecord.retroOfferMemMapperDependents();
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not intialize memory mapper", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public void removeMemory(TargetObject removed) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
public void addMemoryRegion(TargetObject added) {
|
||||
/*
|
||||
TargetMemoryRegion region = (TargetMemoryRegion) added;
|
||||
findThreadOrProcess(added).thenAccept(obj -> {
|
||||
if (obj == target) {
|
||||
recorder.memoryRecorder.offerProcessRegion(region);
|
||||
return;
|
||||
}
|
||||
if (obj instanceof TargetThread) {
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
|
||||
rec.offerThreadRegion(region);
|
||||
}
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Error recording memory region", ex);
|
||||
return null;
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
public void removeMemoryRegion(TargetObject removed) {
|
||||
recorder.memoryRecorder.removeProcessRegion((TargetMemoryRegion) removed);
|
||||
}
|
||||
|
||||
public void addModule(TargetObject added) {
|
||||
recorder.moduleRecorder.offerProcessModule((TargetModule) added);
|
||||
}
|
||||
|
||||
public void removeModule(TargetObject removed) {
|
||||
recorder.moduleRecorder.removeProcessModule((TargetModule) removed);
|
||||
}
|
||||
|
||||
public void addSection(TargetObject added) {
|
||||
/*
|
||||
TargetSection section = (TargetSection) added;
|
||||
TargetModule module = section.getModule();
|
||||
recorder.moduleRecorder.offerProcessModuleSection(module, section);
|
||||
// I hope this should never be a per-thread thing
|
||||
*/
|
||||
}
|
||||
|
||||
public void removeSection(TargetObject removed) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
public void addBreakpointContainer(TargetObject added) {
|
||||
TargetObject obj = findThreadOrProcess(added);
|
||||
if (obj != null) {
|
||||
ManagedBreakpointRecorder breakpointRecorder = recorder.breakpointRecorder;
|
||||
if (obj instanceof TargetThread) {
|
||||
ManagedBreakpointRecorder rec =
|
||||
recorder.getThreadRecorderForSuccessor(added).getBreakpointRecorder();
|
||||
rec.offerBreakpointContainer((TargetBreakpointSpecContainer) added);
|
||||
return;
|
||||
}
|
||||
breakpointRecorder.offerBreakpointContainer((TargetBreakpointSpecContainer) added);
|
||||
}
|
||||
else {
|
||||
Msg.error(this, "Error recording breakpoint container " + added);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeBreakpointContainer(TargetObject removed) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
public void addBreakpointSpec(TargetObject added) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
public void removeBreakpointSpec(TargetObject removed) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
public void addBreakpointLocation(TargetObject added) {
|
||||
// Nothing for now
|
||||
//breakpoints.add((TargetBreakpointLocation) added);
|
||||
//recorder.breakpointRecorder.offerEffectiveBreakpoint((TargetBreakpointLocation) added);
|
||||
}
|
||||
|
||||
public void removeBreakpointLocation(TargetObject removed) {
|
||||
breakpoints.remove(removed);
|
||||
recorder.breakpointRecorder.removeBreakpointLocation((TargetBreakpointLocation) removed);
|
||||
}
|
||||
|
||||
protected TargetObject findThreadOrProcess(TargetObject successor) {
|
||||
TargetObject object = successor;
|
||||
while (object != null) {
|
||||
if (object instanceof TargetProcess)
|
||||
return object;
|
||||
if (object instanceof TargetThread)
|
||||
return object;
|
||||
object = object.getParent();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public AbstractDebuggerTargetTraceMapper getMapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
public DebuggerMemoryMapper getMemoryMapper() {
|
||||
return memMapper;
|
||||
}
|
||||
|
||||
public AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> getRegMappers() {
|
||||
return regMappers;
|
||||
}
|
||||
|
||||
public Set<TargetBreakpointLocation> getBreakpoints() {
|
||||
return breakpoints;
|
||||
}
|
||||
|
||||
public void attributesChangedBreakpointLocation(TargetObject bpt, Map<String, ?> added) {
|
||||
if (added.containsKey(TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME)) {
|
||||
Address traceAddr = recorder.getMemoryMapper()
|
||||
.targetToTrace(((TargetBreakpointLocation) bpt).getAddress());
|
||||
String path = bpt.getJoinedPath(".");
|
||||
Integer length = (Integer) added.get(TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME);
|
||||
recorder.breakpointRecorder.breakpointLengthChanged(length, traceAddr, path);
|
||||
}
|
||||
}
|
||||
|
||||
public void attributesChangedRegister(TargetObject parent, Map<String, ?> added) {
|
||||
if (added.containsKey(TargetRegister.CONTAINER_ATTRIBUTE_NAME)) {
|
||||
TargetRegister register = (TargetRegister) parent;
|
||||
regMappers.get(register.getContainer()).thenAccept(rm -> {
|
||||
rm.targetRegisterAdded(register);
|
||||
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
|
||||
rec.regMapperAmended(rm, register, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (added.containsKey(TargetObject.VALUE_ATTRIBUTE_NAME)) {
|
||||
TargetRegister register = (TargetRegister) parent;
|
||||
String valstr = (String) added.get(TargetObject.VALUE_ATTRIBUTE_NAME);
|
||||
byte[] value = new BigInteger(valstr, 16).toByteArray();
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(register);
|
||||
rec.recordRegisterValue(register, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void attributesChangedStackFrame(TargetObject frame, Map<String, ?> added) {
|
||||
if (added.containsKey(TargetStackFrame.PC_ATTRIBUTE_NAME)) {
|
||||
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(frame);
|
||||
if (rec != null) {
|
||||
rec.getStackRecorder().offerStackFrame((TargetStackFrame) frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void elementsChangedBreakpointLocationContainer(TargetObject locationContainer,
|
||||
Map<String, ?> added) {
|
||||
TargetObject x = findThreadOrProcess(locationContainer);
|
||||
if (x != null) {
|
||||
for (Entry<String, ?> entry : added.entrySet()) {
|
||||
TargetBreakpointLocation loc = (TargetBreakpointLocation) entry.getValue();
|
||||
if (loc.isValid()) {
|
||||
breakpoints.add(loc);
|
||||
recorder.breakpointRecorder.offerBreakpointLocation(x, loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void elementsChangedMemory(TargetObject memory, Map<String, ?> added) {
|
||||
// TODO: This should probably only ever be a process
|
||||
TargetObject threadOrProcess = findThreadOrProcess(memory);
|
||||
if (threadOrProcess != null) {
|
||||
for (Object object : added.values()) {
|
||||
TargetMemoryRegion region = (TargetMemoryRegion) object;
|
||||
if (!region.isValid()) {
|
||||
continue;
|
||||
}
|
||||
if (threadOrProcess == target) {
|
||||
recorder.memoryRecorder.offerProcessRegion(region);
|
||||
}
|
||||
else if (threadOrProcess instanceof TargetThread) {
|
||||
ManagedThreadRecorder rec =
|
||||
recorder.getThreadRecorderForSuccessor(threadOrProcess);
|
||||
rec.offerThreadRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.error(this, "Could not find process/thread for " + memory);
|
||||
}
|
||||
}
|
||||
|
||||
public void elementsChangedSectionContainer(TargetObject sectionContainer,
|
||||
Map<String, ?> added) {
|
||||
for (Object object : added.values()) {
|
||||
TargetSection section = (TargetSection) object;
|
||||
if (!section.isValid()) {
|
||||
continue;
|
||||
}
|
||||
recorder.moduleRecorder.offerProcessModuleSection(section);
|
||||
}
|
||||
}
|
||||
|
||||
public void elementsChangedStack(TargetObject stack, Map<String, ?> added) {
|
||||
ManagedStackRecorder rec = recorder.getThreadRecorderForSuccessor(stack).getStackRecorder();
|
||||
rec.recordStack();
|
||||
}
|
||||
|
||||
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) {
|
||||
return getBreakpoints().stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void onBreakpointContainers(TargetThread thread,
|
||||
Consumer<? super TargetBreakpointSpecContainer> action) {
|
||||
if (thread == null) {
|
||||
objectListener.onProcessBreakpointContainers(action);
|
||||
}
|
||||
else {
|
||||
objectListener.onThreadBreakpointContainers(thread, action);
|
||||
}
|
||||
}
|
||||
|
||||
public void onProcessBreakpointContainers(
|
||||
Consumer<? super TargetBreakpointSpecContainer> action) {
|
||||
TargetBreakpointSpecContainer breakpointContainer =
|
||||
recorder.breakpointRecorder.getBreakpointContainer();
|
||||
if (breakpointContainer == null) {
|
||||
for (TargetThread thread : recorder.getThreadsView()) {
|
||||
objectListener.onThreadBreakpointContainers(thread, action);
|
||||
}
|
||||
}
|
||||
else {
|
||||
action.accept(breakpointContainer);
|
||||
}
|
||||
}
|
||||
|
||||
public void onThreadBreakpointContainers(TargetThread thread,
|
||||
Consumer<? super TargetBreakpointSpecContainer> action) {
|
||||
TargetBreakpointSpecContainer breakpointContainer =
|
||||
recorder.getThreadRecorder(thread).getBreakpointRecorder().getBreakpointContainer();
|
||||
if (breakpointContainer == null) {
|
||||
return;
|
||||
}
|
||||
action.accept(breakpointContainer);
|
||||
}
|
||||
|
||||
// Needed by TraceRecorder
|
||||
public TraceEventListener getEventListener() {
|
||||
return eventListener;
|
||||
}
|
||||
|
||||
public ListenerSet<TraceRecorderListener> getListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public TargetObject getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public DefaultTraceRecorder getRecorder() {
|
||||
return recorder;
|
||||
}
|
||||
|
||||
public boolean hasObject(TargetObject object) {
|
||||
return objects.containsKey(object.getPath());
|
||||
}
|
||||
|
||||
public void addObject(TargetObject added) {
|
||||
objects.put(added.getPath(), added);
|
||||
}
|
||||
|
||||
public void removeObject(List<String> path) {
|
||||
objects.remove(path);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.service.model.interfaces;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public interface AbstractRecorderMemory {
|
||||
|
||||
public void addRegion(TargetMemoryRegion region, TargetMemory memory);
|
||||
|
||||
public boolean removeRegion(TargetObject invalid);
|
||||
|
||||
public CompletableFuture<byte[]> readMemory(Address address, int length);
|
||||
|
||||
public CompletableFuture<Void> writeMemory(Address address, byte[] data);
|
||||
|
||||
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
|
||||
DebuggerMemoryMapper memMapper);
|
||||
|
||||
public AddressRange alignAndLimitToFloor(Address targetAddress, int length);
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public interface AbstractTraceRecorder {
|
||||
|
||||
public Trace getTrace();
|
||||
|
||||
public long getSnap();
|
||||
|
||||
public DebuggerMemoryMapper getMemoryMapper();
|
||||
|
||||
public ManagedBreakpointRecorder getBreakpointRecorder();
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public interface ManagedBreakpointRecorder {
|
||||
|
||||
void offerBreakpointContainer(TargetBreakpointSpecContainer added);
|
||||
|
||||
void offerBreakpointLocation(TargetObject target, TargetBreakpointLocation added);
|
||||
|
||||
void recordBreakpoint(TargetBreakpointLocation loc, Set<TraceThread> traceThreads);
|
||||
|
||||
void removeBreakpointLocation(TargetBreakpointLocation removed);
|
||||
|
||||
TargetBreakpointSpecContainer getBreakpointContainer();
|
||||
|
||||
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
|
||||
|
||||
void breakpointLengthChanged(int length, Address traceAddr, String path);
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
import ghidra.dbg.target.TargetMemoryRegion;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
|
||||
public interface ManagedMemoryRecorder {
|
||||
|
||||
void offerProcessRegion(TargetMemoryRegion region);
|
||||
|
||||
void removeProcessRegion(TargetMemoryRegion region);
|
||||
|
||||
TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region);
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetSection;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
|
||||
public interface ManagedModuleRecorder {
|
||||
|
||||
void offerProcessModule(TargetModule module);
|
||||
|
||||
void offerProcessModuleSection(TargetSection section);
|
||||
|
||||
void removeProcessModule(TargetModule module);
|
||||
|
||||
TraceModule getTraceModule(TargetModule module);
|
||||
|
||||
TraceSection getTraceSection(TargetSection section);
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
public interface ManagedProcessRecorder extends AbstractTraceRecorder {
|
||||
|
||||
public AbstractRecorderMemory getProcessMemory();
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public interface ManagedStackRecorder {
|
||||
|
||||
void offerStackFrame(TargetStackFrame added);
|
||||
|
||||
void recordStack();
|
||||
|
||||
int getSuccessorFrameLevel(TargetObject successor);
|
||||
|
||||
TraceStackFrame getTraceStackFrame(TraceThread traceThread, int level);
|
||||
|
||||
TargetStackFrame getTargetStackFrame(int frameLevel);
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/* ###
|
||||
* 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.interfaces;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public interface ManagedThreadRecorder extends AbstractTraceRecorder {
|
||||
|
||||
public TargetThread getTargetThread();
|
||||
|
||||
public TraceThread getTraceThread();
|
||||
|
||||
public void offerRegisters(TargetRegisterBank added);
|
||||
|
||||
public void removeRegisters(TargetRegisterBank removed);
|
||||
|
||||
public void offerThreadRegion(TargetMemoryRegion region);
|
||||
|
||||
public void recordRegisterValue(TargetRegister targetRegister, byte[] value);
|
||||
|
||||
public void recordRegisterValues(TargetRegisterBank bank, Map<String, byte[]> updates);
|
||||
|
||||
public boolean objectRemoved(TargetObject removed);
|
||||
|
||||
public void stateChanged(TargetExecutionState state);
|
||||
|
||||
public void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg, boolean b);
|
||||
|
||||
public CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank parent);
|
||||
|
||||
public ManagedStackRecorder getStackRecorder();
|
||||
|
||||
}
|
|
@ -101,15 +101,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
}
|
||||
|
||||
private void threadAdded(TraceThread thread) {
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (supportsFocus(recorder)) {
|
||||
// TODO: Same for stack frame? I can't imagine it's as common as this....
|
||||
if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) {
|
||||
activate(DebuggerCoordinates.thread(thread));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (current.getTrace() != trace) {
|
||||
return;
|
||||
}
|
||||
if (current.getThread() != null) {
|
||||
return;
|
||||
}
|
||||
if (supportsFocus(current.getRecorder())) {
|
||||
return;
|
||||
}
|
||||
activate(DebuggerCoordinates.thread(thread));
|
||||
}
|
||||
|
||||
|
@ -198,8 +203,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return t;
|
||||
}
|
||||
|
||||
protected <T> CompletableFuture<T> tryHarder(Supplier<CompletableFuture<T>> action,
|
||||
int retries, long retryAfterMillis) {
|
||||
protected <T> CompletableFuture<T> tryHarder(Supplier<CompletableFuture<T>> action, int retries,
|
||||
long retryAfterMillis) {
|
||||
Executor exe = CompletableFuture.delayedExecutor(retryAfterMillis, TimeUnit.MILLISECONDS);
|
||||
// NB. thenCompose(f -> f) also ensures exceptions are handled here, not passed through
|
||||
CompletableFuture<T> result =
|
||||
|
@ -478,7 +483,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
// Note, not likely we can view non-zero frame with emulated ticks
|
||||
Integer frame = coordinates.getFrame();
|
||||
if (frame == null) {
|
||||
if (recorder != null && recorder.isSupportsFocus()) {
|
||||
if (supportsFocus(recorder)) {
|
||||
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, focus);
|
||||
if (traceFrame == null) {
|
||||
Msg.warn(this,
|
||||
|
@ -664,7 +669,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
if (varView != null) {
|
||||
varView.setSnap(coordinates.getSnap());
|
||||
}
|
||||
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import ghidra.async.AsyncDebouncer;
|
||||
import ghidra.async.AsyncTimer;
|
||||
import ghidra.framework.model.UndoableDomainObject;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class DefaultTransactionCoalescer<T extends UndoableDomainObject, U extends AutoCloseable>
|
||||
implements TransactionCoalescer {
|
||||
|
||||
protected class Coalescer {
|
||||
private final AsyncDebouncer<Void> debouncer =
|
||||
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, delayMs);
|
||||
private final U tid;
|
||||
|
||||
private volatile int activeCount = 0;
|
||||
|
||||
public Coalescer(String description) {
|
||||
this.tid = factory.apply(obj, description);
|
||||
|
||||
debouncer.addListener(this::settled);
|
||||
}
|
||||
|
||||
private void enter() {
|
||||
++activeCount;
|
||||
}
|
||||
|
||||
private void exit() {
|
||||
if (--activeCount == 0) {
|
||||
debouncer.contact(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void settled(Void __) {
|
||||
synchronized (lock) {
|
||||
if (activeCount == 0) {
|
||||
try {
|
||||
tid.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Could not close transaction: ", e);
|
||||
}
|
||||
tx = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultCoalescedTx implements CoalescedTx {
|
||||
protected DefaultCoalescedTx(String description) {
|
||||
synchronized (lock) {
|
||||
if (tx == null) {
|
||||
tx = new Coalescer(description);
|
||||
}
|
||||
tx.enter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
tx.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Object lock = new Object();
|
||||
protected final T obj;
|
||||
protected final TxFactory<? super T, U> factory;
|
||||
protected final int delayMs;
|
||||
|
||||
protected Coalescer tx;
|
||||
|
||||
public DefaultTransactionCoalescer(T obj, TxFactory<? super T, U> factory, int delayMs) {
|
||||
this.obj = obj;
|
||||
this.factory = factory;
|
||||
this.delayMs = delayMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultCoalescedTx start(String description) {
|
||||
return new DefaultCoalescedTx(description);
|
||||
}
|
||||
}
|
|
@ -15,51 +15,19 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.utils;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.async.AsyncDebouncer;
|
||||
import ghidra.async.AsyncTimer;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.framework.model.UndoableDomainObject;
|
||||
|
||||
public class TransactionCoalescer {
|
||||
protected final Program program;
|
||||
protected final AsyncDebouncer<Void> debouncer;
|
||||
|
||||
protected final Deque<Runnable> coalesced = new LinkedList<>();
|
||||
|
||||
public TransactionCoalescer(Program program, int delayWindow) {
|
||||
this.program = program;
|
||||
this.debouncer = new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, delayWindow);
|
||||
|
||||
this.debouncer.addListener(v -> processCoalesced());
|
||||
public interface TransactionCoalescer {
|
||||
public interface TxFactory<T extends UndoableDomainObject, U>
|
||||
extends BiFunction<T, String, U> {
|
||||
}
|
||||
|
||||
protected void processCoalesced() {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Coalesced", false)) {
|
||||
while (true) {
|
||||
Runnable next;
|
||||
synchronized (coalesced) {
|
||||
next = coalesced.poll();
|
||||
}
|
||||
if (next == null) {
|
||||
break;
|
||||
}
|
||||
next.run();
|
||||
}
|
||||
tid.commit();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Cancelled coalesced transaction due to exception", e);
|
||||
}
|
||||
// TODO: Is this really a good place for this?
|
||||
program.clearUndo();
|
||||
public interface CoalescedTx extends AutoCloseable {
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
|
||||
public synchronized void submit(Runnable runnable) {
|
||||
coalesced.offer(runnable);
|
||||
debouncer.contact(null);
|
||||
}
|
||||
CoalescedTx start(String description);
|
||||
}
|
||||
|
|
|
@ -32,9 +32,7 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
|
||||
@ServiceInfo(
|
||||
defaultProvider = DebuggerModelServiceProxyPlugin.class,
|
||||
description = "Service for managing debug sessions and connections")
|
||||
@ServiceInfo(defaultProvider = DebuggerModelServiceProxyPlugin.class, description = "Service for managing debug sessions and connections")
|
||||
public interface DebuggerModelService {
|
||||
/**
|
||||
* Get the set of model factories found on the classpath
|
||||
|
@ -128,7 +126,7 @@ public interface DebuggerModelService {
|
|||
* @param target the target to record.
|
||||
* @return a future which completes with the recorder, or completes exceptionally
|
||||
*/
|
||||
CompletableFuture<TraceRecorder> recordTargetBestOffer(TargetObject target);
|
||||
TraceRecorder recordTargetBestOffer(TargetObject target);
|
||||
|
||||
/**
|
||||
* Query mapping opinions, prompt the user, and record the given target
|
||||
|
@ -143,7 +141,7 @@ public interface DebuggerModelService {
|
|||
* @param target the target to record.
|
||||
* @return a future which completes with the recorder, or completes exceptionally
|
||||
*/
|
||||
CompletableFuture<TraceRecorder> recordTargetPromptOffers(TargetObject target);
|
||||
TraceRecorder recordTargetPromptOffers(TargetObject target);
|
||||
|
||||
/**
|
||||
* Start and open a new trace on the given target
|
||||
|
|
|
@ -21,7 +21,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
||||
import ghidra.app.plugin.core.debug.service.model.DefaultTraceRecorder.ListenerForRecord;
|
||||
import ghidra.app.plugin.core.debug.service.model.TraceEventListener;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
|
@ -36,11 +36,11 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
|||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
|
@ -75,9 +75,9 @@ public interface TraceRecorder {
|
|||
return TraceBreakpointKind.READ;
|
||||
case WRITE:
|
||||
return TraceBreakpointKind.WRITE;
|
||||
case EXECUTE:
|
||||
case HW_EXECUTE:
|
||||
return TraceBreakpointKind.EXECUTE;
|
||||
case SOFTWARE:
|
||||
case SW_EXECUTE:
|
||||
return TraceBreakpointKind.SOFTWARE;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
|
@ -110,9 +110,9 @@ public interface TraceRecorder {
|
|||
case WRITE:
|
||||
return TargetBreakpointKind.WRITE;
|
||||
case EXECUTE:
|
||||
return TargetBreakpointKind.EXECUTE;
|
||||
return TargetBreakpointKind.HW_EXECUTE;
|
||||
case SOFTWARE:
|
||||
return TargetBreakpointKind.SOFTWARE;
|
||||
return TargetBreakpointKind.SW_EXECUTE;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
@ -204,8 +204,6 @@ public interface TraceRecorder {
|
|||
*/
|
||||
void removeListener(TraceRecorderListener listener);
|
||||
|
||||
boolean isViewAtPresent(TraceProgramView view);
|
||||
|
||||
TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt);
|
||||
|
||||
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
|
||||
|
@ -407,7 +405,7 @@ public interface TraceRecorder {
|
|||
* @param thread an optional thread, or {@code null} for the process
|
||||
* @return the list of collected containers, possibly empty
|
||||
*/
|
||||
List<TargetBreakpointContainer> collectBreakpointContainers(TargetThread thread);
|
||||
List<TargetBreakpointSpecContainer> collectBreakpointContainers(TargetThread thread);
|
||||
|
||||
/**
|
||||
* Collect effective breakpoint pertinent to the target or a given thread
|
||||
|
@ -424,8 +422,8 @@ public interface TraceRecorder {
|
|||
/**
|
||||
* Get the kinds of breakpoints supported by any of the recorded breakpoint containers.
|
||||
*
|
||||
* This is the union of all kinds supported among all {@link TargetBreakpointContainer}s found
|
||||
* applicable to the target by this recorder. Chances are, there is only one container.
|
||||
* This is the union of all kinds supported among all {@link TargetBreakpointSpecContainer}s
|
||||
* found applicable to the target by this recorder. Chances are, there is only one container.
|
||||
*
|
||||
* @return the set of supported kinds
|
||||
*/
|
||||
|
@ -478,5 +476,7 @@ public interface TraceRecorder {
|
|||
* @return the listener
|
||||
*/
|
||||
@Internal
|
||||
ListenerForRecord getListenerForRecord();
|
||||
TraceEventListener getListenerForRecord();
|
||||
|
||||
ListenerSet<TraceRecorderListener> getListeners();
|
||||
}
|
||||
|
|
|
@ -34,9 +34,9 @@ import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerService
|
|||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.util.DebuggerModelTestUtils;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
@ -131,16 +131,16 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
|
|||
new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false);
|
||||
}
|
||||
|
||||
TargetBreakpointContainer bc1 =
|
||||
TargetBreakpointSpecContainer bc1 =
|
||||
waitFor(() -> Unique.assertAtMostOne(recorder1.collectBreakpointContainers(null)),
|
||||
"No container");
|
||||
waitOn(bc1.placeBreakpoint(mb.addr(0x00401234), Set.of(TargetBreakpointKind.SOFTWARE)));
|
||||
waitOn(bc1.placeBreakpoint(mb.addr(0x00401234), Set.of(TargetBreakpointKind.SW_EXECUTE)));
|
||||
waitOn(bc1.placeBreakpoint(mb.rng(0x00604321, 0x00604324),
|
||||
Set.of(TargetBreakpointKind.WRITE)));
|
||||
TargetBreakpointContainer bc3 =
|
||||
TargetBreakpointSpecContainer bc3 =
|
||||
waitFor(() -> Unique.assertAtMostOne(recorder3.collectBreakpointContainers(null)),
|
||||
"No container");
|
||||
waitOn(bc3.placeBreakpoint(mb.addr(0x7fac1234), Set.of(TargetBreakpointKind.SOFTWARE)));
|
||||
waitOn(bc3.placeBreakpoint(mb.addr(0x7fac1234), Set.of(TargetBreakpointKind.SW_EXECUTE)));
|
||||
|
||||
TraceBreakpoint bpt = waitForValue(() -> Unique.assertAtMostOne(
|
||||
trace3.getBreakpointManager().getBreakpointsAt(0, addr(trace3, 0x7fac1234))));
|
||||
|
|
|
@ -28,7 +28,7 @@ import ghidra.app.plugin.core.debug.gui.objects.components.*;
|
|||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
||||
import ghidra.dbg.model.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.DebuggerModelTestUtils;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
import ghidra.util.Swing;
|
||||
import help.screenshot.GhidraScreenShotGenerator;
|
||||
|
||||
|
|
|
@ -208,7 +208,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
}
|
||||
}
|
||||
|
||||
protected static TargetBreakpointContainer getBreakpointContainer(TraceRecorder r) {
|
||||
protected static TargetBreakpointSpecContainer getBreakpointContainer(TraceRecorder r) {
|
||||
return waitFor(() -> Unique.assertAtMostOne(r.collectBreakpointContainers(null)),
|
||||
"No container");
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
|
|||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.LogicalBreakpoint.Enablement;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.disassemble.Disassembler;
|
||||
|
@ -100,8 +100,8 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
|
|||
protected void addLiveMemoryAndBreakpoint(TraceRecorder recorder)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
|
||||
TargetBreakpointContainer cont = getBreakpointContainer(recorder);
|
||||
cont.placeBreakpoint(mb.addr(0x55550123), Set.of(TargetBreakpointKind.SOFTWARE))
|
||||
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
|
||||
cont.placeBreakpoint(mb.addr(0x55550123), Set.of(TargetBreakpointKind.SW_EXECUTE))
|
||||
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,9 +33,10 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
|||
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
|
||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncTestUtils;
|
||||
import ghidra.dbg.model.TestTargetProcess;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.model.address.AddressOverflowException;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -50,7 +51,8 @@ import ghidra.util.exception.CancelledException;
|
|||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebuggerGUITest
|
||||
implements AsyncTestUtils {
|
||||
protected static final long TIMEOUT_MILLIS =
|
||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||
|
||||
|
@ -84,14 +86,13 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
|
|||
}
|
||||
|
||||
protected void addLiveBreakpoint(TraceRecorder recorder, long offset) throws Exception {
|
||||
TargetBreakpointContainer cont = getBreakpointContainer(recorder);
|
||||
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SOFTWARE))
|
||||
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
|
||||
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
|
||||
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
protected void addStaticMemoryAndBreakpoint()
|
||||
throws LockException, DuplicateNameException, MemoryConflictException,
|
||||
AddressOverflowException, CancelledException {
|
||||
protected void addStaticMemoryAndBreakpoint() throws LockException, DuplicateNameException,
|
||||
MemoryConflictException, AddressOverflowException, CancelledException {
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(program, "Add bookmark break", true)) {
|
||||
program.getMemory()
|
||||
|
@ -114,7 +115,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAddLiveOpenTracePopulatesProvider() throws Exception {
|
||||
public void testAddLiveOpenTracePopulatesProvider() throws Throwable {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
|
||||
|
@ -122,6 +123,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
|
|||
Trace trace = recorder.getTrace();
|
||||
|
||||
addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
waitForDomainObject(trace);
|
||||
|
||||
// NB, optionally open trace. Mapping only works if open...
|
||||
|
@ -514,8 +516,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
|
|||
assertEquals(2, lb2.getTraceBreakpoints().size());
|
||||
});
|
||||
|
||||
List<LogicalBreakpointRow> breakData =
|
||||
bptModel.getModelData();
|
||||
List<LogicalBreakpointRow> breakData = bptModel.getModelData();
|
||||
List<BreakpointLocationRow> filtLocs =
|
||||
breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
|
||||
|
||||
|
@ -574,8 +575,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
|
|||
}
|
||||
|
||||
public static final Set<String> POPUP_ACTIONS = Set.of(
|
||||
AbstractEnableSelectedBreakpointsAction.NAME,
|
||||
AbstractDisableSelectedBreakpointsAction.NAME,
|
||||
AbstractEnableSelectedBreakpointsAction.NAME, AbstractDisableSelectedBreakpointsAction.NAME,
|
||||
AbstractClearSelectedBreakpointsAction.NAME);
|
||||
|
||||
@Test
|
||||
|
@ -590,10 +590,10 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
|
|||
// NOTE: the row becomes selected by right-click
|
||||
clickTableCellWithButton(breakpointsProvider.breakpointTable, 0, 0, MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(
|
||||
AbstractEnableSelectedBreakpointsAction.NAME,
|
||||
AbstractDisableSelectedBreakpointsAction.NAME,
|
||||
AbstractClearSelectedBreakpointsAction.NAME));
|
||||
assertMenu(POPUP_ACTIONS,
|
||||
Set.of(AbstractEnableSelectedBreakpointsAction.NAME,
|
||||
AbstractDisableSelectedBreakpointsAction.NAME,
|
||||
AbstractClearSelectedBreakpointsAction.NAME));
|
||||
|
||||
// NOTE: With no selection, no actions (even table built-in) apply, so no menu
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
|||
import ghidra.app.plugin.core.interpreter.InterpreterComponentProvider;
|
||||
import ghidra.dbg.model.TestTargetInterpreter.ExecuteCall;
|
||||
import ghidra.dbg.target.TargetConsole.Channel;
|
||||
import ghidra.dbg.util.DebuggerModelTestUtils;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
|
||||
public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerGUITest
|
||||
implements DebuggerModelTestUtils {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.register;
|
||||
|
||||
import static ghidra.lifecycle.Unfinished.TODO;
|
||||
import static ghidra.lifecycle.Unfinished.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
@ -30,12 +30,11 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
|||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingTrackLocationAction.LocationTrackingSpec;
|
||||
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.async.AsyncTestUtils;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
|
||||
|
@ -47,7 +46,8 @@ import ghidra.trace.model.thread.TraceThread;
|
|||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerGUITest
|
||||
implements AsyncTestUtils {
|
||||
static {
|
||||
DebuggerModelServiceTest.addTestModelPathPatterns();
|
||||
}
|
||||
|
@ -151,6 +151,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
if (thread == null) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
DebuggerRegisterMapper mapper = recorder.getRegisterMapper(thread);
|
||||
if (mapper == null) {
|
||||
return false;
|
||||
|
@ -158,6 +159,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
if (!mapper.getRegistersOnTarget().containsAll(baseRegs)) {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
});
|
||||
return recorder;
|
||||
|
@ -320,15 +322,18 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLiveActivateThenAddValuesPopulatesPanel() throws Exception {
|
||||
public void testLiveActivateThenAddValuesPopulatesPanel() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
|
||||
mb.testBank1.writeRegister("pc", new byte[] { 0x00, 0x40, 0x00, 0x00 });
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
waitForDomainObject(recorder.getTrace());
|
||||
|
||||
RegisterRow rowL = findRegisterRow(pc);
|
||||
waitForPass(() -> assertTrue(rowL.isKnown()));
|
||||
assertPCRowValuePopulated();
|
||||
}
|
||||
|
||||
|
@ -396,7 +401,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLiveModifySubValueAffectsTarget() throws Exception {
|
||||
public void testLiveModifySubValueAffectsTarget() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
Trace trace = recorder.getTrace();
|
||||
traceManager.openTrace(trace);
|
||||
|
@ -407,14 +412,12 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
||||
performAction(registersProvider.actionEnableEdits);
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Pretend fetch", true)) {
|
||||
TraceMemoryRegisterSpace regs =
|
||||
trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
|
||||
regs.setValue(0, new RegisterValue(r0, BigInteger.ZERO));
|
||||
}
|
||||
mb.testBank1.writeRegistersNamed(Map.of("r0", new byte[] { 0 }));
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
waitForDomainObject(trace);
|
||||
|
||||
RegisterRow rowL = findRegisterRow(r0l);
|
||||
assertTrue(rowL.isValueEditable());
|
||||
waitForPass(() -> assertTrue(rowL.isValueEditable()));
|
||||
|
||||
setRowText(rowL, "05060708");
|
||||
waitForSwing();
|
||||
|
@ -656,7 +659,8 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
// TODO: It'd be nice if plugin tracked disconnected providers....
|
||||
DebuggerRegistersProvider cloned =
|
||||
(DebuggerRegistersProvider) tool.getActiveComponentProvider();
|
||||
assertEquals("[Registers: Thread1, 0]", cloned.getTitle());
|
||||
assertEquals("[Registers]", cloned.getTitle());
|
||||
assertEquals("Thread1", cloned.getSubTitle());
|
||||
|
||||
traceManager.activateThread(thread2);
|
||||
waitForSwing();
|
||||
|
|
|
@ -34,7 +34,7 @@ import ghidra.dbg.model.TestTargetMemoryRegion;
|
|||
import ghidra.dbg.model.TestTargetProcess;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.util.DebuggerModelTestUtils;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Bookmark;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -290,7 +290,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
}
|
||||
|
||||
protected void addTargetAccessBreakpoint(TraceRecorder r) throws Exception {
|
||||
TargetBreakpointContainer cont = getBreakpointContainer(r);
|
||||
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
|
||||
cont.placeBreakpoint(mb.testModel.getAddress("ram", 0x56550123),
|
||||
Set.of(TargetBreakpointKind.READ, TargetBreakpointKind.WRITE))
|
||||
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
|
@ -301,13 +301,13 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
TraceMemoryRegion textRegion =
|
||||
waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region);
|
||||
long offset = textRegion.getMinAddress().getOffset() + 0x0123;
|
||||
TargetBreakpointContainer cont = getBreakpointContainer(r);
|
||||
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SOFTWARE))
|
||||
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
|
||||
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
|
||||
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
protected void removeTargetSoftwareBreakpoint(TraceRecorder r) throws Exception {
|
||||
TargetBreakpointContainer cont = getBreakpointContainer(r);
|
||||
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
|
||||
cont.fetchElements().thenAccept(elements -> {
|
||||
for (TargetObject obj : elements.values()) {
|
||||
if (!(obj instanceof TargetBreakpointSpec) ||
|
||||
|
@ -315,7 +315,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
continue;
|
||||
}
|
||||
TargetBreakpointSpec spec = (TargetBreakpointSpec) obj;
|
||||
if (!spec.getKinds().contains(TargetBreakpointKind.SOFTWARE)) {
|
||||
if (!spec.getKinds().contains(TargetBreakpointKind.SW_EXECUTE)) {
|
||||
continue;
|
||||
}
|
||||
TargetDeletable del = (TargetDeletable) obj;
|
||||
|
|
|
@ -33,7 +33,9 @@ import ghidra.dbg.DebuggerModelFactory;
|
|||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.model.TestDebuggerObjectModel;
|
||||
import ghidra.dbg.model.TestLocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
import ghidra.dbg.util.PathMatcher;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
@ -51,8 +53,43 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
protected static final long TIMEOUT_MILLIS =
|
||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||
|
||||
public 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[]"));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
public static void addTestModelPathPatterns() {
|
||||
PathMatcher m = DefaultTraceRecorder.HARDCODED_MATCHER;
|
||||
PathMatcher m = HARDCODED_MATCHER;
|
||||
m.addPattern(PathUtils.parse("Processes[]"));
|
||||
m.addPattern(PathUtils.parse("Processes[].Breakpoints[]"));
|
||||
m.addPattern(PathUtils.parse("Processes[].Memory[]"));
|
||||
|
@ -84,8 +121,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
void elementRemoved(E element);
|
||||
}
|
||||
|
||||
static class CollectionChangeDelegateWrapper<E>
|
||||
implements CollectionChangeListener<E> {
|
||||
static class CollectionChangeDelegateWrapper<E> implements CollectionChangeListener<E> {
|
||||
protected final CollectionChangeDelegate<E> delegate;
|
||||
|
||||
public CollectionChangeDelegateWrapper(CollectionChangeDelegate<E> delegate) {
|
||||
|
|
|
@ -22,16 +22,20 @@ import java.util.Map.Entry;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.model.TestTargetMemoryRegion;
|
||||
import ghidra.dbg.model.TestTargetRegisterBankInThread;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.data.PointerDataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.listing.TraceCodeRegisterSpace;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
@ -106,13 +110,14 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
Language lang = trace.getBaseLanguage();
|
||||
Register r0 = lang.getRegister("r0");
|
||||
Register r1 = lang.getRegister("r1");
|
||||
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
|
||||
//TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
|
||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
||||
TraceMemoryRegisterSpace rs = createRegSpace(thread);
|
||||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
|
||||
Register::isBaseRegister);
|
||||
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
|
||||
|
||||
waitForCondition(() -> registerMapped(recorder, thread, r0));
|
||||
//waitForCondition(() -> registerMapped(recorder, thread, r0));
|
||||
regs.writeRegister("r0", tb.arr(1)).get();
|
||||
|
||||
waitForPass(() -> {
|
||||
|
@ -145,12 +150,16 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
|
||||
r -> r.isBaseRegister() && r != pc && r != sp);
|
||||
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type", true)) {
|
||||
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
|
||||
code.definedData().create(Range.atLeast(0L), pc, PointerDataType.dataType);
|
||||
}
|
||||
|
||||
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), pc.getAddress()));
|
||||
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), sp.getAddress()));
|
||||
|
||||
mb.testProcess1.regs.addRegister(pc);
|
||||
waitForCondition(() -> registerMapped(recorder, thread, pc));
|
||||
//waitForCondition(() -> registerMapped(recorder, thread, pc));
|
||||
regs.writeRegister("pc", tb.arr(0x55, 0x55, 0x01, 0x23));
|
||||
|
||||
waitForPass(() -> {
|
||||
|
@ -190,12 +199,16 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
|
||||
r -> r.isBaseRegister() && r != pc && r != sp);
|
||||
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add SP type", true)) {
|
||||
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
|
||||
code.definedData().create(Range.atLeast(0L), sp, PointerDataType.dataType);
|
||||
}
|
||||
|
||||
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), pc.getAddress()));
|
||||
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), sp.getAddress()));
|
||||
|
||||
mb.testProcess1.regs.addRegister(sp);
|
||||
waitForCondition(() -> registerMapped(recorder, thread, sp));
|
||||
//waitForCondition(() -> registerMapped(recorder, thread, sp));
|
||||
regs.writeRegister("sp", tb.arr(0x22, 0x22, 0x03, 0x21));
|
||||
|
||||
waitForPass(() -> {
|
||||
|
@ -228,13 +241,17 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
Trace trace = recorder.getTrace();
|
||||
Language lang = trace.getBaseLanguage();
|
||||
Register pc = lang.getRegister("pc");
|
||||
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
|
||||
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550123, 0x55550321), "rx");
|
||||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
|
||||
Register::isBaseRegister);
|
||||
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
|
||||
|
||||
waitForCondition(() -> registerMapped(recorder, thread, pc));
|
||||
//waitForCondition(() -> registerMapped(recorder, thread, pc));
|
||||
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type", true)) {
|
||||
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
|
||||
code.definedData().create(Range.atLeast(0L), pc, PointerDataType.dataType);
|
||||
}
|
||||
regs.writeRegister("pc", tb.arr(0x55, 0x55, 0x02, 0x22));
|
||||
|
||||
TraceMemoryManager mm = trace.getMemoryManager();
|
||||
|
|
|
@ -28,7 +28,7 @@ import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
|
|||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.model.TestTargetRegisterBankInThread;
|
||||
import ghidra.dbg.util.DebuggerModelTestUtils;
|
||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue