mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-27 14:40:28 +00:00
Merge remote-tracking branch 'origin/GP-2752_Dan_removePerTargetObjectListeners--SQUASHED'
This commit is contained in:
commit
b9a6bfdcd3
@ -23,11 +23,9 @@ import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
|
||||
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
import agent.dbgeng.model.AbstractDbgModel;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.SpiTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public interface DbgModelTargetObject extends SpiTargetObject {
|
||||
|
||||
@ -65,8 +63,6 @@ public interface DbgModelTargetObject extends SpiTargetObject {
|
||||
|
||||
public CompletableFuture<List<TargetObject>> requestNativeElements();
|
||||
|
||||
public ListenerSet<DebuggerModelListener> getListeners();
|
||||
|
||||
public DbgModelTargetSession getParentSession();
|
||||
|
||||
public DbgModelTargetProcess getParentProcess();
|
||||
|
@ -25,12 +25,10 @@ import agent.dbgeng.manager.DbgThread;
|
||||
import agent.dbgeng.manager.impl.*;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.error.DebuggerRegisterAccessException;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.dbg.util.ConversionUtils;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, TargetRegisterBank {
|
||||
|
||||
@ -91,10 +89,8 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
|
||||
reg.setModified(value.toString(16).equals(oldval));
|
||||
}
|
||||
}
|
||||
ListenerSet<DebuggerModelListener> listeners = getListeners();
|
||||
if (listeners != null) {
|
||||
listeners.fire.registersUpdated(getProxy(), result);
|
||||
}
|
||||
|
||||
broadcast().registersUpdated(getProxy(), result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
@ -126,7 +122,7 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
|
||||
getParentThread().getThread().writeRegisters(toWrite).handle(seq::next);
|
||||
// TODO: Should probably filter only effective and normalized writes in the callback
|
||||
}).then(seq -> {
|
||||
getListeners().fire.registersUpdated(getProxy(), values);
|
||||
broadcast().registersUpdated(getProxy(), values);
|
||||
seq.exit();
|
||||
}).finish();
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public interface DbgModelTargetSession extends //
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
getListeners().fire.consoleOutput(getProxy(), chan, output);
|
||||
broadcast().consoleOutput(getProxy(), chan, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,7 +95,7 @@ public class DbgModelTargetBreakpointContainerImpl extends DbgModelTargetObjectI
|
||||
DbgModelTargetThread targetThread =
|
||||
getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
|
||||
DbgModelTargetBreakpointSpec spec = getTargetBreakpointSpec(info);
|
||||
listeners.fire.breakpointHit(getProxy(), targetThread, null, spec, spec);
|
||||
broadcast().breakpointHit(getProxy(), targetThread, null, spec, spec);
|
||||
spec.breakpointHit();
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
|
||||
if (span == null) {
|
||||
throw new DebuggerMemoryAccessException("Cannot read at " + address);
|
||||
}
|
||||
listeners.fire.memoryUpdated(getProxy(), address, buf.array());
|
||||
broadcast().memoryUpdated(getProxy(), address, buf.array());
|
||||
return Arrays.copyOf(buf.array(), (int) span.length());
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
|
||||
}
|
||||
|
||||
private void writeAssist(Address address, byte[] data) {
|
||||
listeners.fire.memoryUpdated(getProxy(), address, data);
|
||||
broadcast().memoryUpdated(getProxy(), address, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,13 +27,16 @@ import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
|
||||
import ghidra.lifecycle.Internal;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "ModuleContainer", elements = { //
|
||||
@TargetElementType(type = DbgModelTargetModuleImpl.class) //
|
||||
}, //
|
||||
elementResync = ResyncMode.ONCE, //
|
||||
attributes = { //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
}, canonicalContainer = true)
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "ModuleContainer",
|
||||
elements = {
|
||||
@TargetElementType(type = DbgModelTargetModuleImpl.class)
|
||||
},
|
||||
elementResync = ResyncMode.ONCE,
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
|
||||
implements DbgModelTargetModuleContainer {
|
||||
// NOTE: -file-list-shared-libraries omits the main module and system-supplied DSO.
|
||||
@ -66,7 +69,7 @@ public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(getManager().getEventThread());
|
||||
changeElements(List.of(), List.of(module), Map.of(), "Loaded");
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + name + " loaded", List.of(module));
|
||||
}
|
||||
|
||||
@ -77,7 +80,7 @@ public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
|
||||
if (targetModule != null) {
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(getManager().getEventThread());
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + name + " unloaded", List.of(targetModule));
|
||||
DbgModelImpl impl = (DbgModelImpl) model;
|
||||
impl.deleteModelObject(targetModule.getDbgModule());
|
||||
@ -108,6 +111,7 @@ public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbgModelTargetModule getTargetModule(String name) {
|
||||
// Only get here from libraryLoaded or getElements. The known list should be fresh.
|
||||
DbgModule module = process.getKnownModules().get(name);
|
||||
|
@ -33,12 +33,12 @@ import ghidra.dbg.target.schema.*;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "ProcessContainer",
|
||||
elements = { //
|
||||
@TargetElementType(type = DbgModelTargetProcessImpl.class) //
|
||||
elements = {
|
||||
@TargetElementType(type = DbgModelTargetProcessImpl.class)
|
||||
},
|
||||
attributes = { //
|
||||
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
attributes = {
|
||||
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl
|
||||
@ -58,7 +58,7 @@ public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl
|
||||
DbgModelTargetProcess process = getTargetProcess(proc);
|
||||
changeElements(List.of(), List.of(process), Map.of(), "Added");
|
||||
process.processStarted(proc.getPid());
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
"Process " + proc.getId() + " started " + process.getName() + "pid=" + proc.getPid(),
|
||||
List.of(process));
|
||||
}
|
||||
|
@ -139,6 +139,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadStateChangedSpecific(DbgThread thread, DbgState state) {
|
||||
TargetExecutionState targetState = convertState(state);
|
||||
setExecutionState(targetState, "ThreadStateChanged");
|
||||
@ -212,7 +213,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
|
||||
EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() //
|
||||
), "Exited");
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
"Process " + proc.getId() + " exited code=" + proc.getExitCode(),
|
||||
List.of(getProxy()));
|
||||
}
|
||||
@ -221,7 +222,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
|
||||
@Override
|
||||
public void memoryChanged(DbgProcess proc, long addr, int len, DbgCause cause) {
|
||||
if (proc.equals(this.process)) {
|
||||
listeners.fire.invalidateCacheRequested(memory);
|
||||
broadcast().invalidateCacheRequested(memory);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
|
||||
changeAttrs(reg, value);
|
||||
}
|
||||
this.values = result;
|
||||
listeners.fire.registersUpdated(getProxy(), result);
|
||||
broadcast().registersUpdated(getProxy(), result);
|
||||
return result;
|
||||
}));
|
||||
}
|
||||
@ -159,7 +159,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
|
||||
return thread.writeRegisters(toWrite);
|
||||
// TODO: Should probably filter only effective and normalized writes in the callback
|
||||
}).thenAccept(__ -> {
|
||||
listeners.fire.registersUpdated(getProxy(), values);
|
||||
broadcast().registersUpdated(getProxy(), values);
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,16 @@ import ghidra.dbg.target.TargetConfigurable;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "ThreadContainer", elements = { //
|
||||
@TargetElementType(type = DbgModelTargetThreadImpl.class) //
|
||||
}, attributes = { //
|
||||
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
}, canonicalContainer = true)
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "ThreadContainer",
|
||||
elements = {
|
||||
@TargetElementType(type = DbgModelTargetThreadImpl.class)
|
||||
},
|
||||
attributes = {
|
||||
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
|
||||
implements DbgModelTargetThreadContainer, DbgModelTargetConfigurable {
|
||||
|
||||
@ -59,7 +63,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
|
||||
DbgModelTargetThread targetThread = getTargetThread(thread);
|
||||
changeElements(List.of(), List.of(targetThread), Map.of(), "Created");
|
||||
targetThread.threadStateChangedSpecific(DbgState.STARTING, DbgReason.getReason(null));
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
"Thread " + thread.getId() + " started", List.of(targetThread));
|
||||
}
|
||||
|
||||
@ -68,7 +72,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
|
||||
DbgReason reason) {
|
||||
DbgModelTargetThread targetThread = getTargetThread(thread);
|
||||
TargetEventType eventType = getEventType(state, cause, reason);
|
||||
getListeners().fire.event(getProxy(), targetThread, eventType,
|
||||
broadcast().event(getProxy(), targetThread, eventType,
|
||||
"Thread " + thread.getId() + " state changed", List.of(targetThread));
|
||||
targetThread.threadStateChangedSpecific(state, reason);
|
||||
}
|
||||
@ -78,7 +82,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
|
||||
DbgModelImpl impl = (DbgModelImpl) model;
|
||||
DbgModelTargetThread targetThread = (DbgModelTargetThread) impl.getModelObject(threadId);
|
||||
if (targetThread != null) {
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
}
|
||||
//synchronized (this) {
|
||||
|
@ -32,7 +32,6 @@ import agent.dbgmodel.jna.dbgmodel.DbgModelNative.ModelObjectKind;
|
||||
import agent.dbgmodel.jna.dbgmodel.DbgModelNative.TypeKind;
|
||||
import agent.dbgmodel.manager.DbgManager2Impl;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
@ -254,7 +253,8 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
TargetExecutionStateful stateful = (TargetExecutionStateful) proxy;
|
||||
TargetExecutionState state = stateful.getExecutionState();
|
||||
attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, state);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME,
|
||||
TargetExecutionState.INACTIVE);
|
||||
}
|
||||
@ -412,11 +412,6 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(DebuggerModelListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbgModelTargetSession getParentSession() {
|
||||
DbgModelTargetObject test = (DbgModelTargetObject) parent;
|
||||
|
@ -179,7 +179,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
System.err.println("processAdded - null");
|
||||
return;
|
||||
}
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
"Process " + proc.getId() + " started " + "notepad.exe" + " pid=" + proc.getPid(),
|
||||
List.of(targetProcess));
|
||||
});
|
||||
@ -193,7 +193,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
System.err.println("threadCreated - null");
|
||||
return;
|
||||
}
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
"Thread " + thread.getId() + " started", List.of(targetThread));
|
||||
DelegateDbgModel2TargetObject delegate =
|
||||
(DelegateDbgModel2TargetObject) targetThread.getDelegate();
|
||||
@ -210,7 +210,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
}
|
||||
getObject(getManager().getEventThread()).thenAccept(t -> {
|
||||
TargetThread eventThread = (TargetThread) t;
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName() + " loaded", List.of(mod));
|
||||
});
|
||||
getObject(getManager().getEventProcess()).thenAccept(p -> {
|
||||
@ -231,7 +231,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
}
|
||||
getObject(getManager().getEventThread()).thenAccept(t -> {
|
||||
TargetThread eventThread = (TargetThread) t;
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName() + " unloaded", List.of(mod));
|
||||
});
|
||||
getObject(getManager().getEventProcess()).thenAccept(p -> {
|
||||
@ -369,7 +369,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() //
|
||||
), "Exited");
|
||||
}
|
||||
getListeners().fire.event(targetProcess.getProxy(), null,
|
||||
broadcast().event(targetProcess.getProxy(), null,
|
||||
TargetEventType.PROCESS_EXITED,
|
||||
"Process " + proc.getId() + " exited code=" + proc.getExitCode(),
|
||||
List.of(getProxy()));
|
||||
@ -383,7 +383,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
return;
|
||||
}
|
||||
DbgModelTargetThread targetThread = (DbgModelTargetThread) thread.getProxy();
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
});
|
||||
}
|
||||
@ -401,7 +401,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
), reason.desc());
|
||||
intrinsics.put(TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread);
|
||||
TargetEventType eventType = getEventType(state, cause, reason);
|
||||
getListeners().fire.event(getProxy(), targetThread, eventType,
|
||||
broadcast().event(getProxy(), targetThread, eventType,
|
||||
"Thread " + thread.getId() + " state changed", List.of(targetThread));
|
||||
DelegateDbgModel2TargetObject delegate =
|
||||
(DelegateDbgModel2TargetObject) targetThread.getDelegate();
|
||||
@ -472,7 +472,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
|
||||
DbgThread thread = info.getEventThread();
|
||||
TargetObject targetThread = getModel().getModelObject(thread);
|
||||
listeners.fire.breakpointHit(bpt.getParent(), targetThread, null, bpt, bpt);
|
||||
broadcast().breakpointHit(bpt.getParent(), targetThread, null, bpt, bpt);
|
||||
bpt.breakpointHit();
|
||||
});
|
||||
}
|
||||
|
@ -22,11 +22,9 @@ import java.util.concurrent.CompletableFuture;
|
||||
import agent.frida.manager.impl.FridaManagerImpl;
|
||||
import agent.frida.model.AbstractFridaModel;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.SpiTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public interface FridaModelTargetObject extends SpiTargetObject {
|
||||
|
||||
@ -59,8 +57,6 @@ public interface FridaModelTargetObject extends SpiTargetObject {
|
||||
|
||||
public CompletableFuture<List<TargetObject>> requestNativeElements();
|
||||
|
||||
public ListenerSet<DebuggerModelListener> getListeners();
|
||||
|
||||
public FridaModelTargetSession getParentSession();
|
||||
|
||||
public FridaModelTargetProcess getParentProcess();
|
||||
|
@ -50,7 +50,7 @@ public interface FridaModelTargetSession extends //
|
||||
== DebugOutputFlags.DEBUG_OUTPUT_WARNING.getValue())) {
|
||||
chan = TargetConsole.Channel.STDERR;
|
||||
}
|
||||
getListeners().fire.consoleOutput(getProxy(), chan, output);
|
||||
broadcast().consoleOutput(getProxy(), chan, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -80,7 +80,7 @@ public class FridaModelTargetKernelMemoryContainerImpl extends FridaModelTargetO
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (refresh) {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
return getManager().listKernelMemory();
|
||||
}
|
||||
@ -104,12 +104,12 @@ public class FridaModelTargetKernelMemoryContainerImpl extends FridaModelTargetO
|
||||
if (range == null) {
|
||||
throw new DebuggerMemoryAccessException("Cannot read at " + address);
|
||||
}
|
||||
listeners.fire.memoryUpdated(getProxy(), address, buf.array());
|
||||
broadcast().memoryUpdated(getProxy(), address, buf.array());
|
||||
return Arrays.copyOf(buf.array(), (int) range.getLength());
|
||||
}
|
||||
|
||||
private void writeAssist(Address address, byte[] data) {
|
||||
listeners.fire.memoryUpdated(getProxy(), address, data);
|
||||
broadcast().memoryUpdated(getProxy(), address, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,12 +72,13 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(thread);
|
||||
changeElements(List.of(), List.of(targetModule), Map.of(), "Loaded");
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moduleReplaced(FridaProcess proc, FridaModuleInfo info, int index, FridaCause cause) {
|
||||
public void moduleReplaced(FridaProcess proc, FridaModuleInfo info, int index,
|
||||
FridaCause cause) {
|
||||
FridaModule module = info.getModule(index);
|
||||
changeElements(List.of(), List.of(getTargetModule(module)), Map.of(), "Replaced");
|
||||
FridaModelTargetModule targetModule = getTargetModule(module);
|
||||
@ -85,14 +86,15 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moduleUnloaded(FridaProcess proc, FridaModuleInfo info, int index, FridaCause cause) {
|
||||
public void moduleUnloaded(FridaProcess proc, FridaModuleInfo info, int index,
|
||||
FridaCause cause) {
|
||||
FridaModelTargetModule targetModule = getTargetModule(info.getModule(index));
|
||||
if (targetModule != null) {
|
||||
FridaThread thread = getManager().getCurrentThread();
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(thread);
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
|
||||
FridaModelImpl impl = (FridaModelImpl) model;
|
||||
impl.deleteModelObject(targetModule.getModule());
|
||||
}
|
||||
@ -112,7 +114,7 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (refresh) {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
return getManager().listKernelModules();
|
||||
}
|
||||
@ -121,7 +123,8 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
|
||||
public FridaModelTargetKernelModuleImpl getTargetModule(FridaModule module) {
|
||||
TargetObject targetObject = getMapObject(module);
|
||||
if (targetObject != null) {
|
||||
FridaModelTargetKernelModuleImpl targetModule = (FridaModelTargetKernelModuleImpl) targetObject;
|
||||
FridaModelTargetKernelModuleImpl targetModule =
|
||||
(FridaModelTargetKernelModuleImpl) targetObject;
|
||||
targetModule.setModelObject(module);
|
||||
return targetModule;
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ public class FridaModelTargetMemoryContainerImpl extends FridaModelTargetObjectI
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (refresh) {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
return getManager().listMemory(process.getProcess());
|
||||
}
|
||||
@ -126,12 +126,12 @@ public class FridaModelTargetMemoryContainerImpl extends FridaModelTargetObjectI
|
||||
if (range == null) {
|
||||
throw new DebuggerMemoryAccessException("Cannot read at " + address);
|
||||
}
|
||||
listeners.fire.memoryUpdated(getProxy(), address, buf.array());
|
||||
broadcast().memoryUpdated(getProxy(), address, buf.array());
|
||||
return Arrays.copyOf(buf.array(), (int) range.getLength());
|
||||
}
|
||||
|
||||
private void writeAssist(Address address, byte[] data) {
|
||||
listeners.fire.memoryUpdated(getProxy(), address, data);
|
||||
broadcast().memoryUpdated(getProxy(), address, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,25 +20,12 @@ import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.frida.frida.FridaModuleInfo;
|
||||
import agent.frida.manager.FridaCause;
|
||||
import agent.frida.manager.FridaModule;
|
||||
import agent.frida.manager.FridaProcess;
|
||||
import agent.frida.manager.FridaSession;
|
||||
import agent.frida.manager.FridaThread;
|
||||
import agent.frida.model.iface2.FridaModelTargetModule;
|
||||
import agent.frida.model.iface2.FridaModelTargetModuleContainer;
|
||||
import agent.frida.model.iface2.FridaModelTargetSession;
|
||||
import agent.frida.model.methods.FridaModelTargetModuleInitImpl;
|
||||
import agent.frida.model.methods.FridaModelTargetModuleInterceptorImpl;
|
||||
import agent.frida.model.methods.FridaModelTargetModuleLoadImpl;
|
||||
import agent.frida.model.methods.FridaModelTargetUnloadScriptImpl;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetThread;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetElementType;
|
||||
import agent.frida.manager.*;
|
||||
import agent.frida.model.iface2.*;
|
||||
import agent.frida.model.methods.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
@ -77,7 +64,7 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
|
||||
unload //
|
||||
), Map.of( //
|
||||
), "Initialized");
|
||||
|
||||
|
||||
getManager().addEventsListener(this);
|
||||
requestElements(true);
|
||||
}
|
||||
@ -103,12 +90,13 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(thread);
|
||||
changeElements(List.of(), List.of(targetModule), Map.of(), "Loaded");
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moduleReplaced(FridaProcess proc, FridaModuleInfo info, int index, FridaCause cause) {
|
||||
public void moduleReplaced(FridaProcess proc, FridaModuleInfo info, int index,
|
||||
FridaCause cause) {
|
||||
FridaModule module = info.getModule(index);
|
||||
changeElements(List.of(), List.of(getTargetModule(module)), Map.of(), "Replaced");
|
||||
FridaModelTargetModule targetModule = getTargetModule(module);
|
||||
@ -116,14 +104,15 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moduleUnloaded(FridaProcess proc, FridaModuleInfo info, int index, FridaCause cause) {
|
||||
public void moduleUnloaded(FridaProcess proc, FridaModuleInfo info, int index,
|
||||
FridaCause cause) {
|
||||
FridaModelTargetModule targetModule = getTargetModule(info.getModule(index));
|
||||
if (targetModule != null) {
|
||||
FridaThread thread = getManager().getCurrentThread();
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(thread);
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
|
||||
FridaModelImpl impl = (FridaModelImpl) model;
|
||||
impl.deleteModelObject(targetModule.getModule());
|
||||
}
|
||||
@ -143,7 +132,7 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (refresh) {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
return getManager().listModules(session.getProcess());
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public class FridaModelTargetProcessContainerImpl extends FridaModelTargetObject
|
||||
FridaModelTargetProcess process = getTargetProcess(proc);
|
||||
changeElements(List.of(), List.of(process), Map.of(), "Added");
|
||||
process.processStarted(proc);
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
"Process " + FridaClient.getId(proc) + " started " + process.getName(),
|
||||
List.of(process));
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ public class FridaModelTargetProcessImpl extends FridaModelTargetObjectImpl
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
|
||||
EXIT_CODE_ATTRIBUTE_NAME, exitDesc //
|
||||
), "Exited");
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
"Process " + FridaClient.getId(getProcess()) + " exited code=" + exitDesc,
|
||||
List.of(getProxy()));
|
||||
}
|
||||
|
@ -16,42 +16,31 @@
|
||||
package agent.frida.model.impl;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import agent.frida.manager.FridaReason;
|
||||
import agent.frida.manager.FridaState;
|
||||
import agent.frida.manager.FridaValue;
|
||||
import agent.frida.model.iface2.FridaModelTargetRegister;
|
||||
import agent.frida.model.iface2.FridaModelTargetRegisterBank;
|
||||
import agent.frida.model.iface2.FridaModelTargetRegisterContainerAndBank;
|
||||
import agent.frida.manager.*;
|
||||
import agent.frida.model.iface2.*;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.error.DebuggerRegisterAccessException;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetElementType;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "RegisterContainer",
|
||||
attributeResync = ResyncMode.ALWAYS,
|
||||
elements = { //
|
||||
@TargetElementType(type = FridaModelTargetRegisterImpl.class) //
|
||||
@TargetElementType(type = FridaModelTargetRegisterImpl.class) //
|
||||
},
|
||||
attributes = {
|
||||
@TargetAttributeType(
|
||||
name = TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME,
|
||||
type = FridaModelTargetRegisterContainerImpl.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
@TargetAttributeType(
|
||||
name = TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME,
|
||||
type = FridaModelTargetRegisterContainerImpl.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
public class FridaModelTargetRegisterContainerImpl
|
||||
@ -64,10 +53,10 @@ public class FridaModelTargetRegisterContainerImpl
|
||||
public FridaModelTargetRegisterContainerImpl(FridaModelTargetThreadImpl thread) {
|
||||
super(thread.getModel(), thread, NAME, "RegisterContainer");
|
||||
this.thread = thread;
|
||||
|
||||
|
||||
changeAttributes(List.of(), List.of(), Map.of(
|
||||
DISPLAY_ATTRIBUTE_NAME, getName(),
|
||||
DESCRIPTIONS_ATTRIBUTE_NAME, this), "Initialized");
|
||||
DISPLAY_ATTRIBUTE_NAME, getName(),
|
||||
DESCRIPTIONS_ATTRIBUTE_NAME, this), "Initialized");
|
||||
|
||||
requestElements(false);
|
||||
}
|
||||
@ -78,7 +67,7 @@ public class FridaModelTargetRegisterContainerImpl
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (refresh) {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
return getManager().listRegisters(thread.getThread()).thenAccept(registers -> {
|
||||
List<TargetObject> targetRegisters;
|
||||
@ -114,7 +103,8 @@ public class FridaModelTargetRegisterContainerImpl
|
||||
requestAttributes(false).thenAccept(__ -> {
|
||||
for (Object attribute : getCachedAttributes().values()) {
|
||||
if (attribute instanceof FridaModelTargetRegisterBank) {
|
||||
FridaModelTargetRegisterBank bank = (FridaModelTargetRegisterBank) attribute;
|
||||
FridaModelTargetRegisterBank bank =
|
||||
(FridaModelTargetRegisterBank) attribute;
|
||||
bank.threadStateChangedSpecific(state, reason);
|
||||
}
|
||||
}
|
||||
@ -136,12 +126,9 @@ public class FridaModelTargetRegisterContainerImpl
|
||||
byte[] bytes = register.getBytes();
|
||||
result.put(regname, bytes);
|
||||
}
|
||||
ListenerSet<DebuggerModelListener> ls = getListeners();
|
||||
if (ls != null) {
|
||||
//if (getName().contains("General")) {
|
||||
ls.fire.registersUpdated(this, result);
|
||||
//}
|
||||
}
|
||||
//if (getName().contains("General")) {
|
||||
broadcast().registersUpdated(this, result);
|
||||
//}
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
@ -158,7 +145,7 @@ public class FridaModelTargetRegisterContainerImpl
|
||||
BigInteger val = new BigInteger(1, ent.getValue());
|
||||
reg.getRegister().setValue(val.toString());
|
||||
}
|
||||
getListeners().fire.registersUpdated(getProxy(), values);
|
||||
broadcast().registersUpdated(getProxy(), values);
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
|
||||
changeElements(List.of(), List.of(targetThread), Map.of(), "Created");
|
||||
targetThread.threadStateChangedSpecific(FridaState.FRIDA_THREAD_UNINTERRUPTIBLE,
|
||||
FridaReason.getReason(null));
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
"Thread " + FridaClient.getId(thread) + " started", List.of(targetThread));
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
|
||||
String threadId = FridaModelTargetThreadImpl.indexThread(thread);
|
||||
FridaModelTargetThread targetThread = (FridaModelTargetThread) getMapObject(thread);
|
||||
if (targetThread != null) {
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
}
|
||||
changeElements(List.of( //
|
||||
@ -108,7 +108,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
|
||||
FridaReason reason) {
|
||||
FridaModelTargetThread targetThread = getTargetThread(thread);
|
||||
TargetEventType eventType = getEventType(state, cause, reason);
|
||||
getListeners().fire.event(getProxy(), targetThread, eventType,
|
||||
broadcast().event(getProxy(), targetThread, eventType,
|
||||
"Thread " + FridaClient.getId(thread) + " state changed", List.of(targetThread));
|
||||
targetThread.threadStateChangedSpecific(state, reason);
|
||||
}
|
||||
@ -133,7 +133,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (refresh) {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
return getManager().listThreads(process);
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ public class GdbModelTargetBreakpointContainer
|
||||
spec + " (pc=" + frame.getProgramCounter() + ")");
|
||||
//return; // Not ideal, but eb == null should be fine, since the spec holds the actions
|
||||
}
|
||||
listeners.fire.breakpointHit(this, frame.thread, frame, spec, loc);
|
||||
broadcast().breakpointHit(this, frame.thread, frame, spec, loc);
|
||||
spec.breakpointHit(frame, loc);
|
||||
return loc;
|
||||
}
|
||||
|
@ -186,7 +186,6 @@ public class GdbModelTargetInferior
|
||||
return impl.gateFuture(inferior.cont());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> step(TargetStepKind kind) {
|
||||
switch (kind) {
|
||||
@ -210,7 +209,7 @@ public class GdbModelTargetInferior
|
||||
public CompletableFuture<Void> interrupt() {
|
||||
return impl.session.interrupt();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> attach(TargetAttachable attachable) {
|
||||
GdbModelTargetAttachable mine = impl.assertMine(GdbModelTargetAttachable.class, attachable);
|
||||
@ -233,7 +232,7 @@ public class GdbModelTargetInferior
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> inferiorStarted(Long pid) {
|
||||
parent.getListeners().fire.event(parent, null, TargetEventType.PROCESS_CREATED,
|
||||
broadcast().event(parent, null, TargetEventType.PROCESS_CREATED,
|
||||
"Inferior " + inferior.getId() + " started " + inferior.getExecutable() + " pid=" + pid,
|
||||
List.of(this));
|
||||
/*System.err.println("inferiorStarted: realState = " + realState);
|
||||
@ -315,28 +314,28 @@ public class GdbModelTargetInferior
|
||||
params.add(loc);
|
||||
}
|
||||
gatherThreads(params, sco.getAffectedThreads());
|
||||
impl.session.getListeners().fire.event(impl.session, targetEventThread,
|
||||
TargetEventType.BREAKPOINT_HIT, bpHit.desc(), params);
|
||||
broadcast().event(impl.session, targetEventThread, TargetEventType.BREAKPOINT_HIT,
|
||||
bpHit.desc(), params);
|
||||
}
|
||||
else if (reason instanceof GdbEndSteppingRangeReason) {
|
||||
List<Object> params = new ArrayList<>();
|
||||
gatherThreads(params, sco.getAffectedThreads());
|
||||
impl.session.getListeners().fire.event(impl.session, targetEventThread,
|
||||
TargetEventType.STEP_COMPLETED, reason.desc(), params);
|
||||
broadcast().event(impl.session, targetEventThread, TargetEventType.STEP_COMPLETED,
|
||||
reason.desc(), params);
|
||||
}
|
||||
else if (reason instanceof GdbSignalReceivedReason) {
|
||||
GdbSignalReceivedReason signal = (GdbSignalReceivedReason) reason;
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(signal.getSignalName());
|
||||
gatherThreads(params, sco.getAffectedThreads());
|
||||
impl.session.getListeners().fire.event(impl.session, targetEventThread,
|
||||
TargetEventType.SIGNAL, reason.desc(), params);
|
||||
broadcast().event(impl.session, targetEventThread, TargetEventType.SIGNAL,
|
||||
reason.desc(), params);
|
||||
}
|
||||
else {
|
||||
List<Object> params = new ArrayList<>();
|
||||
gatherThreads(params, sco.getAffectedThreads());
|
||||
impl.session.getListeners().fire.event(impl.session, targetEventThread,
|
||||
TargetEventType.STOPPED, reason.desc(), params);
|
||||
broadcast().event(impl.session, targetEventThread, TargetEventType.STOPPED,
|
||||
reason.desc(), params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,8 +444,8 @@ public class GdbModelTargetInferior
|
||||
threads.getTargetThread(sco.getAffectedThreads().iterator().next());
|
||||
}
|
||||
if (targetEventThread != null) {
|
||||
impl.session.getListeners().fire.event(impl.session, targetEventThread,
|
||||
TargetEventType.RUNNING, "Running", params);
|
||||
broadcast().event(impl.session, targetEventThread, TargetEventType.RUNNING,
|
||||
"Running", params);
|
||||
invalidateMemoryAndRegisterCaches();
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public class GdbModelTargetInferiorContainer
|
||||
@Override
|
||||
public void inferiorExited(GdbInferior inf, GdbCause cause) {
|
||||
GdbModelTargetInferior inferior = getTargetInferior(inf);
|
||||
parent.getListeners().fire.event(parent, null, TargetEventType.PROCESS_EXITED,
|
||||
broadcast().event(parent, null, TargetEventType.PROCESS_EXITED,
|
||||
"Inferior " + inf.getId() + " exited code=" + inf.getExitCode(), List.of(inferior));
|
||||
inferior.inferiorExited(inf.getExitCode());
|
||||
}
|
||||
@ -89,7 +89,7 @@ public class GdbModelTargetInferiorContainer
|
||||
public void threadCreated(GdbThread thread, GdbCause cause) {
|
||||
GdbModelTargetInferior inferior = getTargetInferior(thread.getInferior());
|
||||
GdbModelTargetThread targetThread = inferior.threads.threadCreated(thread);
|
||||
parent.getListeners().fire.event(parent, targetThread, TargetEventType.THREAD_CREATED,
|
||||
broadcast().event(parent, targetThread, TargetEventType.THREAD_CREATED,
|
||||
"Thread " + thread.getId() + " started", List.of(targetThread));
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ public class GdbModelTargetInferiorContainer
|
||||
GdbModelTargetInferior inferior = getTargetInferior(inf);
|
||||
GdbModelTargetThread targetThread =
|
||||
inferior.threads.getCachedElements().get(GdbModelTargetThread.indexThread(threadId));
|
||||
parent.getListeners().fire.event(parent, targetThread, TargetEventType.THREAD_EXITED,
|
||||
broadcast().event(parent, targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
inferior.threads.threadExited(threadId);
|
||||
}
|
||||
@ -107,7 +107,7 @@ public class GdbModelTargetInferiorContainer
|
||||
public void libraryLoaded(GdbInferior inf, String name, GdbCause cause) {
|
||||
GdbModelTargetInferior inferior = getTargetInferior(inf);
|
||||
GdbModelTargetModule module = inferior.modules.libraryLoaded(name);
|
||||
parent.getListeners().fire.event(parent, null, TargetEventType.MODULE_LOADED,
|
||||
broadcast().event(parent, null, TargetEventType.MODULE_LOADED,
|
||||
"Library " + name + " loaded", List.of(module));
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ public class GdbModelTargetInferiorContainer
|
||||
public void libraryUnloaded(GdbInferior inf, String name, GdbCause cause) {
|
||||
GdbModelTargetInferior inferior = getTargetInferior(inf);
|
||||
GdbModelTargetModule module = inferior.modules.getTargetModuleIfPresent(name);
|
||||
parent.getListeners().fire.event(parent, null, TargetEventType.MODULE_UNLOADED,
|
||||
broadcast().event(parent, null, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + name + " unloaded", List.of(module));
|
||||
inferior.modules.libraryUnloaded(name);
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ public class GdbModelTargetProcessMemory
|
||||
throw new DebuggerMemoryAccessException("Cannot read at " + address);
|
||||
}
|
||||
byte[] content = Arrays.copyOf(buf.array(), (int) s.length());
|
||||
listeners.fire.memoryUpdated(this, address, content);
|
||||
broadcast().memoryUpdated(this, address, content);
|
||||
return content;
|
||||
}).exceptionally(e -> {
|
||||
e = AsyncUtils.unwrapThrowable(e);
|
||||
@ -167,10 +167,10 @@ public class GdbModelTargetProcessMemory
|
||||
GdbCommandError gce = (GdbCommandError) e;
|
||||
e = new DebuggerMemoryAccessException(
|
||||
"Cannot read at " + address + ": " + gce.getInfo().getString("msg"));
|
||||
listeners.fire.memoryReadError(this, range, (DebuggerMemoryAccessException) e);
|
||||
broadcast().memoryReadError(this, range, (DebuggerMemoryAccessException) e);
|
||||
}
|
||||
if (e instanceof DebuggerMemoryAccessException) {
|
||||
listeners.fire.memoryReadError(this, range, (DebuggerMemoryAccessException) e);
|
||||
broadcast().memoryReadError(this, range, (DebuggerMemoryAccessException) e);
|
||||
}
|
||||
return ExceptionUtils.rethrow(e);
|
||||
});
|
||||
@ -186,12 +186,12 @@ public class GdbModelTargetProcessMemory
|
||||
CompletableFuture<Void> future =
|
||||
inferior.writeMemory(address.getOffset(), ByteBuffer.wrap(data));
|
||||
return impl.gateFuture(future.thenAccept(__ -> {
|
||||
listeners.fire.memoryUpdated(this, address, data);
|
||||
broadcast().memoryUpdated(this, address, data);
|
||||
}));
|
||||
}
|
||||
|
||||
protected void invalidateMemoryCaches() {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
|
||||
public void memoryChanged(long offset, int len) {
|
||||
|
@ -147,7 +147,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
listeners.fire.consoleOutput(this, dbgChannel, out);
|
||||
broadcast().consoleOutput(this, dbgChannel, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,7 +110,7 @@ public class GdbModelTargetStackFrame
|
||||
}
|
||||
|
||||
protected void invalidateRegisterCaches() {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -117,7 +117,7 @@ public class GdbModelTargetStackFrameRegisterContainer
|
||||
elements.get(regName).stateChanged(bytes);
|
||||
}
|
||||
this.regValues = result;
|
||||
listeners.fire.registersUpdated(this, result);
|
||||
broadcast().registersUpdated(this, result);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
@ -22,11 +22,9 @@ import java.util.concurrent.CompletableFuture;
|
||||
import agent.lldb.manager.impl.LldbManagerImpl;
|
||||
import agent.lldb.model.AbstractLldbModel;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.SpiTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public interface LldbModelTargetObject extends SpiTargetObject {
|
||||
|
||||
@ -59,8 +57,6 @@ public interface LldbModelTargetObject extends SpiTargetObject {
|
||||
|
||||
public CompletableFuture<List<TargetObject>> requestNativeElements();
|
||||
|
||||
public ListenerSet<DebuggerModelListener> getListeners();
|
||||
|
||||
public LldbModelTargetSession getParentSession();
|
||||
|
||||
public LldbModelTargetProcess getParentProcess();
|
||||
|
@ -53,7 +53,7 @@ public interface LldbModelTargetSession extends //
|
||||
if (output.contains("loaded *kernel* extension dll for usermode")) {
|
||||
return;
|
||||
}
|
||||
getListeners().fire.consoleOutput(getProxy(), chan, output);
|
||||
broadcast().consoleOutput(getProxy(), chan, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,8 +47,7 @@ public class LldbModelTargetBreakpointContainerImpl extends LldbModelTargetObjec
|
||||
TargetBreakpointKind.SW_EXECUTE,
|
||||
//TargetBreakpointKind.HW_EXECUTE,
|
||||
TargetBreakpointKind.READ,
|
||||
TargetBreakpointKind.WRITE
|
||||
);
|
||||
TargetBreakpointKind.WRITE);
|
||||
|
||||
private final SBTarget session;
|
||||
|
||||
@ -98,22 +97,25 @@ public class LldbModelTargetBreakpointContainerImpl extends LldbModelTargetObjec
|
||||
BigInteger bptId = t.GetStopReasonDataAtIndex(0);
|
||||
BigInteger locId = t.GetStopReasonDataAtIndex(1);
|
||||
if (bpt.GetID() == bptId.intValue()) {
|
||||
LldbModelTargetProcess targetProcess = (LldbModelTargetProcess) getModel().getModelObject(eventProcess);
|
||||
LldbModelTargetProcess targetProcess =
|
||||
(LldbModelTargetProcess) getModel().getModelObject(eventProcess);
|
||||
LldbModelTargetThread targetThread =
|
||||
targetProcess.getThreads().getTargetThread(t);
|
||||
targetProcess.getThreads().getTargetThread(t);
|
||||
LldbModelTargetBreakpointSpec spec = getTargetBreakpointSpec(bpt);
|
||||
if (spec == null) {
|
||||
Msg.error(this, "Stopped for breakpoint unknown to the agent: " + bpt + " (pc=" +
|
||||
targetThread + ")");
|
||||
Msg.error(this,
|
||||
"Stopped for breakpoint unknown to the agent: " + bpt + " (pc=" +
|
||||
targetThread + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
LldbModelTargetBreakpointLocation loc = spec.findLocation(locId);
|
||||
if (loc == null) {
|
||||
Msg.warn(this,
|
||||
"Stopped for a breakpoint whose location is unknown to the agent: " + spec);
|
||||
"Stopped for a breakpoint whose location is unknown to the agent: " +
|
||||
spec);
|
||||
}
|
||||
listeners.fire.breakpointHit(this, targetThread, null, spec, loc);
|
||||
broadcast().breakpointHit(this, targetThread, null, spec, loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,6 +150,7 @@ public class LldbModelTargetBreakpointContainerImpl extends LldbModelTargetObjec
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public SBTarget getSession() {
|
||||
return session;
|
||||
}
|
||||
|
@ -87,12 +87,12 @@ public class LldbModelTargetMemoryContainerImpl extends LldbModelTargetObjectImp
|
||||
if (range == null) {
|
||||
throw new DebuggerMemoryAccessException("Cannot read at " + address);
|
||||
}
|
||||
listeners.fire.memoryUpdated(getProxy(), address, buf.array());
|
||||
broadcast().memoryUpdated(getProxy(), address, buf.array());
|
||||
return Arrays.copyOf(buf.array(), (int) range.getLength());
|
||||
}
|
||||
|
||||
private void writeAssist(Address address, byte[] data) {
|
||||
listeners.fire.memoryUpdated(getProxy(), address, data);
|
||||
broadcast().memoryUpdated(getProxy(), address, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,8 +72,8 @@ public class LldbModelTargetModuleContainerImpl extends LldbModelTargetObjectImp
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(thread);
|
||||
changeElements(List.of(), List.of(targetModule), Map.of(), "Loaded");
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
|
||||
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,8 +84,8 @@ public class LldbModelTargetModuleContainerImpl extends LldbModelTargetObjectImp
|
||||
SBThread thread = getManager().getEventThread();
|
||||
TargetThread eventThread =
|
||||
(TargetThread) getModel().getModelObject(thread);
|
||||
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
|
||||
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
|
||||
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
|
||||
LldbModelImpl impl = (LldbModelImpl) model;
|
||||
impl.deleteModelObject(targetModule.getModule());
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public class LldbModelTargetProcessContainerImpl extends LldbModelTargetObjectIm
|
||||
LldbModelTargetProcess process = getTargetProcess(proc);
|
||||
changeElements(List.of(), List.of(process), Map.of(), "Added");
|
||||
process.processStarted(proc);
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_CREATED,
|
||||
"Process " + DebugClient.getId(proc) + " started " + process.getName(),
|
||||
List.of(process));
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ public class LldbModelTargetProcessImpl extends LldbModelTargetObjectImpl
|
||||
threads.requestElements(true);
|
||||
StopReason stopReason = getManager().getCurrentThread().GetStopReason();
|
||||
if (!stopReason.equals(StopReason.eStopReasonPlanComplete)) {
|
||||
memory.requestElements(true);
|
||||
memory.requestElements(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,7 +245,7 @@ public class LldbModelTargetProcessImpl extends LldbModelTargetObjectImpl
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
|
||||
EXIT_CODE_ATTRIBUTE_NAME, exitDesc //
|
||||
), "Exited");
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
broadcast().event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
"Process " + DebugClient.getId(getProcess()) + " exited code=" + exitDesc,
|
||||
List.of(getProxy()));
|
||||
}
|
||||
|
@ -25,13 +25,11 @@ import agent.lldb.manager.LldbReason;
|
||||
import agent.lldb.model.iface2.LldbModelTargetRegister;
|
||||
import agent.lldb.model.iface2.LldbModelTargetStackFrameRegisterBank;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.error.DebuggerRegisterAccessException;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "RegisterValueBank",
|
||||
@ -66,6 +64,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl
|
||||
requestElements(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(int level) {
|
||||
SBStream stream = new SBStream();
|
||||
SBValue val = (SBValue) getModelObject();
|
||||
@ -105,6 +104,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl
|
||||
return new LldbModelTargetStackFrameRegisterImpl(this, register);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadStateChangedSpecific(StateType state, LldbReason reason) {
|
||||
if (state.equals(StateType.eStateStopped)) {
|
||||
requestElements(false).thenAccept(__ -> {
|
||||
@ -127,12 +127,9 @@ public class LldbModelTargetStackFrameRegisterBankImpl
|
||||
byte[] bytes = register.getBytes();
|
||||
result.put(regname, bytes);
|
||||
}
|
||||
ListenerSet<DebuggerModelListener> listeners = getListeners();
|
||||
if (listeners != null) {
|
||||
//if (getName().contains("General")) {
|
||||
listeners.fire.registersUpdated(this, result);
|
||||
//}
|
||||
}
|
||||
//if (getName().contains("General")) {
|
||||
broadcast().registersUpdated(this, result);
|
||||
//}
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
@ -149,7 +146,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl
|
||||
BigInteger val = new BigInteger(1, ent.getValue());
|
||||
reg.getRegister().SetValueFromCString(val.toString());
|
||||
}
|
||||
getListeners().fire.registersUpdated(getProxy(), values);
|
||||
broadcast().registersUpdated(getProxy(), values);
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp
|
||||
changeElements(List.of(), List.of(targetThread), Map.of(), "Created");
|
||||
targetThread.threadStateChangedSpecific(StateType.eStateConnected,
|
||||
LldbReason.getReason(null));
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
|
||||
"Thread " + DebugClient.getId(thread) + " started", List.of(targetThread));
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp
|
||||
LldbReason reason) {
|
||||
LldbModelTargetThread targetThread = getTargetThread(thread);
|
||||
TargetEventType eventType = getEventType(state, cause, reason);
|
||||
getListeners().fire.event(getProxy(), targetThread, eventType,
|
||||
broadcast().event(getProxy(), targetThread, eventType,
|
||||
"Thread " + DebugClient.getId(thread) + " state changed", List.of(targetThread));
|
||||
targetThread.threadStateChangedSpecific(state, reason);
|
||||
}
|
||||
@ -94,7 +94,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp
|
||||
String threadId = LldbModelTargetThreadImpl.indexThread(thread);
|
||||
LldbModelTargetThread targetThread = (LldbModelTargetThread) getMapObject(thread);
|
||||
if (targetThread != null) {
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
}
|
||||
changeElements(List.of( //
|
||||
|
@ -71,7 +71,7 @@ public interface GadpClientTargetBreakpointSpecContainer
|
||||
Path bptPath = evt.getEffective();
|
||||
TargetBreakpointLocation breakpoint = bptPath == null ? null
|
||||
: getModel().getProxy(bptPath.getEList(), true).as(TargetBreakpointLocation.class);
|
||||
getDelegate().getListeners().fire.breakpointHit(this, trapped, frame, spec, breakpoint);
|
||||
broadcast().breakpointHit(this, trapped, frame, spec, breakpoint);
|
||||
if (spec instanceof GadpClientTargetBreakpointSpec) {
|
||||
// If I don't have a cached proxy, then I don't have any listeners
|
||||
GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec;
|
||||
|
@ -33,6 +33,6 @@ public interface GadpClientTargetEventScope extends GadpClientTargetObject, Targ
|
||||
String description = evt.getDescription();
|
||||
List<Object> parameters =
|
||||
GadpValueUtils.getValues(getModel(), evt.getParametersList());
|
||||
getDelegate().getListeners().fire.event(this, thread, type, description, parameters);
|
||||
broadcast().event(this, thread, type, description, parameters);
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ public interface GadpClientTargetMemory extends GadpClientTargetObject, TargetMe
|
||||
byte[] data = evt.getContent().toByteArray();
|
||||
DelegateGadpClientTargetObject delegate = getDelegate();
|
||||
delegate.getMemoryCache(address.getAddressSpace()).updateMemory(address.getOffset(), data);
|
||||
delegate.getListeners().fire.memoryUpdated(this, address, data);
|
||||
broadcast().memoryUpdated(this, address, data);
|
||||
}
|
||||
|
||||
@GadpEventHandler(Gadp.EventNotification.EvtCase.MEMORY_ERROR_EVENT)
|
||||
@ -98,7 +98,6 @@ public interface GadpClientTargetMemory extends GadpClientTargetObject, TargetMe
|
||||
AddressRange range = GadpValueUtils.getAddressRange(getModel(), evt.getRange());
|
||||
String message = evt.getMessage();
|
||||
// Errors are not cached, but recorded in trace
|
||||
getDelegate().getListeners().fire.memoryReadError(this, range,
|
||||
new DebuggerMemoryAccessException(message));
|
||||
broadcast().memoryReadError(this, range, new DebuggerMemoryAccessException(message));
|
||||
}
|
||||
}
|
||||
|
@ -56,8 +56,7 @@ public interface GadpClientTargetObject extends SpiTargetObject {
|
||||
int channelIndex = evt.getChannel();
|
||||
Channel[] allChannels = Channel.values();
|
||||
if (0 <= channelIndex && channelIndex < allChannels.length) {
|
||||
getDelegate().getListeners().fire.consoleOutput(this, allChannels[channelIndex],
|
||||
evt.getData().toByteArray());
|
||||
broadcast().consoleOutput(this, allChannels[channelIndex], evt.getData().toByteArray());
|
||||
}
|
||||
else {
|
||||
Msg.error(this, "Received output for unknown channel " + channelIndex + ": " +
|
||||
|
@ -82,6 +82,6 @@ public interface GadpClientTargetRegisterBank extends GadpClientTargetObject, Ta
|
||||
Map<String, byte[]> updates = GadpValueUtils.getRegisterValueMap(evt.getValueList());
|
||||
DelegateGadpClientTargetObject delegate = getDelegate();
|
||||
delegate.getRegisterCache().putAll(updates);
|
||||
delegate.getListeners().fire.registersUpdated(this, updates);
|
||||
broadcast().registersUpdated(this, updates);
|
||||
}
|
||||
}
|
||||
|
@ -926,6 +926,9 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
|
||||
@Test
|
||||
public void testGetAvailableWithObjectGettingListener() throws Throwable {
|
||||
var l = new Object() {
|
||||
TargetObject avail;
|
||||
};
|
||||
List<ElementsChangedInvocation> invocations = new ArrayList<>();
|
||||
// Any listener which calls .get on a child ref would do....
|
||||
// This object-getting listener is the pattern that revealed this problem, though.
|
||||
@ -933,18 +936,21 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
@Override
|
||||
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
if (parent != l.avail) {
|
||||
return;
|
||||
}
|
||||
invocations.add(new ElementsChangedInvocation(parent, removed, added));
|
||||
}
|
||||
};
|
||||
AsynchronousSocketChannel socket = socketChannel();
|
||||
try (ServerRunner runner = new ServerRunner()) {
|
||||
GadpClient client = new GadpClient("Test", socket);
|
||||
client.addModelListener(listener);
|
||||
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
|
||||
runner.server.getLocalAddress()));
|
||||
waitOn(client.connect());
|
||||
TargetObject avail = waitOn(client.fetchModelObject(List.of("Available")));
|
||||
avail.addListener(listener);
|
||||
Map<String, ? extends TargetObject> elements = waitOn(avail.fetchElements());
|
||||
l.avail = waitOn(client.fetchModelObject(List.of("Available")));
|
||||
Map<String, ? extends TargetObject> elements = waitOn(l.avail.fetchElements());
|
||||
Msg.debug(this, "Elements: " + elements);
|
||||
waitOn(client.close());
|
||||
}
|
||||
@ -957,13 +963,18 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
public void testFocus() throws Throwable {
|
||||
// Interesting because it involves a non-object-valued attribute (link)
|
||||
// Need to check callback as well as getAttributes
|
||||
|
||||
var l = new Object() {
|
||||
TargetObject session;
|
||||
};
|
||||
CompletableFuture<List<String>> focusPath = new CompletableFuture<>();
|
||||
AtomicBoolean failed = new AtomicBoolean();
|
||||
DebuggerModelListener focusListener =
|
||||
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
|
||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||
public void focusChanged(TargetObject object, TargetObject focused) {
|
||||
if (object != l.session) {
|
||||
return;
|
||||
}
|
||||
Msg.info(this, "Focus changed to " + focused);
|
||||
if (!focusPath.complete(focused.getPath())) {
|
||||
failed.set(true);
|
||||
@ -976,8 +987,7 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
|
||||
runner.server.getLocalAddress()));
|
||||
waitOn(client.connect());
|
||||
TargetObject session = waitOn(client.fetchModelObject(List.of()));
|
||||
session.addListener(focusListener);
|
||||
l.session = waitOn(client.fetchModelObject(List.of()));
|
||||
TargetObject procCont = waitOn(client.fetchModelObject(List.of("Processes")));
|
||||
assertTrue(procCont.getInterfaceNames().contains("Launcher"));
|
||||
TargetLauncher launcher = procCont.as(TargetLauncher.class);
|
||||
@ -1017,7 +1027,19 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
|
||||
@Test
|
||||
public void testSubscribeLaunchForChildrenChanged() throws Throwable {
|
||||
ElementsChangedListener elemL = new ElementsChangedListener();
|
||||
var l = new Object() {
|
||||
TargetObject procContainer;
|
||||
};
|
||||
ElementsChangedListener elemL = new ElementsChangedListener() {
|
||||
@Override
|
||||
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
if (parent != l.procContainer) {
|
||||
return;
|
||||
}
|
||||
super.elementsChanged(parent, removed, added);
|
||||
}
|
||||
};
|
||||
|
||||
AsynchronousSocketChannel socket = socketChannel();
|
||||
try (ServerRunner runner = new ServerRunner()) {
|
||||
@ -1026,10 +1048,9 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
|
||||
runner.server.getLocalAddress()));
|
||||
waitOn(client.connect());
|
||||
TargetObject procContainer = waitOn(client.fetchModelObject(List.of("Processes")));
|
||||
assertTrue(procContainer.getInterfaceNames().contains("Launcher"));
|
||||
procContainer.addListener(elemL);
|
||||
TargetLauncher launcher = procContainer.as(TargetLauncher.class);
|
||||
l.procContainer = waitOn(client.fetchModelObject(List.of("Processes")));
|
||||
assertTrue(l.procContainer.getInterfaceNames().contains("Launcher"));
|
||||
TargetLauncher launcher = l.procContainer.as(TargetLauncher.class);
|
||||
waitOn(launcher.launch(
|
||||
Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, "/bin/echo Hello, World!")));
|
||||
waitOn(elemL.count.waitValue(1));
|
||||
@ -1038,7 +1059,7 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
|
||||
assertEquals(1, elemL.invocations.size());
|
||||
ElementsChangedInvocation eci = elemL.invocations.get(0);
|
||||
assertEquals(procContainer, eci.parent);
|
||||
assertEquals(l.procContainer, eci.parent);
|
||||
assertEquals(List.of(), List.copyOf(eci.removed));
|
||||
assertEquals(1, eci.added.size());
|
||||
Entry<String, ? extends TargetObject> ent = eci.added.entrySet().iterator().next();
|
||||
@ -1107,20 +1128,31 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
|
||||
@Test
|
||||
public void testReplaceAttribute() throws Throwable {
|
||||
AttributesChangedListener attrL = new AttributesChangedListener();
|
||||
var l = new Object() {
|
||||
TargetObject echoAvail;
|
||||
};
|
||||
AttributesChangedListener attrL = new AttributesChangedListener() {
|
||||
@Override
|
||||
public void attributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
if (parent != l.echoAvail) {
|
||||
return;
|
||||
}
|
||||
super.attributesChanged(parent, removed, added);
|
||||
}
|
||||
};
|
||||
|
||||
try (AsynchronousSocketChannel socket = socketChannel();
|
||||
ServerRunner runner = new ServerRunner()) {
|
||||
GadpClient client = new GadpClient("Test", socket);
|
||||
client.addModelListener(attrL);
|
||||
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
|
||||
runner.server.getLocalAddress()));
|
||||
waitOn(client.connect());
|
||||
|
||||
TargetObject echoAvail =
|
||||
waitOn(client.fetchModelObject(PathUtils.parse("Available[1]")));
|
||||
echoAvail.addListener(attrL);
|
||||
l.echoAvail = waitOn(client.fetchModelObject(PathUtils.parse("Available[1]")));
|
||||
assertEquals(Map.ofEntries(Map.entry("pid", 1), Map.entry("cmd", "echo"),
|
||||
Map.entry("_display", "[1]")), waitOn(echoAvail.fetchAttributes()));
|
||||
Map.entry("_display", "[1]")), waitOn(l.echoAvail.fetchAttributes()));
|
||||
|
||||
TestGadpTargetAvailable ssEchoAvail =
|
||||
runner.server.model.session.available.getCachedElements().get("1");
|
||||
@ -1132,10 +1164,10 @@ public class GadpClientServerTest implements AsyncTestUtils {
|
||||
waitOn(attrL.count.waitValue(1));
|
||||
|
||||
assertEquals(Map.ofEntries(Map.entry("cmd", "echo"), Map.entry("args", "Hello, World!"),
|
||||
Map.entry("_display", "[1]")), echoAvail.getCachedAttributes());
|
||||
Map.entry("_display", "[1]")), l.echoAvail.getCachedAttributes());
|
||||
|
||||
AttributesChangedInvocation changed = Unique.assertOne(attrL.invocations);
|
||||
assertSame(echoAvail, changed.parent);
|
||||
assertSame(l.echoAvail, changed.parent);
|
||||
assertEquals(Set.of("pid"), Set.copyOf(changed.removed));
|
||||
assertEquals(Map.ofEntries(Map.entry("args", "Hello, World!")), changed.added);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import java.util.stream.Collectors;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@ -90,9 +89,6 @@ public class JdiModelTargetClassContainer extends JdiModelTargetObjectImpl {
|
||||
}
|
||||
|
||||
public CompletableFuture<?> refreshInternal() {
|
||||
if (!isObserved()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return doRefresh().exceptionally(ex -> {
|
||||
Msg.error(this, "Problem refreshing vm's classes", ex);
|
||||
return null;
|
||||
|
@ -23,7 +23,6 @@ import com.sun.jdi.VirtualMachineManager;
|
||||
import com.sun.jdi.connect.Connector;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@ -96,9 +95,6 @@ public class JdiModelTargetConnectorContainer extends JdiModelTargetObjectImpl {
|
||||
}
|
||||
|
||||
public CompletableFuture<?> refreshInternal() {
|
||||
if (!isObserved()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return doRefresh().exceptionally(ex -> {
|
||||
Msg.error(this, "Problem refreshing inferior's modules", ex);
|
||||
return null;
|
||||
|
@ -22,7 +22,6 @@ import java.util.stream.Collectors;
|
||||
import com.sun.jdi.ModuleReference;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.error.DebuggerUserException;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetModuleContainer;
|
||||
@ -136,9 +135,6 @@ public class JdiModelTargetModuleContainer extends JdiModelTargetObjectImpl
|
||||
}
|
||||
|
||||
public CompletableFuture<?> refreshInternal() {
|
||||
if (!isObserved()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return doRefresh().exceptionally(ex -> {
|
||||
Msg.error(this, "Problem refreshing inferior's modules", ex);
|
||||
return null;
|
||||
|
@ -106,7 +106,7 @@ public class JdiModelTargetProcess extends JdiModelTargetObjectImpl
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
listeners.fire.consoleOutput(this, channel, out);
|
||||
broadcast().consoleOutput(this, channel, out);
|
||||
}
|
||||
|
||||
private void readStream(InputStream in, TargetConsole.Channel channel) {
|
||||
|
@ -20,7 +20,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.sun.jdi.Location;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.dbg.target.TargetRegisterContainer;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
@ -121,7 +120,7 @@ public class JdiModelTargetRegisterContainer extends JdiModelTargetObjectImpl
|
||||
map.put(retAddr.getIndex(), bytes);
|
||||
}
|
||||
if (!map.isEmpty()) {
|
||||
listeners.fire.registersUpdated(this, map);
|
||||
broadcast().registersUpdated(this, map);
|
||||
}
|
||||
return CompletableFuture.completedFuture(map);
|
||||
}
|
||||
@ -133,13 +132,10 @@ public class JdiModelTargetRegisterContainer extends JdiModelTargetObjectImpl
|
||||
}
|
||||
|
||||
public void invalidateRegisterCaches() {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
|
||||
protected CompletableFuture<?> update() {
|
||||
if (!isObserved()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return fetchElements(true).exceptionally(e -> {
|
||||
Msg.error(this, "Could not update registers " + this + " on STOPPED");
|
||||
return null;
|
||||
|
@ -125,12 +125,12 @@ public class JdiModelTargetSectionContainer extends JdiModelTargetObjectImpl
|
||||
bytes[i] = (byte) 0xFF;
|
||||
}
|
||||
}
|
||||
listeners.fire.memoryUpdated(this, address, bytes);
|
||||
broadcast().memoryUpdated(this, address, bytes);
|
||||
return CompletableFuture.completedFuture(bytes);
|
||||
}
|
||||
if (addressSpace.equals(impl.getAddressSpace("constantPool"))) {
|
||||
byte[] bytes = constantPool.getPool();
|
||||
listeners.fire.memoryUpdated(this, address, bytes);
|
||||
broadcast().memoryUpdated(this, address, bytes);
|
||||
return CompletableFuture.completedFuture(bytes);
|
||||
}
|
||||
throw new RuntimeException();
|
||||
|
@ -20,7 +20,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.TargetStack;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.util.Msg;
|
||||
@ -104,9 +103,6 @@ public class JdiModelTargetStack extends JdiModelTargetObjectImpl
|
||||
* @return null
|
||||
*/
|
||||
protected CompletableFuture<?> update() {
|
||||
if (!isObserved()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return fetchElements(true).exceptionally(e -> {
|
||||
Msg.error(this, "Could not update stack " + this + " on STOPPED");
|
||||
return null;
|
||||
@ -114,6 +110,6 @@ public class JdiModelTargetStack extends JdiModelTargetObjectImpl
|
||||
}
|
||||
|
||||
public void invalidateRegisterCaches() {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
broadcast().invalidateCacheRequested(this);
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public class JdiModelTargetThreadContainer extends JdiModelTargetObjectImpl
|
||||
TargetExecutionState targetState = targetThread.convertState(state);
|
||||
targetThread.threadStateChanged(targetState);
|
||||
TargetEventType eventType = getEventType(reason);
|
||||
getListeners().fire.event(this, targetThread, eventType,
|
||||
broadcast().event(this, targetThread, eventType,
|
||||
"Thread " + targetThread.getName() + " state changed", List.of(targetThread));
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,15 @@ import ghidra.dbg.target.schema.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "VMContainer", elements = { //
|
||||
@TargetElementType(type = JdiModelTargetVM.class) //
|
||||
}, attributes = { //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
}, canonicalContainer = true)
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "VMContainer",
|
||||
elements = { //
|
||||
@TargetElementType(type = JdiModelTargetVM.class) //
|
||||
},
|
||||
attributes = { //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
},
|
||||
canonicalContainer = true)
|
||||
public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
|
||||
implements JdiEventsListenerAdapter {
|
||||
|
||||
@ -56,7 +60,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
|
||||
// TODO: Move PROCESS_CREATED here to restore proper order of event reporting
|
||||
// Pending some client-side changes to handle architecture selection, though.
|
||||
target.started(vm.name()).thenAccept(__ -> {
|
||||
session.getListeners().fire.event(session, null, TargetEventType.PROCESS_CREATED,
|
||||
broadcast().event(session, null, TargetEventType.PROCESS_CREATED,
|
||||
"VM " + vm.name() + " started " + vm.process() + " pid=" + vm.name(), List.of(vm));
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not notify vm started", ex);
|
||||
@ -70,8 +74,8 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
|
||||
public void vmDied(VMDeathEvent event, JdiCause cause) {
|
||||
VirtualMachine vm = event.virtualMachine();
|
||||
JdiModelTargetVM tgtVM = vmsById.get(vm.name());
|
||||
session.getListeners().fire.event(session, null, TargetEventType.PROCESS_EXITED,
|
||||
"VM " + vm.name(), List.of(tgtVM));
|
||||
broadcast().event(session, null, TargetEventType.PROCESS_EXITED, "VM " + vm.name(),
|
||||
List.of(tgtVM));
|
||||
tgtVM.exited(vm);
|
||||
synchronized (this) {
|
||||
vmsById.remove(vm.name());
|
||||
@ -99,7 +103,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
|
||||
return;
|
||||
}
|
||||
JdiModelTargetThread targetThread = vm.threads.threadCreated(thread);
|
||||
session.getListeners().fire.event(session, targetThread, TargetEventType.THREAD_CREATED,
|
||||
broadcast().event(session, targetThread, TargetEventType.THREAD_CREATED,
|
||||
"Thread " + thread.name() + " started", List.of(targetThread));
|
||||
}
|
||||
|
||||
@ -108,7 +112,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
|
||||
ThreadReference thread = event.thread();
|
||||
JdiModelTargetVM tgtVM = vmsById.get(thread.virtualMachine().name());
|
||||
JdiModelTargetThread targetThread = tgtVM.threads.threadsById.get(thread.name());
|
||||
session.getListeners().fire.event(session, targetThread, TargetEventType.THREAD_EXITED,
|
||||
broadcast().event(session, targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + thread.name() + " exited", List.of(targetThread));
|
||||
tgtVM.threads.threadExited(thread);
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.InvalidatableTargetObjectIf;
|
||||
import ghidra.dbg.jdi.manager.JdiManager;
|
||||
import ghidra.dbg.jdi.model.*;
|
||||
@ -30,7 +29,6 @@ import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public interface JdiModelTargetObject extends TargetObject, InvalidatableTargetObjectIf {
|
||||
|
||||
@ -56,8 +54,6 @@ public interface JdiModelTargetObject extends TargetObject, InvalidatableTargetO
|
||||
|
||||
public Delta<?, ?> changeAttributes(List<String> remove, Map<String, ?> add, String reason);
|
||||
|
||||
public ListenerSet<DebuggerModelListener> getListeners();
|
||||
|
||||
public default JdiModelTargetObject getInstance(Mirror object) {
|
||||
JdiModelTargetObject targetObject = getTargetObject(object);
|
||||
if (targetObject == null) {
|
||||
|
@ -53,7 +53,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleOutput(TargetObject console, Channel channel, byte[] out) {
|
||||
public void consoleOutput(TargetObject object, Channel channel, byte[] out) {
|
||||
if (object != targetConsole) {
|
||||
return;
|
||||
}
|
||||
OutputStream os;
|
||||
switch (channel) {
|
||||
case STDOUT:
|
||||
@ -79,6 +82,9 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||
|
||||
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
|
||||
public void displayChanged(TargetObject object, String display) {
|
||||
if (object != targetConsole) {
|
||||
return;
|
||||
}
|
||||
// TODO: Add setSubTitle(String) to InterpreterConsole
|
||||
if (guiConsole == null) {
|
||||
/**
|
||||
@ -92,7 +98,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME)
|
||||
public void promptChanged(TargetObject interpreter, String prompt) {
|
||||
public void promptChanged(TargetObject object, String prompt) {
|
||||
if (object != targetConsole) {
|
||||
return;
|
||||
}
|
||||
if (guiConsole == null) {
|
||||
/**
|
||||
* Can happen during init. setPrompt will get called immediately after guiConsole is
|
||||
@ -105,10 +114,11 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
if (object != targetConsole) {
|
||||
return;
|
||||
}
|
||||
Swing.runLater(() -> {
|
||||
if (object == targetConsole) { // Redundant
|
||||
consoleInvalidated();
|
||||
}
|
||||
consoleInvalidated();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -131,7 +141,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||
T targetConsole) {
|
||||
this.plugin = plugin;
|
||||
this.targetConsole = targetConsole;
|
||||
targetConsole.addListener(listener);
|
||||
targetConsole.getModel().addModelListener(listener);
|
||||
}
|
||||
|
||||
protected abstract CompletableFuture<Void> sendLine(String line);
|
||||
@ -188,7 +198,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||
.build();
|
||||
guiConsole.addAction(actionPin);
|
||||
|
||||
DockingAction interruptAction = InterpreterInterruptAction.builder(plugin)
|
||||
DockingAction interruptAction = InterpreterInterruptAction.builder(plugin)
|
||||
.onAction(this::sendInterrupt)
|
||||
.build();
|
||||
guiConsole.addAction(interruptAction);
|
||||
|
@ -20,7 +20,6 @@ import java.awt.Color;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -795,9 +794,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
TargetObject targetObject = getRoot().getTargetObject();
|
||||
if (targetObject != null) {
|
||||
targetObject.removeListener(getListener());
|
||||
DebuggerObjectModel model = getModel();
|
||||
if (model != null) {
|
||||
model.removeModelListener(getListener());
|
||||
}
|
||||
super.closeComponent();
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ 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;
|
||||
|
||||
@ -212,21 +211,12 @@ public class DummyTargetObject implements TargetObject {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(DebuggerModelListener l) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(DebuggerModelListener l) {
|
||||
}
|
||||
|
||||
public String getJoinedPath() {
|
||||
return joinedPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -235,6 +225,7 @@ public class DummyTargetObject implements TargetObject {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
@ -404,7 +404,6 @@ public class ObjectTree implements ObjectPane {
|
||||
DebuggerObjectsProvider provider = getProvider();
|
||||
ObjectContainer oc = node.getContainer();
|
||||
provider.deleteFromMap(oc);
|
||||
oc.getTargetObject().removeListener(provider.getListener());
|
||||
nodeMap.remove(path(node.getContainer()));
|
||||
}
|
||||
|
||||
|
@ -78,90 +78,40 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
// Since used for naming, no ':' allowed.
|
||||
public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd-HH.mm.ss-z");
|
||||
|
||||
protected class ListenersForRemovalAndFocus {
|
||||
protected final DebuggerObjectModel model;
|
||||
protected TargetObject root;
|
||||
protected TargetFocusScope focusScope;
|
||||
protected class ForRemovalAndFocusListener extends AnnotatedDebuggerAttributeListener {
|
||||
public ForRemovalAndFocusListener() {
|
||||
super(MethodHandles.lookup());
|
||||
}
|
||||
|
||||
protected DebuggerModelListener forRemoval = new DebuggerModelListener() {
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
synchronized (listenersByModel) {
|
||||
ListenersForRemovalAndFocus listener = listenersByModel.remove(model);
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
assert listener.forRemoval == this;
|
||||
dispose();
|
||||
}
|
||||
modelListeners.fire.elementRemoved(model);
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
if (!object.isRoot()) {
|
||||
return;
|
||||
}
|
||||
DebuggerObjectModel model = object.getModel();
|
||||
synchronized (models) {
|
||||
models.remove(model);
|
||||
}
|
||||
model.removeModelListener(this);
|
||||
modelListeners.fire.elementRemoved(model);
|
||||
if (currentModel == model) {
|
||||
activateModel(null);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected ListenersForRemovalAndFocus(DebuggerObjectModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> init() {
|
||||
return model.fetchModelRoot().thenCompose(r -> {
|
||||
synchronized (this) {
|
||||
this.root = r;
|
||||
}
|
||||
boolean isInvalid = false;
|
||||
try {
|
||||
r.addListener(this.forRemoval);
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
isInvalid = true;
|
||||
}
|
||||
isInvalid |= !r.isValid();
|
||||
if (isInvalid) {
|
||||
forRemoval.invalidated(root, root, "Who knows?");
|
||||
}
|
||||
CompletableFuture<? extends TargetFocusScope> findSuitable =
|
||||
DebugModelConventions.findSuitable(TargetFocusScope.class, r);
|
||||
return findSuitable;
|
||||
}).thenAccept(fs -> {
|
||||
synchronized (this) {
|
||||
this.focusScope = fs;
|
||||
}
|
||||
if (fs != null) {
|
||||
fs.addListener(this.forFocus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
TargetObject savedRoot;
|
||||
TargetFocusScope savedFocusScope;
|
||||
synchronized (this) {
|
||||
savedRoot = root;
|
||||
savedFocusScope = focusScope;
|
||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||
public void focusChanged(TargetObject object, TargetObject focused) {
|
||||
// I don't think I care which scope
|
||||
fireFocusEvent(focused);
|
||||
List<DebuggerModelServiceProxyPlugin> copy;
|
||||
synchronized (proxies) {
|
||||
copy = List.copyOf(proxies);
|
||||
}
|
||||
if (savedRoot != null) {
|
||||
savedRoot.removeListener(this.forRemoval);
|
||||
}
|
||||
if (savedFocusScope != null) {
|
||||
savedFocusScope.removeListener(this.forFocus);
|
||||
for (DebuggerModelServiceProxyPlugin proxy : copy) {
|
||||
proxy.fireFocusEvent(focused);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected class ListenerOnRecorders implements TraceRecorderListener {
|
||||
@Override
|
||||
@ -195,8 +145,11 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
|
||||
protected final Set<DebuggerModelFactory> factories = new HashSet<>();
|
||||
// Keep strong references to my listeners, or they'll get torched
|
||||
protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel =
|
||||
new LinkedHashMap<>();
|
||||
//protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel =
|
||||
//new LinkedHashMap<>();
|
||||
protected final Set<DebuggerObjectModel> models = new LinkedHashSet<>();
|
||||
protected final ForRemovalAndFocusListener forRemovalAndFocusListener =
|
||||
new ForRemovalAndFocusListener();
|
||||
protected final Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>();
|
||||
|
||||
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
|
||||
@ -262,8 +215,8 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public Set<DebuggerObjectModel> getModels() {
|
||||
synchronized (listenersByModel) {
|
||||
return Set.copyOf(listenersByModel.keySet());
|
||||
synchronized (models) {
|
||||
return Set.copyOf(models);
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,20 +239,16 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
@Override
|
||||
public boolean addModel(DebuggerObjectModel model) {
|
||||
Objects.requireNonNull(model);
|
||||
synchronized (listenersByModel) {
|
||||
if (listenersByModel.containsKey(model)) {
|
||||
synchronized (models) {
|
||||
if (models.contains(model)) {
|
||||
return false;
|
||||
}
|
||||
ListenersForRemovalAndFocus listener = new ListenersForRemovalAndFocus(model);
|
||||
listener.init().exceptionally(e -> {
|
||||
Msg.error(this, "Could not add model " + model, e);
|
||||
synchronized (listenersByModel) {
|
||||
listenersByModel.remove(model);
|
||||
listener.dispose();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
listenersByModel.put(model, listener);
|
||||
model.addModelListener(forRemovalAndFocusListener);
|
||||
TargetObject root = model.getModelRoot();
|
||||
if (!root.isValid()) {
|
||||
forRemovalAndFocusListener.invalidated(root, root,
|
||||
"Invalidated before or during add to service");
|
||||
}
|
||||
}
|
||||
modelListeners.fire.elementAdded(model);
|
||||
return true;
|
||||
@ -307,14 +256,12 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public boolean removeModel(DebuggerObjectModel model) {
|
||||
ListenersForRemovalAndFocus listener;
|
||||
synchronized (listenersByModel) {
|
||||
listener = listenersByModel.remove(model);
|
||||
if (listener == null) {
|
||||
model.removeModelListener(forRemovalAndFocusListener);
|
||||
synchronized (models) {
|
||||
if (!models.remove(model)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
listener.dispose();
|
||||
modelListeners.fire.elementRemoved(model);
|
||||
return true;
|
||||
}
|
||||
|
@ -1,251 +0,0 @@
|
||||
/* ###
|
||||
* 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;
|
||||
}
|
||||
AddressRange traceRange = memMapper.targetToTraceTruncated(ent.getKey().getRange());
|
||||
if (traceRange == null) {
|
||||
continue;
|
||||
}
|
||||
accessible.add(traceRange);
|
||||
}
|
||||
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 addMemory(TargetMemory memory) {
|
||||
// Do nothing
|
||||
// This delegates by region, so need regions even if I have memory
|
||||
}
|
||||
|
||||
@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 void removeMemory(TargetMemory invalid) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/* ###
|
||||
* 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.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 DefaultTraceRecorder 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(DefaultTraceRecorder 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -15,9 +15,7 @@
|
||||
*/
|
||||
package ghidra.dbg;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -457,281 +455,6 @@ public enum DebugModelConventions {
|
||||
return isProcessAlive(process) ? process : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience for listening to selected portions (possible all) of a sub-tree of a model
|
||||
*/
|
||||
public abstract static class SubTreeListenerAdapter extends AnnotatedDebuggerAttributeListener {
|
||||
protected boolean disposed = false;
|
||||
protected final NavigableMap<List<String>, TargetObject> objects =
|
||||
new TreeMap<>(PathComparator.KEYED);
|
||||
|
||||
public SubTreeListenerAdapter() {
|
||||
super(MethodHandles.lookup());
|
||||
}
|
||||
|
||||
/**
|
||||
* An object has been removed from the sub-tree
|
||||
*
|
||||
* @param removed the removed object
|
||||
*/
|
||||
protected abstract void objectRemoved(TargetObject removed);
|
||||
|
||||
/**
|
||||
* An object has been added to the sub-tree
|
||||
*
|
||||
* @param added the added object
|
||||
*/
|
||||
protected abstract void objectAdded(TargetObject added);
|
||||
|
||||
/**
|
||||
* Decide whether a sub-tree (of the sub-tree) should be tracked
|
||||
*
|
||||
* @param obj the root of the sub-tree to consider
|
||||
* @return false to ignore, true to track
|
||||
*/
|
||||
protected abstract boolean checkDescend(TargetObject obj);
|
||||
|
||||
@Override
|
||||
public void invalidated(TargetObject object, TargetObject branch, String reason) {
|
||||
runNotInSwing(this, () -> doInvalidated(object, reason), "invalidated");
|
||||
}
|
||||
|
||||
private void doInvalidated(TargetObject object, String reason) {
|
||||
List<TargetObject> removed = new ArrayList<>();
|
||||
synchronized (objects) {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* NOTE: Can't use iteration, because subtrees will also remove stuff, causing
|
||||
* ConcurrentModificationException, even if removal is via the iterator...
|
||||
*/
|
||||
List<String> path = object.getPath();
|
||||
while (true) {
|
||||
Entry<List<String>, TargetObject> ent = objects.ceilingEntry(path);
|
||||
if (ent == null || !PathUtils.isAncestor(path, ent.getKey())) {
|
||||
break;
|
||||
}
|
||||
objects.remove(ent.getKey());
|
||||
TargetObject succ = ent.getValue();
|
||||
succ.removeListener(this);
|
||||
removed.add(succ);
|
||||
}
|
||||
}
|
||||
for (TargetObject r : removed) {
|
||||
objectRemovedSafe(r);
|
||||
}
|
||||
}
|
||||
|
||||
private void objectRemovedSafe(TargetObject removed) {
|
||||
try {
|
||||
objectRemoved(removed);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Msg.error(this, "Error in callback", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void objectAddedSafe(TargetObject obj) {
|
||||
try {
|
||||
objectAdded(obj);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Msg.error(this, "Error in callback", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void considerObj(TargetObject obj) {
|
||||
if (!checkDescend(obj)) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> addListenerAndConsiderSuccessors(obj))
|
||||
.exceptionally(ex -> {
|
||||
Msg.error(this, "Could add to object: " + obj, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void considerElements(TargetObject parent,
|
||||
Map<String, ? extends TargetObject> elements) {
|
||||
synchronized (objects) {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
if (!objects.containsKey(parent.getPath())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (TargetObject e : elements.values()) {
|
||||
considerObj(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void considerAttributes(TargetObject obj, Map<String, ?> attributes) {
|
||||
synchronized (objects) {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
if (!objects.containsKey(obj.getPath())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, ?> ent : attributes.entrySet()) {
|
||||
String name = ent.getKey();
|
||||
Object val = ent.getValue();
|
||||
if (!(val instanceof TargetObject)) {
|
||||
continue;
|
||||
}
|
||||
TargetObject a = (TargetObject) val;
|
||||
if (PathUtils.isLink(obj.getPath(), name, a.getPath())) {
|
||||
continue;
|
||||
}
|
||||
considerObj(a);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a specified object, without initially adding the sub-tree
|
||||
*
|
||||
* <p>
|
||||
* Note that {@link #checkDescend(TargetObject)} must also exclude the sub-tree, otherwise
|
||||
* children added later will be tracked.
|
||||
*
|
||||
* @param obj the object to track
|
||||
* @return true if the object was not already being listened to
|
||||
*/
|
||||
public boolean addListener(TargetObject obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
obj.addListener(this);
|
||||
synchronized (objects) {
|
||||
if (objects.put(obj.getPath(), obj) == obj) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
objectAddedSafe(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a specified sub-tree to this listener
|
||||
*
|
||||
* @param obj
|
||||
* @return true if the object was not already being listened to
|
||||
*/
|
||||
public boolean addListenerAndConsiderSuccessors(TargetObject obj) {
|
||||
boolean result = addListener(obj);
|
||||
if (result && checkDescend(obj)) {
|
||||
obj.fetchElements()
|
||||
.thenAcceptAsync(elems -> considerElements(obj, elems))
|
||||
.exceptionally(ex -> {
|
||||
Msg.error(this, "Could not fetch elements of obj: " + obj, ex);
|
||||
return null;
|
||||
});
|
||||
obj.fetchAttributes()
|
||||
.thenAcceptAsync(attrs -> considerAttributes(obj, attrs))
|
||||
.exceptionally(ex -> {
|
||||
Msg.error(this, "Could not fetch attributes of obj: " + obj, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
runNotInSwing(this, () -> doElementsChanged(parent, removed, added), "elementsChanged");
|
||||
}
|
||||
|
||||
private void doElementsChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ? extends TargetObject> added) {
|
||||
if (checkDescend(parent)) {
|
||||
considerElements(parent, added);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
runNotInSwing(this, () -> doAttributesChanged(parent, removed, added),
|
||||
"attributesChanged");
|
||||
}
|
||||
|
||||
private void doAttributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
if (checkDescend(parent)) {
|
||||
considerAttributes(parent, added);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this sub-tree tracker/listener
|
||||
*
|
||||
* <p>
|
||||
* This uninstalls the listener from every tracked object and clears its collection of
|
||||
* tracked objects.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable that is updated whenever access changes according to the (now deprecated)
|
||||
* "every-ancestor" convention.
|
||||
*
|
||||
* @deprecated The "every-ancestor" thing doesn't add any flexibility to model implementations.
|
||||
* It might even restrict it. Not to mention it's obtuse to implement.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static class AllRequiredAccess extends AsyncReference<Boolean, Void> {
|
||||
protected class ListenerForAccess extends AnnotatedDebuggerAttributeListener {
|
||||
protected final TargetAccessConditioned access;
|
||||
private boolean accessible;
|
||||
|
||||
public ListenerForAccess(TargetAccessConditioned access) {
|
||||
super(MethodHandles.lookup());
|
||||
this.access = access;
|
||||
this.access.addListener(this);
|
||||
this.accessible = access.isAccessible();
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME)
|
||||
public void accessibilityChanged(TargetObject object, boolean accessibility) {
|
||||
//Msg.debug(this, "Obj " + object + " has become " + accessibility);
|
||||
synchronized (AllRequiredAccess.this) {
|
||||
this.accessible = accessibility;
|
||||
// Check that all requests have been issued (fence is ready)
|
||||
if (listeners != null) {
|
||||
set(getAllAccessibility(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final List<ListenerForAccess> listeners;
|
||||
protected final AsyncFence initFence = new AsyncFence();
|
||||
|
||||
public AllRequiredAccess(Collection<? extends TargetAccessConditioned> allReq) {
|
||||
Msg.debug(this, "Listening for access on: " + allReq);
|
||||
listeners = allReq.stream().map(ListenerForAccess::new).collect(Collectors.toList());
|
||||
set(getAllAccessibility(), null);
|
||||
}
|
||||
|
||||
public boolean getAllAccessibility() {
|
||||
return listeners.stream().allMatch(l -> l.accessible);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AsyncAttribute<T> extends AsyncReference<T, Void>
|
||||
implements DebuggerModelListener {
|
||||
private final TargetObject obj;
|
||||
@ -741,7 +464,7 @@ public enum DebugModelConventions {
|
||||
public AsyncAttribute(TargetObject obj, String name) {
|
||||
this.name = name;
|
||||
this.obj = obj;
|
||||
obj.addListener(this);
|
||||
obj.getModel().addModelListener(this);
|
||||
set((T) obj.getCachedAttribute(name), null);
|
||||
obj.fetchAttribute(name).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not get initial value of " + name + " for " + obj, ex);
|
||||
@ -753,6 +476,9 @@ public enum DebugModelConventions {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void attributesChanged(TargetObject parent, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
if (parent != obj) {
|
||||
return;
|
||||
}
|
||||
if (added.containsKey(name)) {
|
||||
set((T) added.get(name), null);
|
||||
}
|
||||
@ -768,7 +494,7 @@ public enum DebugModelConventions {
|
||||
@Override
|
||||
public void dispose(Throwable reason) {
|
||||
super.dispose(reason);
|
||||
obj.removeListener(this);
|
||||
obj.getModel().removeModelListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -784,34 +510,6 @@ public enum DebugModelConventions {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an object which tracks accessibility for a given target object.
|
||||
*
|
||||
* <p>
|
||||
* Recall that for an object to be considered accessible, it and its ancestors must all be
|
||||
* accessible. Objects without the {@link TargetAccessConditioned} interface, are assumed
|
||||
* accessible.
|
||||
*
|
||||
* <p>
|
||||
* <b>Caution:</b> The returned {@link AllRequiredAccess} object has the only strong references
|
||||
* to the listeners. If you intend to wait for access, e.g., by calling
|
||||
* {@link AsyncReference#waitValue(Object)}, you must ensure a strong reference to this object
|
||||
* is maintained for the duration of the wait. If not, it could be garbage collected, and you
|
||||
* will never get a callback.
|
||||
*
|
||||
* @param obj the object whose accessibility to track
|
||||
* @return a future which completes with an {@link AsyncReference} of the objects effective
|
||||
* accessibility.
|
||||
* @deprecated Just listen on the nearest {@link TargetAccessConditioned} ancestor instead. The
|
||||
* "every-ancestor" convention is deprecated.
|
||||
*/
|
||||
@Deprecated
|
||||
public static CompletableFuture<AllRequiredAccess> trackAccessibility(TargetObject obj) {
|
||||
CompletableFuture<? extends Collection<? extends TargetAccessConditioned>> collectAncestors =
|
||||
collectAncestors(obj, TargetAccessConditioned.class);
|
||||
return collectAncestors.thenApply(AllRequiredAccess::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request activation of the given object in its nearest active scope
|
||||
*
|
||||
|
@ -25,9 +25,6 @@ import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
/**
|
||||
* An abstract implementation of {@link TargetObject}
|
||||
@ -63,15 +60,10 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
|
||||
|
||||
protected volatile boolean valid = true;
|
||||
|
||||
// TODO: Remove these, and just do invocations on model's listeners?
|
||||
protected final ListenerSet<DebuggerModelListener> listeners;
|
||||
|
||||
public <I> AbstractTargetObject(ProxyFactory<I> proxyFactory, I proxyInfo,
|
||||
AbstractDebuggerObjectModel model, P parent, String key, String typeHint,
|
||||
TargetObjectSchema schema) {
|
||||
this.listeners = new ListenerSet<>(DebuggerModelListener.class, model.clientExecutor);
|
||||
this.model = model;
|
||||
listeners.addChained(model.listeners);
|
||||
this.parent = parent;
|
||||
if (parent == null) {
|
||||
this.path = key == null ? List.of() : List.of(key);
|
||||
@ -103,7 +95,7 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
|
||||
assert proxy != null;
|
||||
synchronized (model.lock) {
|
||||
model.objectCreated(proxy);
|
||||
listeners.fire.created(proxy);
|
||||
broadcast().created(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,19 +185,6 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(DebuggerModelListener l) {
|
||||
if (!valid) {
|
||||
throw new IllegalStateException("Object is no longer valid: " + getProxy());
|
||||
}
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(DebuggerModelListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDebuggerObjectModel getModel() {
|
||||
return model;
|
||||
@ -252,14 +231,7 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
|
||||
}
|
||||
valid = false;
|
||||
model.objectInvalidated(getProxy());
|
||||
listeners.fire.invalidated(getProxy(), branch, reason);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
listeners.clear();
|
||||
listeners.clearChained();
|
||||
}, model.clientExecutor).exceptionally(ex -> {
|
||||
Msg.error(this, "Error emptying invalidated object's listener set: ", ex);
|
||||
return null;
|
||||
});
|
||||
broadcast().invalidated(getProxy(), branch, reason);
|
||||
}
|
||||
|
||||
protected void doInvalidateElements(Map<String, ?> elems, String reason) {
|
||||
@ -330,19 +302,8 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the listener set
|
||||
*
|
||||
* <p>
|
||||
* TODO: This method should only be used by the internal implementation. It's not exposed on the
|
||||
* {@link TargetObject} interface, but it could be dangerous to have it here, since clients
|
||||
* could cast to {@link AbstractTargetObject} and get at it, even if the implementation's jar is
|
||||
* excluded from the compile-time classpath.
|
||||
*
|
||||
* @return the listener set
|
||||
*/
|
||||
@Internal
|
||||
public ListenerSet<DebuggerModelListener> getListeners() {
|
||||
return listeners;
|
||||
@Override
|
||||
public DebuggerModelListener broadcast() {
|
||||
return model.listeners.fire;
|
||||
}
|
||||
}
|
||||
|
@ -131,32 +131,6 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
||||
parent.getSchema().getChildSchema(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this object is being observed
|
||||
*
|
||||
* <p>
|
||||
* TODO: It'd be nice if we could know what is being observed: attributes, elements, console
|
||||
* output, etc. In other words, the sub-types and overrides of the listeners.
|
||||
*
|
||||
* <p>
|
||||
* Note, if an implementation chooses to cull requests because no one is listening, it should
|
||||
* take care to re-synchronize when a listener is added. The implementor will need to override
|
||||
* {@link #addListener(TargetObjectListener)}.
|
||||
*
|
||||
* @implNote The recommended pattern on the client side for keeping a synchronized cache is to
|
||||
* add a listener, and then retrieve the current elements. Thus, it is acceptable to
|
||||
* neglect invoking the callback on the new listener during re-synchronization.
|
||||
* However, more testing is needed to verify this doesn't cause problems when network
|
||||
* messaging is involved.
|
||||
*
|
||||
* @return true if there is at least one listener on this object
|
||||
* @deprecated Since the addition of model listeners, everything is always observed
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
protected boolean isObserved() {
|
||||
return !listeners.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> resync(boolean refreshAttributes, boolean refreshElements) {
|
||||
return CompletableFuture.allOf(fetchAttributes(refreshAttributes),
|
||||
@ -303,7 +277,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
||||
doInvalidateElements(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
updateCallbackElements(delta);
|
||||
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
broadcast().elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
@ -351,7 +325,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
||||
doInvalidateElements(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
updateCallbackElements(delta);
|
||||
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
broadcast().elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
@ -493,7 +467,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
updateCallbackAttributes(delta);
|
||||
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
broadcast().attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
@ -558,7 +532,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
if (!delta.isEmpty()/* && !reason.equals("Default")*/) {
|
||||
updateCallbackAttributes(delta);
|
||||
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
broadcast().attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.dbg.agent;
|
||||
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
public interface SpiTargetObject extends TargetObject, InvalidatableTargetObjectIf {
|
||||
@ -35,4 +36,6 @@ public interface SpiTargetObject extends TargetObject, InvalidatableTargetObject
|
||||
default SpiTargetObject getDelegate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
DebuggerModelListener broadcast();
|
||||
}
|
||||
|
@ -1084,41 +1084,4 @@ public interface TargetObject extends Comparable<TargetObject> {
|
||||
public default CompletableFuture<Void> invalidateCaches() {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for object events
|
||||
*
|
||||
* <p>
|
||||
* The client must maintain a strong reference to the listener. To allow stale listeners to be
|
||||
* garbage collected, the implementation should use weak or soft references. That said, the
|
||||
* client should not rely on the implementation to garbage collect its listeners. All unneeded
|
||||
* listeners should be removed using {@link #removeListener(TargetObjectListener)}. The
|
||||
* exception is when an object is invalidated. The client may safely neglect removing any
|
||||
* listeners it registered with that object. If the object does not keep listeners, i.e., it
|
||||
* produces no events, this method may do nothing.
|
||||
*
|
||||
* <p>
|
||||
* Clients ought to listen on the model instead of specific objects, especially since an object
|
||||
* may emit events immediately after its creation, but before the client has a chance to add a
|
||||
* listener. Worse yet, the object could be invalidated before the client can retrieve its
|
||||
* children. Listening on the model ensures the reception of a complete log of events.
|
||||
*
|
||||
* @see DebuggerObjectModel#addModelListener(DebuggerModelListener)
|
||||
* @param l the listener
|
||||
*/
|
||||
public default void addListener(DebuggerModelListener l) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener
|
||||
*
|
||||
* <p>
|
||||
* If the given listener is not registered with this object, this method does nothing.
|
||||
*
|
||||
* @param l the listener
|
||||
*/
|
||||
public default void removeListener(DebuggerModelListener l) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,7 @@
|
||||
package ghidra.dbg;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@ -45,8 +44,17 @@ public class AnnotatedDebuggerAttributeListenerTest implements DebuggerModelTest
|
||||
private void testChanged(TargetObject object, String disp) {
|
||||
display.set(disp, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributesChanged(TargetObject object, Collection<String> removed,
|
||||
Map<String, ?> added) {
|
||||
if (object != obj) {
|
||||
return;
|
||||
}
|
||||
super.attributesChanged(object, removed, added);
|
||||
}
|
||||
};
|
||||
obj.addListener(l);
|
||||
obj.getModel().addModelListener(l);
|
||||
obj.changeAttributes(List.of(), Map.ofEntries(Map.entry("_test", "Testing")), "Because");
|
||||
waitOn(display.waitValue("Testing"));
|
||||
|
||||
|
@ -309,7 +309,7 @@ public class DefaultDebuggerObjectModelTest implements AsyncTestUtils {
|
||||
|
||||
FakeTargetObject fakeA = new FakeTargetObject(model, model.root, "A");
|
||||
FakeTargetRegisterBank fakeA1rb = new FakeTargetRegisterBank(model, fakeA, "[1]");
|
||||
fakeA1rb.listeners.fire.registersUpdated(fakeA1rb, Map.of());
|
||||
fakeA1rb.broadcast().registersUpdated(fakeA1rb, Map.of());
|
||||
fakeA.setElements(List.of(fakeA1rb), "Init");
|
||||
model.root.setAttributes(List.of(fakeA), Map.of(), "Init");
|
||||
|
||||
@ -329,7 +329,7 @@ public class DefaultDebuggerObjectModelTest implements AsyncTestUtils {
|
||||
|
||||
FakeTargetObject fakeA = new FakeTargetObject(model, model.root, "A");
|
||||
FakeTargetRegisterBank fakeA1rb = new FakeTargetRegisterBank(model, fakeA, "[1]");
|
||||
fakeA1rb.listeners.fire.registersUpdated(fakeA1rb, Map.of());
|
||||
fakeA1rb.broadcast().registersUpdated(fakeA1rb, Map.of());
|
||||
fakeA.setElements(List.of(fakeA1rb), "Init");
|
||||
model.root.setAttributes(List.of(fakeA), Map.of(), "Init");
|
||||
EventRecordingListener listener = new EventRecordingListener();
|
||||
|
@ -64,7 +64,7 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
|
||||
}
|
||||
populateObjectValues(result, "Read registers");
|
||||
return model.gateFuture(descs.getModel().future(result).thenApply(__ -> {
|
||||
listeners.fire.registersUpdated(this, result);
|
||||
broadcast().registersUpdated(this, result);
|
||||
return result;
|
||||
}));
|
||||
}
|
||||
@ -90,7 +90,7 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
|
||||
}
|
||||
populateObjectValues(updates, "Write registers");
|
||||
future.thenAccept(__ -> {
|
||||
listeners.fire.registersUpdated(this, updates);
|
||||
broadcast().registersUpdated(this, updates);
|
||||
});
|
||||
return model.gateFuture(future);
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class TestTargetInterpreter
|
||||
}
|
||||
|
||||
public void output(Channel channel, String line) {
|
||||
listeners.fire.consoleOutput(this, channel, line + "\n");
|
||||
broadcast().consoleOutput(this, channel, line + "\n");
|
||||
}
|
||||
|
||||
public void clearCalls() {
|
||||
|
@ -52,7 +52,7 @@ public class TestTargetMemory
|
||||
getMemory(address, data);
|
||||
CompletableFuture<byte[]> future = getModel().future(data);
|
||||
future.thenAccept(__ -> {
|
||||
listeners.fire.memoryUpdated(this, address, data);
|
||||
broadcast().memoryUpdated(this, address, data);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
@ -67,7 +67,7 @@ public class TestTargetMemory
|
||||
setMemory(address, data);
|
||||
CompletableFuture<Void> future = getModel().future(null);
|
||||
future.thenAccept(__ -> {
|
||||
listeners.fire.memoryUpdated(this, address, data);
|
||||
broadcast().memoryUpdated(this, address, data);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class TestTargetSession extends DefaultTargetModelRoot
|
||||
|
||||
public void simulateStep(TestTargetThread eventThread) {
|
||||
eventThread.setState(TargetExecutionState.RUNNING);
|
||||
listeners.fire.event(this, eventThread, TargetEventType.STEP_COMPLETED,
|
||||
broadcast().event(this, eventThread, TargetEventType.STEP_COMPLETED,
|
||||
"Test thread completed a step", List.of());
|
||||
eventThread.setState(TargetExecutionState.STOPPED);
|
||||
}
|
||||
|
@ -119,7 +119,10 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
||||
AsyncReference<String, Void> lastOut = new AsyncReference<>();
|
||||
DebuggerModelListener l = new DebuggerModelListener() {
|
||||
@Override
|
||||
public void consoleOutput(TargetObject interpreter, Channel channel, byte[] out) {
|
||||
public void consoleOutput(TargetObject object, Channel channel, byte[] out) {
|
||||
if (object != interpreter) {
|
||||
return;
|
||||
}
|
||||
String str = new String(out);
|
||||
Msg.debug(this, "Got " + channel + " output: " + str);
|
||||
for (String line : str.split("\n")) {
|
||||
@ -127,7 +130,7 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
||||
}
|
||||
}
|
||||
};
|
||||
interpreter.addListener(l);
|
||||
interpreter.getModel().addModelListener(l);
|
||||
waitAcc(interpreter);
|
||||
waitOn(interpreter.execute(cmd));
|
||||
waitOn(lastOut.waitValue("test"));
|
||||
@ -150,7 +153,10 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
||||
try (CatchOffThread off = new CatchOffThread()) {
|
||||
DebuggerModelListener l = new DebuggerModelListener() {
|
||||
@Override
|
||||
public void consoleOutput(TargetObject interpreter, Channel channel, byte[] out) {
|
||||
public void consoleOutput(TargetObject object, Channel channel, byte[] out) {
|
||||
if (object != interpreter) {
|
||||
return;
|
||||
}
|
||||
String str = new String(out);
|
||||
Msg.debug(this, "Got " + channel + " output: " + str);
|
||||
if (!str.contains("test")) {
|
||||
@ -159,7 +165,7 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
|
||||
off.catching(() -> fail("Unexpected output:" + str));
|
||||
}
|
||||
};
|
||||
interpreter.addListener(l);
|
||||
interpreter.getModel().addModelListener(l);
|
||||
waitAcc(interpreter);
|
||||
String out = waitOn(interpreter.executeCapture(cmd));
|
||||
// Not the greatest, but allow extra lines
|
||||
|
@ -262,7 +262,7 @@ public class DebuggerCallbackReordererTest implements DebuggerModelTestUtils {
|
||||
* child of [r1], to guarantee registersUpdated has happened
|
||||
*/
|
||||
toA.changeElements(List.of(), List.of(toA1), "Test");
|
||||
toA.getListeners().fire.registersUpdated(toA, Map.of("r1", new byte[4]));
|
||||
toA.broadcast().registersUpdated(toA, Map.of("r1", new byte[4]));
|
||||
root.changeAttributes(List.of(), List.of(toA), Map.of(), "Test");
|
||||
/**
|
||||
* CFs may get queued in depth, so add root here to ensure registersUpdated comes before
|
||||
@ -321,12 +321,14 @@ public class DebuggerCallbackReordererTest implements DebuggerModelTestUtils {
|
||||
FakeTargetRoot root = new FakeTargetRoot(model, "Root", model.getRootSchema());
|
||||
FakeTargetObject processes = new FakeTargetObject(model, root, "Processes");
|
||||
FakeTargetProcess proc1 = new FakeTargetProcess(model, processes, "[1]");
|
||||
root.getListeners().fire.event(root, null, TargetEventType.PROCESS_CREATED,
|
||||
"Process 1 created", List.of(proc1));
|
||||
root.broadcast()
|
||||
.event(root, null, TargetEventType.PROCESS_CREATED, "Process 1 created",
|
||||
List.of(proc1));
|
||||
FakeTargetObject p1threads = new FakeTargetObject(model, proc1, "Threads");
|
||||
FakeTargetThread thread1 = new FakeTargetThread(model, p1threads, "[1]");
|
||||
root.getListeners().fire.event(root, thread1, TargetEventType.THREAD_CREATED,
|
||||
"Thread 1 created", List.of());
|
||||
root.broadcast()
|
||||
.event(root, thread1, TargetEventType.THREAD_CREATED, "Thread 1 created",
|
||||
List.of());
|
||||
|
||||
p1threads.changeElements(List.of(), List.of(thread1), "Test");
|
||||
proc1.changeAttributes(List.of(), List.of(p1threads), Map.of(), "Test");
|
||||
@ -366,15 +368,18 @@ public class DebuggerCallbackReordererTest implements DebuggerModelTestUtils {
|
||||
FakeTargetRoot root = new FakeTargetRoot(model, "Root", model.getRootSchema());
|
||||
FakeTargetObject processes = new FakeTargetObject(model, root, "Processes");
|
||||
FakeTargetProcess proc1 = new FakeTargetProcess(model, processes, "[1]");
|
||||
root.getListeners().fire.event(root, null, TargetEventType.PROCESS_CREATED,
|
||||
"Process 1 created", List.of(proc1));
|
||||
root.broadcast()
|
||||
.event(root, null, TargetEventType.PROCESS_CREATED, "Process 1 created",
|
||||
List.of(proc1));
|
||||
FakeTargetObject p1threads = new FakeTargetObject(model, proc1, "Threads");
|
||||
FakeTargetThread thread1 = new FakeTargetThread(model, p1threads, "[1]");
|
||||
root.getListeners().fire.event(root, thread1, TargetEventType.THREAD_CREATED,
|
||||
"Thread 1 created", List.of());
|
||||
root.broadcast()
|
||||
.event(root, thread1, TargetEventType.THREAD_CREATED, "Thread 1 created",
|
||||
List.of());
|
||||
FakeTargetThread thread2 = new FakeTargetThread(model, p1threads, "[2]");
|
||||
root.getListeners().fire.event(root, thread1, TargetEventType.THREAD_CREATED,
|
||||
"Thread 2 created", List.of());
|
||||
root.broadcast()
|
||||
.event(root, thread1, TargetEventType.THREAD_CREATED, "Thread 2 created",
|
||||
List.of());
|
||||
|
||||
p1threads.changeElements(List.of(), List.of(thread2), "Test");
|
||||
proc1.changeAttributes(List.of(), List.of(p1threads), Map.of(), "Test");
|
||||
|
@ -34,6 +34,7 @@ import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceProgramViewMemory;
|
||||
import ghidra.trace.util.MemoryAdapter;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
@ -79,9 +80,11 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void computeFullAdddressSet() {
|
||||
protected void computeFullAdddressSet() {
|
||||
AddressSet temp = new AddressSet();
|
||||
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
|
||||
}
|
||||
addressSet = temp;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import com.google.common.cache.CacheBuilder;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.util.LockHold;
|
||||
|
||||
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||
|
||||
@ -74,10 +75,12 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void recomputeAddressSet() {
|
||||
protected void recomputeAddressSet() {
|
||||
AddressSet temp = new AddressSet();
|
||||
// TODO: Performance test this
|
||||
forVisibleRegions(reg -> temp.add(reg.getRange()));
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
// TODO: Performance test this
|
||||
forVisibleRegions(reg -> temp.add(reg.getRange()));
|
||||
}
|
||||
addressSet = temp;
|
||||
}
|
||||
|
||||
|
@ -142,25 +142,6 @@ public class ListenerMap<K, P, V extends P> {
|
||||
}
|
||||
}
|
||||
});
|
||||
Set<ListenerMap<?, ? extends P, ?>> chainedVolatile;
|
||||
synchronized (lock) {
|
||||
chainedVolatile = chained;
|
||||
}
|
||||
for (ListenerMap<?, ? extends P, ?> c : chainedVolatile) {
|
||||
// Invocation will check if assignable
|
||||
@SuppressWarnings("unchecked")
|
||||
T l = ((ListenerMap<?, P, ?>) c).fire(ext);
|
||||
try {
|
||||
method.invoke(l, args);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
reportError(l, cause);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
reportError(l, e);
|
||||
}
|
||||
}
|
||||
return null; // TODO: Assumes void return type
|
||||
}
|
||||
}
|
||||
@ -169,7 +150,6 @@ public class ListenerMap<K, P, V extends P> {
|
||||
private final Class<P> iface;
|
||||
private final Executor executor;
|
||||
private Map<K, V> map = createMap();
|
||||
private Set<ListenerMap<?, ? extends P, ?>> chained = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* A proxy which passes invocations to each value of this map
|
||||
@ -300,37 +280,4 @@ public class ListenerMap<K, P, V extends P> {
|
||||
map = createMap();
|
||||
}
|
||||
}
|
||||
|
||||
public void addChained(ListenerMap<?, ? extends P, ?> map) {
|
||||
synchronized (lock) {
|
||||
if (chained.contains(map)) {
|
||||
return;
|
||||
}
|
||||
Set<ListenerMap<?, ? extends P, ?>> newChained = new LinkedHashSet<>();
|
||||
newChained.addAll(chained);
|
||||
newChained.add(map);
|
||||
chained = newChained;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeChained(ListenerMap<?, ?, ?> map) {
|
||||
synchronized (lock) {
|
||||
if (!chained.contains(map)) {
|
||||
return;
|
||||
}
|
||||
Set<ListenerMap<?, ? extends P, ?>> newChained = new LinkedHashSet<>();
|
||||
newChained.addAll(chained);
|
||||
newChained.remove(map);
|
||||
chained = newChained;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearChained() {
|
||||
synchronized (lock) {
|
||||
if (chained.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
chained = new LinkedHashSet<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,16 +107,4 @@ public class ListenerSet<E> {
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
public void addChained(ListenerSet<? extends E> set) {
|
||||
map.addChained(set.map);
|
||||
}
|
||||
|
||||
public void removeChained(ListenerSet<?> set) {
|
||||
map.removeChained(set.map);
|
||||
}
|
||||
|
||||
public void clearChained() {
|
||||
map.clearChained();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user