Merge remote-tracking branch 'origin/GP-2752_Dan_removePerTargetObjectListeners--SQUASHED'

This commit is contained in:
Ryan Kurtz 2022-11-12 01:36:31 -05:00
commit b9a6bfdcd3
78 changed files with 384 additions and 1261 deletions

View File

@ -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();

View File

@ -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();
}

View File

@ -79,7 +79,7 @@ public interface DbgModelTargetSession extends //
if (!isValid()) {
return;
}
getListeners().fire.consoleOutput(getProxy(), chan, output);
broadcast().consoleOutput(getProxy(), chan, output);
}
@Override

View File

@ -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();
}

View File

@ -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

View File

@ -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);

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -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);
}));
}

View File

@ -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) {

View File

@ -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;

View File

@ -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();
});
}

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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,
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,13 +86,14 @@ 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,
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;
}

View File

@ -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

View File

@ -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(
@ -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,
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,13 +104,14 @@ 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,
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());
}

View File

@ -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));
}

View File

@ -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()));
}

View File

@ -16,30 +16,19 @@
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",
@ -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);
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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -186,7 +186,6 @@ public class GdbModelTargetInferior
return impl.gateFuture(inferior.cont());
}
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
switch (kind) {
@ -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();
}
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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

View File

@ -110,7 +110,7 @@ public class GdbModelTargetStackFrame
}
protected void invalidateRegisterCaches() {
listeners.fire.invalidateCacheRequested(this);
broadcast().invalidateCacheRequested(this);
}
@Override

View File

@ -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;
});
}

View File

@ -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();

View File

@ -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

View File

@ -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,12 +97,14 @@ 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);
LldbModelTargetBreakpointSpec spec = getTargetBreakpointSpec(bpt);
if (spec == null) {
Msg.error(this, "Stopped for breakpoint unknown to the agent: " + bpt + " (pc=" +
Msg.error(this,
"Stopped for breakpoint unknown to the agent: " + bpt + " (pc=" +
targetThread + ")");
return;
}
@ -111,9 +112,10 @@ public class LldbModelTargetBreakpointContainerImpl extends LldbModelTargetObjec
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;
}

View File

@ -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

View File

@ -72,7 +72,7 @@ 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,
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
"Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
}
@ -84,7 +84,7 @@ public class LldbModelTargetModuleContainerImpl extends LldbModelTargetObjectImp
SBThread thread = getManager().getEventThread();
TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread);
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
broadcast().event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
"Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
LldbModelImpl impl = (LldbModelImpl) model;
impl.deleteModelObject(targetModule.getModule());

View File

@ -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));
}

View File

@ -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()));
}

View File

@ -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);
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;
}

View File

@ -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( //

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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 + ": " +

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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));
}

View File

@ -31,11 +31,15 @@ import ghidra.dbg.target.schema.*;
import ghidra.util.Msg;
import ghidra.util.datastruct.WeakValueHashMap;
@TargetObjectSchemaInfo(name = "VMContainer", elements = { //
@TargetObjectSchemaInfo(
name = "VMContainer",
elements = { //
@TargetElementType(type = JdiModelTargetVM.class) //
}, attributes = { //
},
attributes = { //
@TargetAttributeType(type = Void.class) //
}, canonicalContainer = true)
},
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);
}

View File

@ -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) {

View File

@ -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) {
Swing.runLater(() -> {
if (object == targetConsole) { // Redundant
consoleInvalidated();
if (object != targetConsole) {
return;
}
Swing.runLater(() -> {
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);

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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()));
}

View File

@ -78,31 +78,30 @@ 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) {
if (!object.isRoot()) {
return;
}
assert listener.forRemoval == this;
dispose();
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) {
// I don't think I care which scope
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
@ -114,55 +113,6 @@ public class DebuggerModelServicePlugin extends Plugin
}
};
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;
}
if (savedRoot != null) {
savedRoot.removeListener(this.forRemoval);
}
if (savedFocusScope != null) {
savedFocusScope.removeListener(this.forFocus);
}
}
}
protected class ListenerOnRecorders implements TraceRecorderListener {
@Override
public void snapAdvanced(TraceRecorder recorder, long snap) {
@ -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();
model.addModelListener(forRemovalAndFocusListener);
TargetObject root = model.getModelRoot();
if (!root.isValid()) {
forRemovalAndFocusListener.invalidated(root, root,
"Invalidated before or during add to service");
}
return null;
});
listenersByModel.put(model, listener);
}
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;
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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
*

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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"));

View File

@ -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();

View File

@ -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);
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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");

View File

@ -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();
try (LockHold hold = program.trace.lockRead()) {
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
}
addressSet = temp;
}

View File

@ -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();
try (LockHold hold = program.trace.lockRead()) {
// TODO: Performance test this
forVisibleRegions(reg -> temp.add(reg.getRange()));
}
addressSet = temp;
}

View File

@ -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<>();
}
}
}

View File

@ -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();
}
}