GP-2752: Remove TargetObject.addListener() and related

This commit is contained in:
Dan 2022-11-10 13:46:23 -05:00
parent c301dd2c89
commit 50c7217635
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.manager.impl.DbgManagerImpl;
import agent.dbgeng.model.AbstractDbgModel; import agent.dbgeng.model.AbstractDbgModel;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.agent.SpiTargetObject; import ghidra.dbg.agent.SpiTargetObject;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.util.datastruct.ListenerSet;
public interface DbgModelTargetObject extends SpiTargetObject { public interface DbgModelTargetObject extends SpiTargetObject {
@ -65,8 +63,6 @@ public interface DbgModelTargetObject extends SpiTargetObject {
public CompletableFuture<List<TargetObject>> requestNativeElements(); public CompletableFuture<List<TargetObject>> requestNativeElements();
public ListenerSet<DebuggerModelListener> getListeners();
public DbgModelTargetSession getParentSession(); public DbgModelTargetSession getParentSession();
public DbgModelTargetProcess getParentProcess(); public DbgModelTargetProcess getParentProcess();

View file

@ -25,12 +25,10 @@ import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.*; import agent.dbgeng.manager.impl.*;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec; import ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.error.DebuggerRegisterAccessException; import ghidra.dbg.error.DebuggerRegisterAccessException;
import ghidra.dbg.target.TargetRegisterBank; import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.util.ConversionUtils; import ghidra.dbg.util.ConversionUtils;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, TargetRegisterBank { public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, TargetRegisterBank {
@ -91,10 +89,8 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
reg.setModified(value.toString(16).equals(oldval)); reg.setModified(value.toString(16).equals(oldval));
} }
} }
ListenerSet<DebuggerModelListener> listeners = getListeners();
if (listeners != null) { broadcast().registersUpdated(getProxy(), result);
listeners.fire.registersUpdated(getProxy(), result);
}
return result; return result;
}); });
} }
@ -126,7 +122,7 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
getParentThread().getThread().writeRegisters(toWrite).handle(seq::next); getParentThread().getThread().writeRegisters(toWrite).handle(seq::next);
// TODO: Should probably filter only effective and normalized writes in the callback // TODO: Should probably filter only effective and normalized writes in the callback
}).then(seq -> { }).then(seq -> {
getListeners().fire.registersUpdated(getProxy(), values); broadcast().registersUpdated(getProxy(), values);
seq.exit(); seq.exit();
}).finish(); }).finish();
} }

View file

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

View file

@ -95,7 +95,7 @@ public class DbgModelTargetBreakpointContainerImpl extends DbgModelTargetObjectI
DbgModelTargetThread targetThread = DbgModelTargetThread targetThread =
getParentProcess().getThreads().getTargetThread(getManager().getEventThread()); getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
DbgModelTargetBreakpointSpec spec = getTargetBreakpointSpec(info); DbgModelTargetBreakpointSpec spec = getTargetBreakpointSpec(info);
listeners.fire.breakpointHit(getProxy(), targetThread, null, spec, spec); broadcast().breakpointHit(getProxy(), targetThread, null, spec, spec);
spec.breakpointHit(); spec.breakpointHit();
} }

View file

@ -121,7 +121,7 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
if (span == null) { if (span == null) {
throw new DebuggerMemoryAccessException("Cannot read at " + address); 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()); return Arrays.copyOf(buf.array(), (int) span.length());
} }
@ -138,7 +138,7 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
} }
private void writeAssist(Address address, byte[] data) { private void writeAssist(Address address, byte[] data) {
listeners.fire.memoryUpdated(getProxy(), address, data); broadcast().memoryUpdated(getProxy(), address, data);
} }
@Override @Override

View file

@ -27,13 +27,16 @@ import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode; import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
@TargetObjectSchemaInfo(name = "ModuleContainer", elements = { // @TargetObjectSchemaInfo(
@TargetElementType(type = DbgModelTargetModuleImpl.class) // name = "ModuleContainer",
}, // elements = {
elementResync = ResyncMode.ONCE, // @TargetElementType(type = DbgModelTargetModuleImpl.class)
attributes = { // },
@TargetAttributeType(type = Void.class) // elementResync = ResyncMode.ONCE,
}, canonicalContainer = true) attributes = {
@TargetAttributeType(type = Void.class)
},
canonicalContainer = true)
public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetModuleContainer { implements DbgModelTargetModuleContainer {
// NOTE: -file-list-shared-libraries omits the main module and system-supplied DSO. // 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 eventThread =
(TargetThread) getModel().getModelObject(getManager().getEventThread()); (TargetThread) getModel().getModelObject(getManager().getEventThread());
changeElements(List.of(), List.of(module), Map.of(), "Loaded"); 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)); "Library " + name + " loaded", List.of(module));
} }
@ -77,7 +80,7 @@ public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
if (targetModule != null) { if (targetModule != null) {
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(getManager().getEventThread()); (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)); "Library " + name + " unloaded", List.of(targetModule));
DbgModelImpl impl = (DbgModelImpl) model; DbgModelImpl impl = (DbgModelImpl) model;
impl.deleteModelObject(targetModule.getDbgModule()); impl.deleteModelObject(targetModule.getDbgModule());
@ -108,6 +111,7 @@ public class DbgModelTargetModuleContainerImpl extends DbgModelTargetObjectImpl
}); });
} }
@Override
public DbgModelTargetModule getTargetModule(String name) { public DbgModelTargetModule getTargetModule(String name) {
// Only get here from libraryLoaded or getElements. The known list should be fresh. // Only get here from libraryLoaded or getElements. The known list should be fresh.
DbgModule module = process.getKnownModules().get(name); DbgModule module = process.getKnownModules().get(name);

View file

@ -33,12 +33,12 @@ import ghidra.dbg.target.schema.*;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(
name = "ProcessContainer", name = "ProcessContainer",
elements = { // elements = {
@TargetElementType(type = DbgModelTargetProcessImpl.class) // @TargetElementType(type = DbgModelTargetProcessImpl.class)
}, },
attributes = { // attributes = {
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // @TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class),
@TargetAttributeType(type = Void.class) // @TargetAttributeType(type = Void.class)
}, },
canonicalContainer = true) canonicalContainer = true)
public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl
@ -58,7 +58,7 @@ public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl
DbgModelTargetProcess process = getTargetProcess(proc); DbgModelTargetProcess process = getTargetProcess(proc);
changeElements(List.of(), List.of(process), Map.of(), "Added"); changeElements(List.of(), List.of(process), Map.of(), "Added");
process.processStarted(proc.getPid()); 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(), "Process " + proc.getId() + " started " + process.getName() + "pid=" + proc.getPid(),
List.of(process)); List.of(process));
} }

View file

@ -139,6 +139,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
} }
} }
@Override
public void threadStateChangedSpecific(DbgThread thread, DbgState state) { public void threadStateChangedSpecific(DbgThread thread, DbgState state) {
TargetExecutionState targetState = convertState(state); TargetExecutionState targetState = convertState(state);
setExecutionState(targetState, "ThreadStateChanged"); setExecutionState(targetState, "ThreadStateChanged");
@ -212,7 +213,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, // STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() // EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() //
), "Exited"); ), "Exited");
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED, broadcast().event(getProxy(), null, TargetEventType.PROCESS_EXITED,
"Process " + proc.getId() + " exited code=" + proc.getExitCode(), "Process " + proc.getId() + " exited code=" + proc.getExitCode(),
List.of(getProxy())); List.of(getProxy()));
} }
@ -221,7 +222,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
@Override @Override
public void memoryChanged(DbgProcess proc, long addr, int len, DbgCause cause) { public void memoryChanged(DbgProcess proc, long addr, int len, DbgCause cause) {
if (proc.equals(this.process)) { 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); changeAttrs(reg, value);
} }
this.values = result; this.values = result;
listeners.fire.registersUpdated(getProxy(), result); broadcast().registersUpdated(getProxy(), result);
return result; return result;
})); }));
} }
@ -159,7 +159,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
return thread.writeRegisters(toWrite); return thread.writeRegisters(toWrite);
// TODO: Should probably filter only effective and normalized writes in the callback // TODO: Should probably filter only effective and normalized writes in the callback
}).thenAccept(__ -> { }).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.TargetObject;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
@TargetObjectSchemaInfo(name = "ThreadContainer", elements = { // @TargetObjectSchemaInfo(
@TargetElementType(type = DbgModelTargetThreadImpl.class) // name = "ThreadContainer",
}, attributes = { // elements = {
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // @TargetElementType(type = DbgModelTargetThreadImpl.class)
@TargetAttributeType(type = Void.class) // },
}, canonicalContainer = true) attributes = {
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class),
@TargetAttributeType(type = Void.class)
},
canonicalContainer = true)
public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetThreadContainer, DbgModelTargetConfigurable { implements DbgModelTargetThreadContainer, DbgModelTargetConfigurable {
@ -59,7 +63,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
DbgModelTargetThread targetThread = getTargetThread(thread); DbgModelTargetThread targetThread = getTargetThread(thread);
changeElements(List.of(), List.of(targetThread), Map.of(), "Created"); changeElements(List.of(), List.of(targetThread), Map.of(), "Created");
targetThread.threadStateChangedSpecific(DbgState.STARTING, DbgReason.getReason(null)); 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)); "Thread " + thread.getId() + " started", List.of(targetThread));
} }
@ -68,7 +72,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
DbgReason reason) { DbgReason reason) {
DbgModelTargetThread targetThread = getTargetThread(thread); DbgModelTargetThread targetThread = getTargetThread(thread);
TargetEventType eventType = getEventType(state, cause, reason); 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)); "Thread " + thread.getId() + " state changed", List.of(targetThread));
targetThread.threadStateChangedSpecific(state, reason); targetThread.threadStateChangedSpecific(state, reason);
} }
@ -78,7 +82,7 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl
DbgModelImpl impl = (DbgModelImpl) model; DbgModelImpl impl = (DbgModelImpl) model;
DbgModelTargetThread targetThread = (DbgModelTargetThread) impl.getModelObject(threadId); DbgModelTargetThread targetThread = (DbgModelTargetThread) impl.getModelObject(threadId);
if (targetThread != null) { if (targetThread != null) {
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED, broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
"Thread " + threadId + " exited", List.of(targetThread)); "Thread " + threadId + " exited", List.of(targetThread));
} }
//synchronized (this) { //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.jna.dbgmodel.DbgModelNative.TypeKind;
import agent.dbgmodel.manager.DbgManager2Impl; import agent.dbgmodel.manager.DbgManager2Impl;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.agent.DefaultTargetObject; import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
@ -254,7 +253,8 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
TargetExecutionStateful stateful = (TargetExecutionStateful) proxy; TargetExecutionStateful stateful = (TargetExecutionStateful) proxy;
TargetExecutionState state = stateful.getExecutionState(); TargetExecutionState state = stateful.getExecutionState();
attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, state); attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, state);
} else { }
else {
attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME,
TargetExecutionState.INACTIVE); TargetExecutionState.INACTIVE);
} }
@ -412,11 +412,6 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
}); });
} }
@Override
public void removeListener(DebuggerModelListener l) {
listeners.remove(l);
}
@Override @Override
public DbgModelTargetSession getParentSession() { public DbgModelTargetSession getParentSession() {
DbgModelTargetObject test = (DbgModelTargetObject) parent; DbgModelTargetObject test = (DbgModelTargetObject) parent;

View file

@ -179,7 +179,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
System.err.println("processAdded - null"); System.err.println("processAdded - null");
return; 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(), "Process " + proc.getId() + " started " + "notepad.exe" + " pid=" + proc.getPid(),
List.of(targetProcess)); List.of(targetProcess));
}); });
@ -193,7 +193,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
System.err.println("threadCreated - null"); System.err.println("threadCreated - null");
return; return;
} }
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_CREATED, broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
"Thread " + thread.getId() + " started", List.of(targetThread)); "Thread " + thread.getId() + " started", List.of(targetThread));
DelegateDbgModel2TargetObject delegate = DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) targetThread.getDelegate(); (DelegateDbgModel2TargetObject) targetThread.getDelegate();
@ -210,7 +210,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
} }
getObject(getManager().getEventThread()).thenAccept(t -> { getObject(getManager().getEventThread()).thenAccept(t -> {
TargetThread eventThread = (TargetThread) 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)); "Library " + info.getModuleName() + " loaded", List.of(mod));
}); });
getObject(getManager().getEventProcess()).thenAccept(p -> { getObject(getManager().getEventProcess()).thenAccept(p -> {
@ -231,7 +231,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
} }
getObject(getManager().getEventThread()).thenAccept(t -> { getObject(getManager().getEventThread()).thenAccept(t -> {
TargetThread eventThread = (TargetThread) 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)); "Library " + info.getModuleName() + " unloaded", List.of(mod));
}); });
getObject(getManager().getEventProcess()).thenAccept(p -> { getObject(getManager().getEventProcess()).thenAccept(p -> {
@ -369,7 +369,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() // DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() //
), "Exited"); ), "Exited");
} }
getListeners().fire.event(targetProcess.getProxy(), null, broadcast().event(targetProcess.getProxy(), null,
TargetEventType.PROCESS_EXITED, TargetEventType.PROCESS_EXITED,
"Process " + proc.getId() + " exited code=" + proc.getExitCode(), "Process " + proc.getId() + " exited code=" + proc.getExitCode(),
List.of(getProxy())); List.of(getProxy()));
@ -383,7 +383,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
return; return;
} }
DbgModelTargetThread targetThread = (DbgModelTargetThread) thread.getProxy(); 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)); "Thread " + threadId + " exited", List.of(targetThread));
}); });
} }
@ -401,7 +401,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
), reason.desc()); ), reason.desc());
intrinsics.put(TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread); intrinsics.put(TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread);
TargetEventType eventType = getEventType(state, cause, reason); 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)); "Thread " + thread.getId() + " state changed", List.of(targetThread));
DelegateDbgModel2TargetObject delegate = DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) targetThread.getDelegate(); (DelegateDbgModel2TargetObject) targetThread.getDelegate();
@ -472,7 +472,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
DbgThread thread = info.getEventThread(); DbgThread thread = info.getEventThread();
TargetObject targetThread = getModel().getModelObject(thread); TargetObject targetThread = getModel().getModelObject(thread);
listeners.fire.breakpointHit(bpt.getParent(), targetThread, null, bpt, bpt); broadcast().breakpointHit(bpt.getParent(), targetThread, null, bpt, bpt);
bpt.breakpointHit(); bpt.breakpointHit();
}); });
} }

View file

@ -22,11 +22,9 @@ import java.util.concurrent.CompletableFuture;
import agent.frida.manager.impl.FridaManagerImpl; import agent.frida.manager.impl.FridaManagerImpl;
import agent.frida.model.AbstractFridaModel; import agent.frida.model.AbstractFridaModel;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.agent.SpiTargetObject; import ghidra.dbg.agent.SpiTargetObject;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.util.datastruct.ListenerSet;
public interface FridaModelTargetObject extends SpiTargetObject { public interface FridaModelTargetObject extends SpiTargetObject {
@ -59,8 +57,6 @@ public interface FridaModelTargetObject extends SpiTargetObject {
public CompletableFuture<List<TargetObject>> requestNativeElements(); public CompletableFuture<List<TargetObject>> requestNativeElements();
public ListenerSet<DebuggerModelListener> getListeners();
public FridaModelTargetSession getParentSession(); public FridaModelTargetSession getParentSession();
public FridaModelTargetProcess getParentProcess(); public FridaModelTargetProcess getParentProcess();

View file

@ -50,7 +50,7 @@ public interface FridaModelTargetSession extends //
== DebugOutputFlags.DEBUG_OUTPUT_WARNING.getValue())) { == DebugOutputFlags.DEBUG_OUTPUT_WARNING.getValue())) {
chan = TargetConsole.Channel.STDERR; chan = TargetConsole.Channel.STDERR;
} }
getListeners().fire.consoleOutput(getProxy(), chan, output); broadcast().consoleOutput(getProxy(), chan, output);
} }
@Override @Override

View file

@ -80,7 +80,7 @@ public class FridaModelTargetKernelMemoryContainerImpl extends FridaModelTargetO
@Override @Override
public CompletableFuture<Void> requestElements(boolean refresh) { public CompletableFuture<Void> requestElements(boolean refresh) {
if (refresh) { if (refresh) {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
return getManager().listKernelMemory(); return getManager().listKernelMemory();
} }
@ -104,12 +104,12 @@ public class FridaModelTargetKernelMemoryContainerImpl extends FridaModelTargetO
if (range == null) { if (range == null) {
throw new DebuggerMemoryAccessException("Cannot read at " + address); 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()); return Arrays.copyOf(buf.array(), (int) range.getLength());
} }
private void writeAssist(Address address, byte[] data) { private void writeAssist(Address address, byte[] data) {
listeners.fire.memoryUpdated(getProxy(), address, data); broadcast().memoryUpdated(getProxy(), address, data);
} }
@Override @Override

View file

@ -72,12 +72,13 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread); (TargetThread) getModel().getModelObject(thread);
changeElements(List.of(), List.of(targetModule), Map.of(), "Loaded"); 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)); "Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
} }
@Override @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); FridaModule module = info.getModule(index);
changeElements(List.of(), List.of(getTargetModule(module)), Map.of(), "Replaced"); changeElements(List.of(), List.of(getTargetModule(module)), Map.of(), "Replaced");
FridaModelTargetModule targetModule = getTargetModule(module); FridaModelTargetModule targetModule = getTargetModule(module);
@ -85,14 +86,15 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
} }
@Override @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)); FridaModelTargetModule targetModule = getTargetModule(info.getModule(index));
if (targetModule != null) { if (targetModule != null) {
FridaThread thread = getManager().getCurrentThread(); FridaThread thread = getManager().getCurrentThread();
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread); (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)); "Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
FridaModelImpl impl = (FridaModelImpl) model; FridaModelImpl impl = (FridaModelImpl) model;
impl.deleteModelObject(targetModule.getModule()); impl.deleteModelObject(targetModule.getModule());
} }
@ -112,7 +114,7 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
@Override @Override
public CompletableFuture<Void> requestElements(boolean refresh) { public CompletableFuture<Void> requestElements(boolean refresh) {
if (refresh) { if (refresh) {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
return getManager().listKernelModules(); return getManager().listKernelModules();
} }
@ -121,7 +123,8 @@ public class FridaModelTargetKernelModuleContainerImpl extends FridaModelTargetO
public FridaModelTargetKernelModuleImpl getTargetModule(FridaModule module) { public FridaModelTargetKernelModuleImpl getTargetModule(FridaModule module) {
TargetObject targetObject = getMapObject(module); TargetObject targetObject = getMapObject(module);
if (targetObject != null) { if (targetObject != null) {
FridaModelTargetKernelModuleImpl targetModule = (FridaModelTargetKernelModuleImpl) targetObject; FridaModelTargetKernelModuleImpl targetModule =
(FridaModelTargetKernelModuleImpl) targetObject;
targetModule.setModelObject(module); targetModule.setModelObject(module);
return targetModule; return targetModule;
} }

View file

@ -102,7 +102,7 @@ public class FridaModelTargetMemoryContainerImpl extends FridaModelTargetObjectI
@Override @Override
public CompletableFuture<Void> requestElements(boolean refresh) { public CompletableFuture<Void> requestElements(boolean refresh) {
if (refresh) { if (refresh) {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
return getManager().listMemory(process.getProcess()); return getManager().listMemory(process.getProcess());
} }
@ -126,12 +126,12 @@ public class FridaModelTargetMemoryContainerImpl extends FridaModelTargetObjectI
if (range == null) { if (range == null) {
throw new DebuggerMemoryAccessException("Cannot read at " + address); 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()); return Arrays.copyOf(buf.array(), (int) range.getLength());
} }
private void writeAssist(Address address, byte[] data) { private void writeAssist(Address address, byte[] data) {
listeners.fire.memoryUpdated(getProxy(), address, data); broadcast().memoryUpdated(getProxy(), address, data);
} }
@Override @Override

View file

@ -20,25 +20,12 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.frida.frida.FridaModuleInfo; import agent.frida.frida.FridaModuleInfo;
import agent.frida.manager.FridaCause; import agent.frida.manager.*;
import agent.frida.manager.FridaModule; import agent.frida.model.iface2.*;
import agent.frida.manager.FridaProcess; import agent.frida.model.methods.*;
import agent.frida.manager.FridaSession; import ghidra.dbg.target.*;
import agent.frida.manager.FridaThread; import ghidra.dbg.target.schema.*;
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 ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode; import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.util.Msg; import ghidra.util.Msg;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(
@ -77,7 +64,7 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
unload // unload //
), Map.of( // ), Map.of( //
), "Initialized"); ), "Initialized");
getManager().addEventsListener(this); getManager().addEventsListener(this);
requestElements(true); requestElements(true);
} }
@ -103,12 +90,13 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread); (TargetThread) getModel().getModelObject(thread);
changeElements(List.of(), List.of(targetModule), Map.of(), "Loaded"); 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)); "Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
} }
@Override @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); FridaModule module = info.getModule(index);
changeElements(List.of(), List.of(getTargetModule(module)), Map.of(), "Replaced"); changeElements(List.of(), List.of(getTargetModule(module)), Map.of(), "Replaced");
FridaModelTargetModule targetModule = getTargetModule(module); FridaModelTargetModule targetModule = getTargetModule(module);
@ -116,14 +104,15 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
} }
@Override @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)); FridaModelTargetModule targetModule = getTargetModule(info.getModule(index));
if (targetModule != null) { if (targetModule != null) {
FridaThread thread = getManager().getCurrentThread(); FridaThread thread = getManager().getCurrentThread();
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread); (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)); "Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
FridaModelImpl impl = (FridaModelImpl) model; FridaModelImpl impl = (FridaModelImpl) model;
impl.deleteModelObject(targetModule.getModule()); impl.deleteModelObject(targetModule.getModule());
} }
@ -143,7 +132,7 @@ public class FridaModelTargetModuleContainerImpl extends FridaModelTargetObjectI
@Override @Override
public CompletableFuture<Void> requestElements(boolean refresh) { public CompletableFuture<Void> requestElements(boolean refresh) {
if (refresh) { if (refresh) {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
return getManager().listModules(session.getProcess()); return getManager().listModules(session.getProcess());
} }

View file

@ -64,7 +64,7 @@ public class FridaModelTargetProcessContainerImpl extends FridaModelTargetObject
FridaModelTargetProcess process = getTargetProcess(proc); FridaModelTargetProcess process = getTargetProcess(proc);
changeElements(List.of(), List.of(process), Map.of(), "Added"); changeElements(List.of(), List.of(process), Map.of(), "Added");
process.processStarted(proc); 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(), "Process " + FridaClient.getId(proc) + " started " + process.getName(),
List.of(process)); List.of(process));
} }

View file

@ -236,7 +236,7 @@ public class FridaModelTargetProcessImpl extends FridaModelTargetObjectImpl
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, // STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
EXIT_CODE_ATTRIBUTE_NAME, exitDesc // EXIT_CODE_ATTRIBUTE_NAME, exitDesc //
), "Exited"); ), "Exited");
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED, broadcast().event(getProxy(), null, TargetEventType.PROCESS_EXITED,
"Process " + FridaClient.getId(getProcess()) + " exited code=" + exitDesc, "Process " + FridaClient.getId(getProcess()) + " exited code=" + exitDesc,
List.of(getProxy())); List.of(getProxy()));
} }

View file

@ -16,42 +16,31 @@
package agent.frida.model.impl; package agent.frida.model.impl;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import agent.frida.manager.FridaReason; import agent.frida.manager.*;
import agent.frida.manager.FridaState; import agent.frida.model.iface2.*;
import agent.frida.manager.FridaValue;
import agent.frida.model.iface2.FridaModelTargetRegister;
import agent.frida.model.iface2.FridaModelTargetRegisterBank;
import agent.frida.model.iface2.FridaModelTargetRegisterContainerAndBank;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.error.DebuggerRegisterAccessException; import ghidra.dbg.error.DebuggerRegisterAccessException;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegisterBank; import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetElementType;
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode; import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(
name = "RegisterContainer", name = "RegisterContainer",
attributeResync = ResyncMode.ALWAYS, attributeResync = ResyncMode.ALWAYS,
elements = { // elements = { //
@TargetElementType(type = FridaModelTargetRegisterImpl.class) // @TargetElementType(type = FridaModelTargetRegisterImpl.class) //
}, },
attributes = { attributes = {
@TargetAttributeType( @TargetAttributeType(
name = TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME, name = TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME,
type = FridaModelTargetRegisterContainerImpl.class), type = FridaModelTargetRegisterContainerImpl.class),
@TargetAttributeType(type = Void.class) @TargetAttributeType(type = Void.class)
}, },
canonicalContainer = true) canonicalContainer = true)
public class FridaModelTargetRegisterContainerImpl public class FridaModelTargetRegisterContainerImpl
@ -64,10 +53,10 @@ public class FridaModelTargetRegisterContainerImpl
public FridaModelTargetRegisterContainerImpl(FridaModelTargetThreadImpl thread) { public FridaModelTargetRegisterContainerImpl(FridaModelTargetThreadImpl thread) {
super(thread.getModel(), thread, NAME, "RegisterContainer"); super(thread.getModel(), thread, NAME, "RegisterContainer");
this.thread = thread; this.thread = thread;
changeAttributes(List.of(), List.of(), Map.of( changeAttributes(List.of(), List.of(), Map.of(
DISPLAY_ATTRIBUTE_NAME, getName(), DISPLAY_ATTRIBUTE_NAME, getName(),
DESCRIPTIONS_ATTRIBUTE_NAME, this), "Initialized"); DESCRIPTIONS_ATTRIBUTE_NAME, this), "Initialized");
requestElements(false); requestElements(false);
} }
@ -78,7 +67,7 @@ public class FridaModelTargetRegisterContainerImpl
@Override @Override
public CompletableFuture<Void> requestElements(boolean refresh) { public CompletableFuture<Void> requestElements(boolean refresh) {
if (refresh) { if (refresh) {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
return getManager().listRegisters(thread.getThread()).thenAccept(registers -> { return getManager().listRegisters(thread.getThread()).thenAccept(registers -> {
List<TargetObject> targetRegisters; List<TargetObject> targetRegisters;
@ -114,7 +103,8 @@ public class FridaModelTargetRegisterContainerImpl
requestAttributes(false).thenAccept(__ -> { requestAttributes(false).thenAccept(__ -> {
for (Object attribute : getCachedAttributes().values()) { for (Object attribute : getCachedAttributes().values()) {
if (attribute instanceof FridaModelTargetRegisterBank) { if (attribute instanceof FridaModelTargetRegisterBank) {
FridaModelTargetRegisterBank bank = (FridaModelTargetRegisterBank) attribute; FridaModelTargetRegisterBank bank =
(FridaModelTargetRegisterBank) attribute;
bank.threadStateChangedSpecific(state, reason); bank.threadStateChangedSpecific(state, reason);
} }
} }
@ -136,12 +126,9 @@ public class FridaModelTargetRegisterContainerImpl
byte[] bytes = register.getBytes(); byte[] bytes = register.getBytes();
result.put(regname, bytes); result.put(regname, bytes);
} }
ListenerSet<DebuggerModelListener> ls = getListeners(); //if (getName().contains("General")) {
if (ls != null) { broadcast().registersUpdated(this, result);
//if (getName().contains("General")) { //}
ls.fire.registersUpdated(this, result);
//}
}
return CompletableFuture.completedFuture(result); return CompletableFuture.completedFuture(result);
} }
@ -158,7 +145,7 @@ public class FridaModelTargetRegisterContainerImpl
BigInteger val = new BigInteger(1, ent.getValue()); BigInteger val = new BigInteger(1, ent.getValue());
reg.getRegister().setValue(val.toString()); reg.getRegister().setValue(val.toString());
} }
getListeners().fire.registersUpdated(getProxy(), values); broadcast().registersUpdated(getProxy(), values);
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }

View file

@ -76,7 +76,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
changeElements(List.of(), List.of(targetThread), Map.of(), "Created"); changeElements(List.of(), List.of(targetThread), Map.of(), "Created");
targetThread.threadStateChangedSpecific(FridaState.FRIDA_THREAD_UNINTERRUPTIBLE, targetThread.threadStateChangedSpecific(FridaState.FRIDA_THREAD_UNINTERRUPTIBLE,
FridaReason.getReason(null)); 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)); "Thread " + FridaClient.getId(thread) + " started", List.of(targetThread));
} }
@ -95,7 +95,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
String threadId = FridaModelTargetThreadImpl.indexThread(thread); String threadId = FridaModelTargetThreadImpl.indexThread(thread);
FridaModelTargetThread targetThread = (FridaModelTargetThread) getMapObject(thread); FridaModelTargetThread targetThread = (FridaModelTargetThread) getMapObject(thread);
if (targetThread != null) { if (targetThread != null) {
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED, broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
"Thread " + threadId + " exited", List.of(targetThread)); "Thread " + threadId + " exited", List.of(targetThread));
} }
changeElements(List.of( // changeElements(List.of( //
@ -108,7 +108,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
FridaReason reason) { FridaReason reason) {
FridaModelTargetThread targetThread = getTargetThread(thread); FridaModelTargetThread targetThread = getTargetThread(thread);
TargetEventType eventType = getEventType(state, cause, reason); 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)); "Thread " + FridaClient.getId(thread) + " state changed", List.of(targetThread));
targetThread.threadStateChangedSpecific(state, reason); targetThread.threadStateChangedSpecific(state, reason);
} }
@ -133,7 +133,7 @@ public class FridaModelTargetThreadContainerImpl extends FridaModelTargetObjectI
@Override @Override
public CompletableFuture<Void> requestElements(boolean refresh) { public CompletableFuture<Void> requestElements(boolean refresh) {
if (refresh) { if (refresh) {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
return getManager().listThreads(process); return getManager().listThreads(process);
} }

View file

@ -102,7 +102,7 @@ public class GdbModelTargetBreakpointContainer
spec + " (pc=" + frame.getProgramCounter() + ")"); spec + " (pc=" + frame.getProgramCounter() + ")");
//return; // Not ideal, but eb == null should be fine, since the spec holds the actions //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); spec.breakpointHit(frame, loc);
return loc; return loc;
} }

View file

@ -186,7 +186,6 @@ public class GdbModelTargetInferior
return impl.gateFuture(inferior.cont()); return impl.gateFuture(inferior.cont());
} }
@Override @Override
public CompletableFuture<Void> step(TargetStepKind kind) { public CompletableFuture<Void> step(TargetStepKind kind) {
switch (kind) { switch (kind) {
@ -210,7 +209,7 @@ public class GdbModelTargetInferior
public CompletableFuture<Void> interrupt() { public CompletableFuture<Void> interrupt() {
return impl.session.interrupt(); return impl.session.interrupt();
} }
@Override @Override
public CompletableFuture<Void> attach(TargetAttachable attachable) { public CompletableFuture<Void> attach(TargetAttachable attachable) {
GdbModelTargetAttachable mine = impl.assertMine(GdbModelTargetAttachable.class, attachable); GdbModelTargetAttachable mine = impl.assertMine(GdbModelTargetAttachable.class, attachable);
@ -233,7 +232,7 @@ public class GdbModelTargetInferior
} }
protected CompletableFuture<Void> inferiorStarted(Long pid) { 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, "Inferior " + inferior.getId() + " started " + inferior.getExecutable() + " pid=" + pid,
List.of(this)); List.of(this));
/*System.err.println("inferiorStarted: realState = " + realState); /*System.err.println("inferiorStarted: realState = " + realState);
@ -315,28 +314,28 @@ public class GdbModelTargetInferior
params.add(loc); params.add(loc);
} }
gatherThreads(params, sco.getAffectedThreads()); gatherThreads(params, sco.getAffectedThreads());
impl.session.getListeners().fire.event(impl.session, targetEventThread, broadcast().event(impl.session, targetEventThread, TargetEventType.BREAKPOINT_HIT,
TargetEventType.BREAKPOINT_HIT, bpHit.desc(), params); bpHit.desc(), params);
} }
else if (reason instanceof GdbEndSteppingRangeReason) { else if (reason instanceof GdbEndSteppingRangeReason) {
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
gatherThreads(params, sco.getAffectedThreads()); gatherThreads(params, sco.getAffectedThreads());
impl.session.getListeners().fire.event(impl.session, targetEventThread, broadcast().event(impl.session, targetEventThread, TargetEventType.STEP_COMPLETED,
TargetEventType.STEP_COMPLETED, reason.desc(), params); reason.desc(), params);
} }
else if (reason instanceof GdbSignalReceivedReason) { else if (reason instanceof GdbSignalReceivedReason) {
GdbSignalReceivedReason signal = (GdbSignalReceivedReason) reason; GdbSignalReceivedReason signal = (GdbSignalReceivedReason) reason;
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
params.add(signal.getSignalName()); params.add(signal.getSignalName());
gatherThreads(params, sco.getAffectedThreads()); gatherThreads(params, sco.getAffectedThreads());
impl.session.getListeners().fire.event(impl.session, targetEventThread, broadcast().event(impl.session, targetEventThread, TargetEventType.SIGNAL,
TargetEventType.SIGNAL, reason.desc(), params); reason.desc(), params);
} }
else { else {
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
gatherThreads(params, sco.getAffectedThreads()); gatherThreads(params, sco.getAffectedThreads());
impl.session.getListeners().fire.event(impl.session, targetEventThread, broadcast().event(impl.session, targetEventThread, TargetEventType.STOPPED,
TargetEventType.STOPPED, reason.desc(), params); reason.desc(), params);
} }
} }
@ -445,8 +444,8 @@ public class GdbModelTargetInferior
threads.getTargetThread(sco.getAffectedThreads().iterator().next()); threads.getTargetThread(sco.getAffectedThreads().iterator().next());
} }
if (targetEventThread != null) { if (targetEventThread != null) {
impl.session.getListeners().fire.event(impl.session, targetEventThread, broadcast().event(impl.session, targetEventThread, TargetEventType.RUNNING,
TargetEventType.RUNNING, "Running", params); "Running", params);
invalidateMemoryAndRegisterCaches(); invalidateMemoryAndRegisterCaches();
} }
} }

View file

@ -71,7 +71,7 @@ public class GdbModelTargetInferiorContainer
@Override @Override
public void inferiorExited(GdbInferior inf, GdbCause cause) { public void inferiorExited(GdbInferior inf, GdbCause cause) {
GdbModelTargetInferior inferior = getTargetInferior(inf); 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 " + inf.getId() + " exited code=" + inf.getExitCode(), List.of(inferior));
inferior.inferiorExited(inf.getExitCode()); inferior.inferiorExited(inf.getExitCode());
} }
@ -89,7 +89,7 @@ public class GdbModelTargetInferiorContainer
public void threadCreated(GdbThread thread, GdbCause cause) { public void threadCreated(GdbThread thread, GdbCause cause) {
GdbModelTargetInferior inferior = getTargetInferior(thread.getInferior()); GdbModelTargetInferior inferior = getTargetInferior(thread.getInferior());
GdbModelTargetThread targetThread = inferior.threads.threadCreated(thread); 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)); "Thread " + thread.getId() + " started", List.of(targetThread));
} }
@ -98,7 +98,7 @@ public class GdbModelTargetInferiorContainer
GdbModelTargetInferior inferior = getTargetInferior(inf); GdbModelTargetInferior inferior = getTargetInferior(inf);
GdbModelTargetThread targetThread = GdbModelTargetThread targetThread =
inferior.threads.getCachedElements().get(GdbModelTargetThread.indexThread(threadId)); 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)); "Thread " + threadId + " exited", List.of(targetThread));
inferior.threads.threadExited(threadId); inferior.threads.threadExited(threadId);
} }
@ -107,7 +107,7 @@ public class GdbModelTargetInferiorContainer
public void libraryLoaded(GdbInferior inf, String name, GdbCause cause) { public void libraryLoaded(GdbInferior inf, String name, GdbCause cause) {
GdbModelTargetInferior inferior = getTargetInferior(inf); GdbModelTargetInferior inferior = getTargetInferior(inf);
GdbModelTargetModule module = inferior.modules.libraryLoaded(name); 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)); "Library " + name + " loaded", List.of(module));
} }
@ -115,7 +115,7 @@ public class GdbModelTargetInferiorContainer
public void libraryUnloaded(GdbInferior inf, String name, GdbCause cause) { public void libraryUnloaded(GdbInferior inf, String name, GdbCause cause) {
GdbModelTargetInferior inferior = getTargetInferior(inf); GdbModelTargetInferior inferior = getTargetInferior(inf);
GdbModelTargetModule module = inferior.modules.getTargetModuleIfPresent(name); 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)); "Library " + name + " unloaded", List.of(module));
inferior.modules.libraryUnloaded(name); inferior.modules.libraryUnloaded(name);
} }

View file

@ -159,7 +159,7 @@ public class GdbModelTargetProcessMemory
throw new DebuggerMemoryAccessException("Cannot read at " + address); throw new DebuggerMemoryAccessException("Cannot read at " + address);
} }
byte[] content = Arrays.copyOf(buf.array(), (int) s.length()); byte[] content = Arrays.copyOf(buf.array(), (int) s.length());
listeners.fire.memoryUpdated(this, address, content); broadcast().memoryUpdated(this, address, content);
return content; return content;
}).exceptionally(e -> { }).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e); e = AsyncUtils.unwrapThrowable(e);
@ -167,10 +167,10 @@ public class GdbModelTargetProcessMemory
GdbCommandError gce = (GdbCommandError) e; GdbCommandError gce = (GdbCommandError) e;
e = new DebuggerMemoryAccessException( e = new DebuggerMemoryAccessException(
"Cannot read at " + address + ": " + gce.getInfo().getString("msg")); "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) { if (e instanceof DebuggerMemoryAccessException) {
listeners.fire.memoryReadError(this, range, (DebuggerMemoryAccessException) e); broadcast().memoryReadError(this, range, (DebuggerMemoryAccessException) e);
} }
return ExceptionUtils.rethrow(e); return ExceptionUtils.rethrow(e);
}); });
@ -186,12 +186,12 @@ public class GdbModelTargetProcessMemory
CompletableFuture<Void> future = CompletableFuture<Void> future =
inferior.writeMemory(address.getOffset(), ByteBuffer.wrap(data)); inferior.writeMemory(address.getOffset(), ByteBuffer.wrap(data));
return impl.gateFuture(future.thenAccept(__ -> { return impl.gateFuture(future.thenAccept(__ -> {
listeners.fire.memoryUpdated(this, address, data); broadcast().memoryUpdated(this, address, data);
})); }));
} }
protected void invalidateMemoryCaches() { protected void invalidateMemoryCaches() {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
public void memoryChanged(long offset, int len) { public void memoryChanged(long offset, int len) {

View file

@ -147,7 +147,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
default: default:
throw new AssertionError(); throw new AssertionError();
} }
listeners.fire.consoleOutput(this, dbgChannel, out); broadcast().consoleOutput(this, dbgChannel, out);
} }
@Override @Override

View file

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

View file

@ -117,7 +117,7 @@ public class GdbModelTargetStackFrameRegisterContainer
elements.get(regName).stateChanged(bytes); elements.get(regName).stateChanged(bytes);
} }
this.regValues = result; this.regValues = result;
listeners.fire.registersUpdated(this, result); broadcast().registersUpdated(this, result);
return result; return result;
}); });
} }

View file

@ -22,11 +22,9 @@ import java.util.concurrent.CompletableFuture;
import agent.lldb.manager.impl.LldbManagerImpl; import agent.lldb.manager.impl.LldbManagerImpl;
import agent.lldb.model.AbstractLldbModel; import agent.lldb.model.AbstractLldbModel;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.agent.SpiTargetObject; import ghidra.dbg.agent.SpiTargetObject;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.util.datastruct.ListenerSet;
public interface LldbModelTargetObject extends SpiTargetObject { public interface LldbModelTargetObject extends SpiTargetObject {
@ -59,8 +57,6 @@ public interface LldbModelTargetObject extends SpiTargetObject {
public CompletableFuture<List<TargetObject>> requestNativeElements(); public CompletableFuture<List<TargetObject>> requestNativeElements();
public ListenerSet<DebuggerModelListener> getListeners();
public LldbModelTargetSession getParentSession(); public LldbModelTargetSession getParentSession();
public LldbModelTargetProcess getParentProcess(); public LldbModelTargetProcess getParentProcess();

View file

@ -53,7 +53,7 @@ public interface LldbModelTargetSession extends //
if (output.contains("loaded *kernel* extension dll for usermode")) { if (output.contains("loaded *kernel* extension dll for usermode")) {
return; return;
} }
getListeners().fire.consoleOutput(getProxy(), chan, output); broadcast().consoleOutput(getProxy(), chan, output);
} }
@Override @Override

View file

@ -47,8 +47,7 @@ public class LldbModelTargetBreakpointContainerImpl extends LldbModelTargetObjec
TargetBreakpointKind.SW_EXECUTE, TargetBreakpointKind.SW_EXECUTE,
//TargetBreakpointKind.HW_EXECUTE, //TargetBreakpointKind.HW_EXECUTE,
TargetBreakpointKind.READ, TargetBreakpointKind.READ,
TargetBreakpointKind.WRITE TargetBreakpointKind.WRITE);
);
private final SBTarget session; private final SBTarget session;
@ -98,22 +97,25 @@ public class LldbModelTargetBreakpointContainerImpl extends LldbModelTargetObjec
BigInteger bptId = t.GetStopReasonDataAtIndex(0); BigInteger bptId = t.GetStopReasonDataAtIndex(0);
BigInteger locId = t.GetStopReasonDataAtIndex(1); BigInteger locId = t.GetStopReasonDataAtIndex(1);
if (bpt.GetID() == bptId.intValue()) { if (bpt.GetID() == bptId.intValue()) {
LldbModelTargetProcess targetProcess = (LldbModelTargetProcess) getModel().getModelObject(eventProcess); LldbModelTargetProcess targetProcess =
(LldbModelTargetProcess) getModel().getModelObject(eventProcess);
LldbModelTargetThread targetThread = LldbModelTargetThread targetThread =
targetProcess.getThreads().getTargetThread(t); targetProcess.getThreads().getTargetThread(t);
LldbModelTargetBreakpointSpec spec = getTargetBreakpointSpec(bpt); LldbModelTargetBreakpointSpec spec = getTargetBreakpointSpec(bpt);
if (spec == null) { if (spec == null) {
Msg.error(this, "Stopped for breakpoint unknown to the agent: " + bpt + " (pc=" + Msg.error(this,
targetThread + ")"); "Stopped for breakpoint unknown to the agent: " + bpt + " (pc=" +
targetThread + ")");
return; return;
} }
LldbModelTargetBreakpointLocation loc = spec.findLocation(locId); LldbModelTargetBreakpointLocation loc = spec.findLocation(locId);
if (loc == null) { if (loc == null) {
Msg.warn(this, 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() { public SBTarget getSession() {
return session; return session;
} }

View file

@ -87,12 +87,12 @@ public class LldbModelTargetMemoryContainerImpl extends LldbModelTargetObjectImp
if (range == null) { if (range == null) {
throw new DebuggerMemoryAccessException("Cannot read at " + address); 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()); return Arrays.copyOf(buf.array(), (int) range.getLength());
} }
private void writeAssist(Address address, byte[] data) { private void writeAssist(Address address, byte[] data) {
listeners.fire.memoryUpdated(getProxy(), address, data); broadcast().memoryUpdated(getProxy(), address, data);
} }
@Override @Override

View file

@ -72,8 +72,8 @@ public class LldbModelTargetModuleContainerImpl extends LldbModelTargetObjectImp
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread); (TargetThread) getModel().getModelObject(thread);
changeElements(List.of(), List.of(targetModule), Map.of(), "Loaded"); 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)); "Library " + info.getModuleName(index) + " loaded", List.of(targetModule));
} }
@Override @Override
@ -84,8 +84,8 @@ public class LldbModelTargetModuleContainerImpl extends LldbModelTargetObjectImp
SBThread thread = getManager().getEventThread(); SBThread thread = getManager().getEventThread();
TargetThread eventThread = TargetThread eventThread =
(TargetThread) getModel().getModelObject(thread); (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)); "Library " + info.getModuleName(index) + " unloaded", List.of(targetModule));
LldbModelImpl impl = (LldbModelImpl) model; LldbModelImpl impl = (LldbModelImpl) model;
impl.deleteModelObject(targetModule.getModule()); impl.deleteModelObject(targetModule.getModule());
} }

View file

@ -70,7 +70,7 @@ public class LldbModelTargetProcessContainerImpl extends LldbModelTargetObjectIm
LldbModelTargetProcess process = getTargetProcess(proc); LldbModelTargetProcess process = getTargetProcess(proc);
changeElements(List.of(), List.of(process), Map.of(), "Added"); changeElements(List.of(), List.of(process), Map.of(), "Added");
process.processStarted(proc); 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(), "Process " + DebugClient.getId(proc) + " started " + process.getName(),
List.of(process)); List.of(process));
} }

View file

@ -153,7 +153,7 @@ public class LldbModelTargetProcessImpl extends LldbModelTargetObjectImpl
threads.requestElements(true); threads.requestElements(true);
StopReason stopReason = getManager().getCurrentThread().GetStopReason(); StopReason stopReason = getManager().getCurrentThread().GetStopReason();
if (!stopReason.equals(StopReason.eStopReasonPlanComplete)) { if (!stopReason.equals(StopReason.eStopReasonPlanComplete)) {
memory.requestElements(true); memory.requestElements(true);
} }
} }
} }
@ -245,7 +245,7 @@ public class LldbModelTargetProcessImpl extends LldbModelTargetObjectImpl
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, // STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
EXIT_CODE_ATTRIBUTE_NAME, exitDesc // EXIT_CODE_ATTRIBUTE_NAME, exitDesc //
), "Exited"); ), "Exited");
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED, broadcast().event(getProxy(), null, TargetEventType.PROCESS_EXITED,
"Process " + DebugClient.getId(getProcess()) + " exited code=" + exitDesc, "Process " + DebugClient.getId(getProcess()) + " exited code=" + exitDesc,
List.of(getProxy())); 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.LldbModelTargetRegister;
import agent.lldb.model.iface2.LldbModelTargetStackFrameRegisterBank; import agent.lldb.model.iface2.LldbModelTargetStackFrameRegisterBank;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.error.DebuggerRegisterAccessException; import ghidra.dbg.error.DebuggerRegisterAccessException;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode; import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(
name = "RegisterValueBank", name = "RegisterValueBank",
@ -66,6 +64,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl
requestElements(false); requestElements(false);
} }
@Override
public String getDescription(int level) { public String getDescription(int level) {
SBStream stream = new SBStream(); SBStream stream = new SBStream();
SBValue val = (SBValue) getModelObject(); SBValue val = (SBValue) getModelObject();
@ -105,6 +104,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl
return new LldbModelTargetStackFrameRegisterImpl(this, register); return new LldbModelTargetStackFrameRegisterImpl(this, register);
} }
@Override
public void threadStateChangedSpecific(StateType state, LldbReason reason) { public void threadStateChangedSpecific(StateType state, LldbReason reason) {
if (state.equals(StateType.eStateStopped)) { if (state.equals(StateType.eStateStopped)) {
requestElements(false).thenAccept(__ -> { requestElements(false).thenAccept(__ -> {
@ -127,12 +127,9 @@ public class LldbModelTargetStackFrameRegisterBankImpl
byte[] bytes = register.getBytes(); byte[] bytes = register.getBytes();
result.put(regname, bytes); result.put(regname, bytes);
} }
ListenerSet<DebuggerModelListener> listeners = getListeners(); //if (getName().contains("General")) {
if (listeners != null) { broadcast().registersUpdated(this, result);
//if (getName().contains("General")) { //}
listeners.fire.registersUpdated(this, result);
//}
}
return CompletableFuture.completedFuture(result); return CompletableFuture.completedFuture(result);
} }
@ -149,7 +146,7 @@ public class LldbModelTargetStackFrameRegisterBankImpl
BigInteger val = new BigInteger(1, ent.getValue()); BigInteger val = new BigInteger(1, ent.getValue());
reg.getRegister().SetValueFromCString(val.toString()); reg.getRegister().SetValueFromCString(val.toString());
} }
getListeners().fire.registersUpdated(getProxy(), values); broadcast().registersUpdated(getProxy(), values);
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }

View file

@ -65,7 +65,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp
changeElements(List.of(), List.of(targetThread), Map.of(), "Created"); changeElements(List.of(), List.of(targetThread), Map.of(), "Created");
targetThread.threadStateChangedSpecific(StateType.eStateConnected, targetThread.threadStateChangedSpecific(StateType.eStateConnected,
LldbReason.getReason(null)); 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)); "Thread " + DebugClient.getId(thread) + " started", List.of(targetThread));
} }
@ -81,7 +81,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp
LldbReason reason) { LldbReason reason) {
LldbModelTargetThread targetThread = getTargetThread(thread); LldbModelTargetThread targetThread = getTargetThread(thread);
TargetEventType eventType = getEventType(state, cause, reason); 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)); "Thread " + DebugClient.getId(thread) + " state changed", List.of(targetThread));
targetThread.threadStateChangedSpecific(state, reason); targetThread.threadStateChangedSpecific(state, reason);
} }
@ -94,7 +94,7 @@ public class LldbModelTargetThreadContainerImpl extends LldbModelTargetObjectImp
String threadId = LldbModelTargetThreadImpl.indexThread(thread); String threadId = LldbModelTargetThreadImpl.indexThread(thread);
LldbModelTargetThread targetThread = (LldbModelTargetThread) getMapObject(thread); LldbModelTargetThread targetThread = (LldbModelTargetThread) getMapObject(thread);
if (targetThread != null) { if (targetThread != null) {
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED, broadcast().event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
"Thread " + threadId + " exited", List.of(targetThread)); "Thread " + threadId + " exited", List.of(targetThread));
} }
changeElements(List.of( // changeElements(List.of( //

View file

@ -71,7 +71,7 @@ public interface GadpClientTargetBreakpointSpecContainer
Path bptPath = evt.getEffective(); Path bptPath = evt.getEffective();
TargetBreakpointLocation breakpoint = bptPath == null ? null TargetBreakpointLocation breakpoint = bptPath == null ? null
: getModel().getProxy(bptPath.getEList(), true).as(TargetBreakpointLocation.class); : 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 (spec instanceof GadpClientTargetBreakpointSpec) {
// If I don't have a cached proxy, then I don't have any listeners // If I don't have a cached proxy, then I don't have any listeners
GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec; GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec;

View file

@ -33,6 +33,6 @@ public interface GadpClientTargetEventScope extends GadpClientTargetObject, Targ
String description = evt.getDescription(); String description = evt.getDescription();
List<Object> parameters = List<Object> parameters =
GadpValueUtils.getValues(getModel(), evt.getParametersList()); 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(); byte[] data = evt.getContent().toByteArray();
DelegateGadpClientTargetObject delegate = getDelegate(); DelegateGadpClientTargetObject delegate = getDelegate();
delegate.getMemoryCache(address.getAddressSpace()).updateMemory(address.getOffset(), data); 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) @GadpEventHandler(Gadp.EventNotification.EvtCase.MEMORY_ERROR_EVENT)
@ -98,7 +98,6 @@ public interface GadpClientTargetMemory extends GadpClientTargetObject, TargetMe
AddressRange range = GadpValueUtils.getAddressRange(getModel(), evt.getRange()); AddressRange range = GadpValueUtils.getAddressRange(getModel(), evt.getRange());
String message = evt.getMessage(); String message = evt.getMessage();
// Errors are not cached, but recorded in trace // Errors are not cached, but recorded in trace
getDelegate().getListeners().fire.memoryReadError(this, range, broadcast().memoryReadError(this, range, new DebuggerMemoryAccessException(message));
new DebuggerMemoryAccessException(message));
} }
} }

View file

@ -56,8 +56,7 @@ public interface GadpClientTargetObject extends SpiTargetObject {
int channelIndex = evt.getChannel(); int channelIndex = evt.getChannel();
Channel[] allChannels = Channel.values(); Channel[] allChannels = Channel.values();
if (0 <= channelIndex && channelIndex < allChannels.length) { if (0 <= channelIndex && channelIndex < allChannels.length) {
getDelegate().getListeners().fire.consoleOutput(this, allChannels[channelIndex], broadcast().consoleOutput(this, allChannels[channelIndex], evt.getData().toByteArray());
evt.getData().toByteArray());
} }
else { else {
Msg.error(this, "Received output for unknown channel " + channelIndex + ": " + 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()); Map<String, byte[]> updates = GadpValueUtils.getRegisterValueMap(evt.getValueList());
DelegateGadpClientTargetObject delegate = getDelegate(); DelegateGadpClientTargetObject delegate = getDelegate();
delegate.getRegisterCache().putAll(updates); 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 @Test
public void testGetAvailableWithObjectGettingListener() throws Throwable { public void testGetAvailableWithObjectGettingListener() throws Throwable {
var l = new Object() {
TargetObject avail;
};
List<ElementsChangedInvocation> invocations = new ArrayList<>(); List<ElementsChangedInvocation> invocations = new ArrayList<>();
// Any listener which calls .get on a child ref would do.... // Any listener which calls .get on a child ref would do....
// This object-getting listener is the pattern that revealed this problem, though. // This object-getting listener is the pattern that revealed this problem, though.
@ -933,18 +936,21 @@ public class GadpClientServerTest implements AsyncTestUtils {
@Override @Override
public void elementsChanged(TargetObject parent, Collection<String> removed, public void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObject> added) { Map<String, ? extends TargetObject> added) {
if (parent != l.avail) {
return;
}
invocations.add(new ElementsChangedInvocation(parent, removed, added)); invocations.add(new ElementsChangedInvocation(parent, removed, added));
} }
}; };
AsynchronousSocketChannel socket = socketChannel(); AsynchronousSocketChannel socket = socketChannel();
try (ServerRunner runner = new ServerRunner()) { try (ServerRunner runner = new ServerRunner()) {
GadpClient client = new GadpClient("Test", socket); GadpClient client = new GadpClient("Test", socket);
client.addModelListener(listener);
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect, waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
runner.server.getLocalAddress())); runner.server.getLocalAddress()));
waitOn(client.connect()); waitOn(client.connect());
TargetObject avail = waitOn(client.fetchModelObject(List.of("Available"))); l.avail = waitOn(client.fetchModelObject(List.of("Available")));
avail.addListener(listener); Map<String, ? extends TargetObject> elements = waitOn(l.avail.fetchElements());
Map<String, ? extends TargetObject> elements = waitOn(avail.fetchElements());
Msg.debug(this, "Elements: " + elements); Msg.debug(this, "Elements: " + elements);
waitOn(client.close()); waitOn(client.close());
} }
@ -957,13 +963,18 @@ public class GadpClientServerTest implements AsyncTestUtils {
public void testFocus() throws Throwable { public void testFocus() throws Throwable {
// Interesting because it involves a non-object-valued attribute (link) // Interesting because it involves a non-object-valued attribute (link)
// Need to check callback as well as getAttributes // Need to check callback as well as getAttributes
var l = new Object() {
TargetObject session;
};
CompletableFuture<List<String>> focusPath = new CompletableFuture<>(); CompletableFuture<List<String>> focusPath = new CompletableFuture<>();
AtomicBoolean failed = new AtomicBoolean(); AtomicBoolean failed = new AtomicBoolean();
DebuggerModelListener focusListener = DebuggerModelListener focusListener =
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) { new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME) @AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) { public void focusChanged(TargetObject object, TargetObject focused) {
if (object != l.session) {
return;
}
Msg.info(this, "Focus changed to " + focused); Msg.info(this, "Focus changed to " + focused);
if (!focusPath.complete(focused.getPath())) { if (!focusPath.complete(focused.getPath())) {
failed.set(true); failed.set(true);
@ -976,8 +987,7 @@ public class GadpClientServerTest implements AsyncTestUtils {
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect, waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
runner.server.getLocalAddress())); runner.server.getLocalAddress()));
waitOn(client.connect()); waitOn(client.connect());
TargetObject session = waitOn(client.fetchModelObject(List.of())); l.session = waitOn(client.fetchModelObject(List.of()));
session.addListener(focusListener);
TargetObject procCont = waitOn(client.fetchModelObject(List.of("Processes"))); TargetObject procCont = waitOn(client.fetchModelObject(List.of("Processes")));
assertTrue(procCont.getInterfaceNames().contains("Launcher")); assertTrue(procCont.getInterfaceNames().contains("Launcher"));
TargetLauncher launcher = procCont.as(TargetLauncher.class); TargetLauncher launcher = procCont.as(TargetLauncher.class);
@ -1017,7 +1027,19 @@ public class GadpClientServerTest implements AsyncTestUtils {
@Test @Test
public void testSubscribeLaunchForChildrenChanged() throws Throwable { 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(); AsynchronousSocketChannel socket = socketChannel();
try (ServerRunner runner = new ServerRunner()) { try (ServerRunner runner = new ServerRunner()) {
@ -1026,10 +1048,9 @@ public class GadpClientServerTest implements AsyncTestUtils {
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect, waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
runner.server.getLocalAddress())); runner.server.getLocalAddress()));
waitOn(client.connect()); waitOn(client.connect());
TargetObject procContainer = waitOn(client.fetchModelObject(List.of("Processes"))); l.procContainer = waitOn(client.fetchModelObject(List.of("Processes")));
assertTrue(procContainer.getInterfaceNames().contains("Launcher")); assertTrue(l.procContainer.getInterfaceNames().contains("Launcher"));
procContainer.addListener(elemL); TargetLauncher launcher = l.procContainer.as(TargetLauncher.class);
TargetLauncher launcher = procContainer.as(TargetLauncher.class);
waitOn(launcher.launch( waitOn(launcher.launch(
Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, "/bin/echo Hello, World!"))); Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, "/bin/echo Hello, World!")));
waitOn(elemL.count.waitValue(1)); waitOn(elemL.count.waitValue(1));
@ -1038,7 +1059,7 @@ public class GadpClientServerTest implements AsyncTestUtils {
assertEquals(1, elemL.invocations.size()); assertEquals(1, elemL.invocations.size());
ElementsChangedInvocation eci = elemL.invocations.get(0); ElementsChangedInvocation eci = elemL.invocations.get(0);
assertEquals(procContainer, eci.parent); assertEquals(l.procContainer, eci.parent);
assertEquals(List.of(), List.copyOf(eci.removed)); assertEquals(List.of(), List.copyOf(eci.removed));
assertEquals(1, eci.added.size()); assertEquals(1, eci.added.size());
Entry<String, ? extends TargetObject> ent = eci.added.entrySet().iterator().next(); Entry<String, ? extends TargetObject> ent = eci.added.entrySet().iterator().next();
@ -1107,20 +1128,31 @@ public class GadpClientServerTest implements AsyncTestUtils {
@Test @Test
public void testReplaceAttribute() throws Throwable { 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(); try (AsynchronousSocketChannel socket = socketChannel();
ServerRunner runner = new ServerRunner()) { ServerRunner runner = new ServerRunner()) {
GadpClient client = new GadpClient("Test", socket); GadpClient client = new GadpClient("Test", socket);
client.addModelListener(attrL);
waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect, waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect,
runner.server.getLocalAddress())); runner.server.getLocalAddress()));
waitOn(client.connect()); waitOn(client.connect());
TargetObject echoAvail = l.echoAvail = waitOn(client.fetchModelObject(PathUtils.parse("Available[1]")));
waitOn(client.fetchModelObject(PathUtils.parse("Available[1]")));
echoAvail.addListener(attrL);
assertEquals(Map.ofEntries(Map.entry("pid", 1), Map.entry("cmd", "echo"), 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 = TestGadpTargetAvailable ssEchoAvail =
runner.server.model.session.available.getCachedElements().get("1"); runner.server.model.session.available.getCachedElements().get("1");
@ -1132,10 +1164,10 @@ public class GadpClientServerTest implements AsyncTestUtils {
waitOn(attrL.count.waitValue(1)); waitOn(attrL.count.waitValue(1));
assertEquals(Map.ofEntries(Map.entry("cmd", "echo"), Map.entry("args", "Hello, World!"), 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); AttributesChangedInvocation changed = Unique.assertOne(attrL.invocations);
assertSame(echoAvail, changed.parent); assertSame(l.echoAvail, changed.parent);
assertEquals(Set.of("pid"), Set.copyOf(changed.removed)); assertEquals(Set.of("pid"), Set.copyOf(changed.removed));
assertEquals(Map.ofEntries(Map.entry("args", "Hello, World!")), changed.added); 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 com.sun.jdi.ReferenceType;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -90,9 +89,6 @@ public class JdiModelTargetClassContainer extends JdiModelTargetObjectImpl {
} }
public CompletableFuture<?> refreshInternal() { public CompletableFuture<?> refreshInternal() {
if (!isObserved()) {
return AsyncUtils.NIL;
}
return doRefresh().exceptionally(ex -> { return doRefresh().exceptionally(ex -> {
Msg.error(this, "Problem refreshing vm's classes", ex); Msg.error(this, "Problem refreshing vm's classes", ex);
return null; return null;

View file

@ -23,7 +23,6 @@ import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.Connector;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -96,9 +95,6 @@ public class JdiModelTargetConnectorContainer extends JdiModelTargetObjectImpl {
} }
public CompletableFuture<?> refreshInternal() { public CompletableFuture<?> refreshInternal() {
if (!isObserved()) {
return AsyncUtils.NIL;
}
return doRefresh().exceptionally(ex -> { return doRefresh().exceptionally(ex -> {
Msg.error(this, "Problem refreshing inferior's modules", ex); Msg.error(this, "Problem refreshing inferior's modules", ex);
return null; return null;

View file

@ -22,7 +22,6 @@ import java.util.stream.Collectors;
import com.sun.jdi.ModuleReference; import com.sun.jdi.ModuleReference;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.error.DebuggerUserException; import ghidra.dbg.error.DebuggerUserException;
import ghidra.dbg.target.TargetModule; import ghidra.dbg.target.TargetModule;
import ghidra.dbg.target.TargetModuleContainer; import ghidra.dbg.target.TargetModuleContainer;
@ -136,9 +135,6 @@ public class JdiModelTargetModuleContainer extends JdiModelTargetObjectImpl
} }
public CompletableFuture<?> refreshInternal() { public CompletableFuture<?> refreshInternal() {
if (!isObserved()) {
return AsyncUtils.NIL;
}
return doRefresh().exceptionally(ex -> { return doRefresh().exceptionally(ex -> {
Msg.error(this, "Problem refreshing inferior's modules", ex); Msg.error(this, "Problem refreshing inferior's modules", ex);
return null; return null;

View file

@ -106,7 +106,7 @@ public class JdiModelTargetProcess extends JdiModelTargetObjectImpl
default: default:
throw new AssertionError(); throw new AssertionError();
} }
listeners.fire.consoleOutput(this, channel, out); broadcast().consoleOutput(this, channel, out);
} }
private void readStream(InputStream in, TargetConsole.Channel channel) { 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 com.sun.jdi.Location;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetRegisterBank; import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.target.TargetRegisterContainer; import ghidra.dbg.target.TargetRegisterContainer;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
@ -121,7 +120,7 @@ public class JdiModelTargetRegisterContainer extends JdiModelTargetObjectImpl
map.put(retAddr.getIndex(), bytes); map.put(retAddr.getIndex(), bytes);
} }
if (!map.isEmpty()) { if (!map.isEmpty()) {
listeners.fire.registersUpdated(this, map); broadcast().registersUpdated(this, map);
} }
return CompletableFuture.completedFuture(map); return CompletableFuture.completedFuture(map);
} }
@ -133,13 +132,10 @@ public class JdiModelTargetRegisterContainer extends JdiModelTargetObjectImpl
} }
public void invalidateRegisterCaches() { public void invalidateRegisterCaches() {
listeners.fire.invalidateCacheRequested(this); broadcast().invalidateCacheRequested(this);
} }
protected CompletableFuture<?> update() { protected CompletableFuture<?> update() {
if (!isObserved()) {
return AsyncUtils.NIL;
}
return fetchElements(true).exceptionally(e -> { return fetchElements(true).exceptionally(e -> {
Msg.error(this, "Could not update registers " + this + " on STOPPED"); Msg.error(this, "Could not update registers " + this + " on STOPPED");
return null; return null;

View file

@ -125,12 +125,12 @@ public class JdiModelTargetSectionContainer extends JdiModelTargetObjectImpl
bytes[i] = (byte) 0xFF; bytes[i] = (byte) 0xFF;
} }
} }
listeners.fire.memoryUpdated(this, address, bytes); broadcast().memoryUpdated(this, address, bytes);
return CompletableFuture.completedFuture(bytes); return CompletableFuture.completedFuture(bytes);
} }
if (addressSpace.equals(impl.getAddressSpace("constantPool"))) { if (addressSpace.equals(impl.getAddressSpace("constantPool"))) {
byte[] bytes = constantPool.getPool(); byte[] bytes = constantPool.getPool();
listeners.fire.memoryUpdated(this, address, bytes); broadcast().memoryUpdated(this, address, bytes);
return CompletableFuture.completedFuture(bytes); return CompletableFuture.completedFuture(bytes);
} }
throw new RuntimeException(); throw new RuntimeException();

View file

@ -20,7 +20,6 @@ import java.util.concurrent.CompletableFuture;
import com.sun.jdi.*; import com.sun.jdi.*;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetStack; import ghidra.dbg.target.TargetStack;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -104,9 +103,6 @@ public class JdiModelTargetStack extends JdiModelTargetObjectImpl
* @return null * @return null
*/ */
protected CompletableFuture<?> update() { protected CompletableFuture<?> update() {
if (!isObserved()) {
return AsyncUtils.NIL;
}
return fetchElements(true).exceptionally(e -> { return fetchElements(true).exceptionally(e -> {
Msg.error(this, "Could not update stack " + this + " on STOPPED"); Msg.error(this, "Could not update stack " + this + " on STOPPED");
return null; return null;
@ -114,6 +110,6 @@ public class JdiModelTargetStack extends JdiModelTargetObjectImpl
} }
public void invalidateRegisterCaches() { 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); TargetExecutionState targetState = targetThread.convertState(state);
targetThread.threadStateChanged(targetState); targetThread.threadStateChanged(targetState);
TargetEventType eventType = getEventType(reason); TargetEventType eventType = getEventType(reason);
getListeners().fire.event(this, targetThread, eventType, broadcast().event(this, targetThread, eventType,
"Thread " + targetThread.getName() + " state changed", List.of(targetThread)); "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.Msg;
import ghidra.util.datastruct.WeakValueHashMap; import ghidra.util.datastruct.WeakValueHashMap;
@TargetObjectSchemaInfo(name = "VMContainer", elements = { // @TargetObjectSchemaInfo(
@TargetElementType(type = JdiModelTargetVM.class) // name = "VMContainer",
}, attributes = { // elements = { //
@TargetAttributeType(type = Void.class) // @TargetElementType(type = JdiModelTargetVM.class) //
}, canonicalContainer = true) },
attributes = { //
@TargetAttributeType(type = Void.class) //
},
canonicalContainer = true)
public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
implements JdiEventsListenerAdapter { implements JdiEventsListenerAdapter {
@ -56,7 +60,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
// TODO: Move PROCESS_CREATED here to restore proper order of event reporting // TODO: Move PROCESS_CREATED here to restore proper order of event reporting
// Pending some client-side changes to handle architecture selection, though. // Pending some client-side changes to handle architecture selection, though.
target.started(vm.name()).thenAccept(__ -> { 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)); "VM " + vm.name() + " started " + vm.process() + " pid=" + vm.name(), List.of(vm));
}).exceptionally(ex -> { }).exceptionally(ex -> {
Msg.error(this, "Could not notify vm started", 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) { public void vmDied(VMDeathEvent event, JdiCause cause) {
VirtualMachine vm = event.virtualMachine(); VirtualMachine vm = event.virtualMachine();
JdiModelTargetVM tgtVM = vmsById.get(vm.name()); JdiModelTargetVM tgtVM = vmsById.get(vm.name());
session.getListeners().fire.event(session, null, TargetEventType.PROCESS_EXITED, broadcast().event(session, null, TargetEventType.PROCESS_EXITED, "VM " + vm.name(),
"VM " + vm.name(), List.of(tgtVM)); List.of(tgtVM));
tgtVM.exited(vm); tgtVM.exited(vm);
synchronized (this) { synchronized (this) {
vmsById.remove(vm.name()); vmsById.remove(vm.name());
@ -99,7 +103,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
return; return;
} }
JdiModelTargetThread targetThread = vm.threads.threadCreated(thread); 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)); "Thread " + thread.name() + " started", List.of(targetThread));
} }
@ -108,7 +112,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl
ThreadReference thread = event.thread(); ThreadReference thread = event.thread();
JdiModelTargetVM tgtVM = vmsById.get(thread.virtualMachine().name()); JdiModelTargetVM tgtVM = vmsById.get(thread.virtualMachine().name());
JdiModelTargetThread targetThread = tgtVM.threads.threadsById.get(thread.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)); "Thread " + thread.name() + " exited", List.of(targetThread));
tgtVM.threads.threadExited(thread); tgtVM.threads.threadExited(thread);
} }

View file

@ -21,7 +21,6 @@ import java.util.concurrent.CompletableFuture;
import com.sun.jdi.*; import com.sun.jdi.*;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.agent.InvalidatableTargetObjectIf; import ghidra.dbg.agent.InvalidatableTargetObjectIf;
import ghidra.dbg.jdi.manager.JdiManager; import ghidra.dbg.jdi.manager.JdiManager;
import ghidra.dbg.jdi.model.*; 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;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.util.datastruct.ListenerSet;
public interface JdiModelTargetObject extends TargetObject, InvalidatableTargetObjectIf { 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 Delta<?, ?> changeAttributes(List<String> remove, Map<String, ?> add, String reason);
public ListenerSet<DebuggerModelListener> getListeners();
public default JdiModelTargetObject getInstance(Mirror object) { public default JdiModelTargetObject getInstance(Mirror object) {
JdiModelTargetObject targetObject = getTargetObject(object); JdiModelTargetObject targetObject = getTargetObject(object);
if (targetObject == null) { if (targetObject == null) {

View file

@ -53,7 +53,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
} }
@Override @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; OutputStream os;
switch (channel) { switch (channel) {
case STDOUT: case STDOUT:
@ -79,6 +82,9 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME) @AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
public void displayChanged(TargetObject object, String display) { public void displayChanged(TargetObject object, String display) {
if (object != targetConsole) {
return;
}
// TODO: Add setSubTitle(String) to InterpreterConsole // TODO: Add setSubTitle(String) to InterpreterConsole
if (guiConsole == null) { if (guiConsole == null) {
/** /**
@ -92,7 +98,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
} }
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME) @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) { if (guiConsole == null) {
/** /**
* Can happen during init. setPrompt will get called immediately after guiConsole is * Can happen during init. setPrompt will get called immediately after guiConsole is
@ -105,10 +114,11 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
@Override @Override
public void invalidated(TargetObject object, TargetObject branch, String reason) { public void invalidated(TargetObject object, TargetObject branch, String reason) {
if (object != targetConsole) {
return;
}
Swing.runLater(() -> { Swing.runLater(() -> {
if (object == targetConsole) { // Redundant consoleInvalidated();
consoleInvalidated();
}
}); });
} }
} }
@ -131,7 +141,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
T targetConsole) { T targetConsole) {
this.plugin = plugin; this.plugin = plugin;
this.targetConsole = targetConsole; this.targetConsole = targetConsole;
targetConsole.addListener(listener); targetConsole.getModel().addModelListener(listener);
} }
protected abstract CompletableFuture<Void> sendLine(String line); protected abstract CompletableFuture<Void> sendLine(String line);
@ -188,7 +198,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
.build(); .build();
guiConsole.addAction(actionPin); guiConsole.addAction(actionPin);
DockingAction interruptAction = InterpreterInterruptAction.builder(plugin) DockingAction interruptAction = InterpreterInterruptAction.builder(plugin)
.onAction(this::sendInterrupt) .onAction(this::sendInterrupt)
.build(); .build();
guiConsole.addAction(interruptAction); guiConsole.addAction(interruptAction);

View file

@ -20,7 +20,6 @@ import java.awt.Color;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -795,9 +794,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
@Override @Override
public void closeComponent() { public void closeComponent() {
TargetObject targetObject = getRoot().getTargetObject(); DebuggerObjectModel model = getModel();
if (targetObject != null) { if (model != null) {
targetObject.removeListener(getListener()); model.removeModelListener(getListener());
} }
super.closeComponent(); super.closeComponent();
} }

View file

@ -21,7 +21,6 @@ import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
@ -212,21 +211,12 @@ public class DummyTargetObject implements TargetObject {
return attributes; return attributes;
} }
@Override
public void addListener(DebuggerModelListener l) {
}
@Override
public void removeListener(DebuggerModelListener l) {
}
public String getJoinedPath() { public String getJoinedPath() {
return joinedPath; return joinedPath;
} }
@Override @Override
public boolean isValid() { public boolean isValid() {
// TODO Auto-generated method stub
return true; return true;
} }
@ -235,6 +225,7 @@ public class DummyTargetObject implements TargetObject {
return getName(); return getName();
} }
@Override
public Object getValue() { public Object getValue() {
return value; return value;
} }

View file

@ -404,7 +404,6 @@ public class ObjectTree implements ObjectPane {
DebuggerObjectsProvider provider = getProvider(); DebuggerObjectsProvider provider = getProvider();
ObjectContainer oc = node.getContainer(); ObjectContainer oc = node.getContainer();
provider.deleteFromMap(oc); provider.deleteFromMap(oc);
oc.getTargetObject().removeListener(provider.getListener());
nodeMap.remove(path(node.getContainer())); nodeMap.remove(path(node.getContainer()));
} }

View file

@ -78,90 +78,40 @@ public class DebuggerModelServicePlugin extends Plugin
// Since used for naming, no ':' allowed. // Since used for naming, no ':' allowed.
public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd-HH.mm.ss-z"); public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd-HH.mm.ss-z");
protected class ListenersForRemovalAndFocus { protected class ForRemovalAndFocusListener extends AnnotatedDebuggerAttributeListener {
protected final DebuggerObjectModel model; public ForRemovalAndFocusListener() {
protected TargetObject root; super(MethodHandles.lookup());
protected TargetFocusScope focusScope; }
protected DebuggerModelListener forRemoval = new DebuggerModelListener() { @Override
@Override public void invalidated(TargetObject object, TargetObject branch, String reason) {
public void invalidated(TargetObject object, TargetObject branch, String reason) { if (!object.isRoot()) {
synchronized (listenersByModel) { return;
ListenersForRemovalAndFocus listener = listenersByModel.remove(model); }
if (listener == null) { DebuggerObjectModel model = object.getModel();
return; synchronized (models) {
} models.remove(model);
assert listener.forRemoval == this; }
dispose(); model.removeModelListener(this);
} modelListeners.fire.elementRemoved(model);
modelListeners.fire.elementRemoved(model); if (currentModel == model) {
activateModel(null); activateModel(null);
} }
};
protected DebuggerModelListener forFocus =
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
proxy.fireFocusEvent(focused);
}
}
};
protected ListenersForRemovalAndFocus(DebuggerObjectModel model) {
this.model = model;
} }
protected CompletableFuture<Void> init() { @AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
return model.fetchModelRoot().thenCompose(r -> { public void focusChanged(TargetObject object, TargetObject focused) {
synchronized (this) { // I don't think I care which scope
this.root = r; fireFocusEvent(focused);
} List<DebuggerModelServiceProxyPlugin> copy;
boolean isInvalid = false; synchronized (proxies) {
try { copy = List.copyOf(proxies);
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) { for (DebuggerModelServiceProxyPlugin proxy : copy) {
savedRoot.removeListener(this.forRemoval); proxy.fireFocusEvent(focused);
}
if (savedFocusScope != null) {
savedFocusScope.removeListener(this.forFocus);
} }
} }
} };
protected class ListenerOnRecorders implements TraceRecorderListener { protected class ListenerOnRecorders implements TraceRecorderListener {
@Override @Override
@ -195,8 +145,11 @@ public class DebuggerModelServicePlugin extends Plugin
protected final Set<DebuggerModelFactory> factories = new HashSet<>(); protected final Set<DebuggerModelFactory> factories = new HashSet<>();
// Keep strong references to my listeners, or they'll get torched // Keep strong references to my listeners, or they'll get torched
protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel = //protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel =
new LinkedHashMap<>(); //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 Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>();
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners = protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
@ -262,8 +215,8 @@ public class DebuggerModelServicePlugin extends Plugin
@Override @Override
public Set<DebuggerObjectModel> getModels() { public Set<DebuggerObjectModel> getModels() {
synchronized (listenersByModel) { synchronized (models) {
return Set.copyOf(listenersByModel.keySet()); return Set.copyOf(models);
} }
} }
@ -286,20 +239,16 @@ public class DebuggerModelServicePlugin extends Plugin
@Override @Override
public boolean addModel(DebuggerObjectModel model) { public boolean addModel(DebuggerObjectModel model) {
Objects.requireNonNull(model); Objects.requireNonNull(model);
synchronized (listenersByModel) { synchronized (models) {
if (listenersByModel.containsKey(model)) { if (models.contains(model)) {
return false; return false;
} }
ListenersForRemovalAndFocus listener = new ListenersForRemovalAndFocus(model); model.addModelListener(forRemovalAndFocusListener);
listener.init().exceptionally(e -> { TargetObject root = model.getModelRoot();
Msg.error(this, "Could not add model " + model, e); if (!root.isValid()) {
synchronized (listenersByModel) { forRemovalAndFocusListener.invalidated(root, root,
listenersByModel.remove(model); "Invalidated before or during add to service");
listener.dispose(); }
}
return null;
});
listenersByModel.put(model, listener);
} }
modelListeners.fire.elementAdded(model); modelListeners.fire.elementAdded(model);
return true; return true;
@ -307,14 +256,12 @@ public class DebuggerModelServicePlugin extends Plugin
@Override @Override
public boolean removeModel(DebuggerObjectModel model) { public boolean removeModel(DebuggerObjectModel model) {
ListenersForRemovalAndFocus listener; model.removeModelListener(forRemovalAndFocusListener);
synchronized (listenersByModel) { synchronized (models) {
listener = listenersByModel.remove(model); if (!models.remove(model)) {
if (listener == null) {
return false; return false;
} }
} }
listener.dispose();
modelListeners.fire.elementRemoved(model); modelListeners.fire.elementRemoved(model);
return true; 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; package ghidra.dbg;
import java.lang.invoke.MethodHandles;
import java.util.*; import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -457,281 +455,6 @@ public enum DebugModelConventions {
return isProcessAlive(process) ? process : null; 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> public static class AsyncAttribute<T> extends AsyncReference<T, Void>
implements DebuggerModelListener { implements DebuggerModelListener {
private final TargetObject obj; private final TargetObject obj;
@ -741,7 +464,7 @@ public enum DebugModelConventions {
public AsyncAttribute(TargetObject obj, String name) { public AsyncAttribute(TargetObject obj, String name) {
this.name = name; this.name = name;
this.obj = obj; this.obj = obj;
obj.addListener(this); obj.getModel().addModelListener(this);
set((T) obj.getCachedAttribute(name), null); set((T) obj.getCachedAttribute(name), null);
obj.fetchAttribute(name).exceptionally(ex -> { obj.fetchAttribute(name).exceptionally(ex -> {
Msg.error(this, "Could not get initial value of " + name + " for " + obj, ex); Msg.error(this, "Could not get initial value of " + name + " for " + obj, ex);
@ -753,6 +476,9 @@ public enum DebugModelConventions {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void attributesChanged(TargetObject parent, Collection<String> removed, public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) { Map<String, ?> added) {
if (parent != obj) {
return;
}
if (added.containsKey(name)) { if (added.containsKey(name)) {
set((T) added.get(name), null); set((T) added.get(name), null);
} }
@ -768,7 +494,7 @@ public enum DebugModelConventions {
@Override @Override
public void dispose(Throwable reason) { public void dispose(Throwable reason) {
super.dispose(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 * 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.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
/** /**
* An abstract implementation of {@link TargetObject} * An abstract implementation of {@link TargetObject}
@ -63,15 +60,10 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
protected volatile boolean valid = true; 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, public <I> AbstractTargetObject(ProxyFactory<I> proxyFactory, I proxyInfo,
AbstractDebuggerObjectModel model, P parent, String key, String typeHint, AbstractDebuggerObjectModel model, P parent, String key, String typeHint,
TargetObjectSchema schema) { TargetObjectSchema schema) {
this.listeners = new ListenerSet<>(DebuggerModelListener.class, model.clientExecutor);
this.model = model; this.model = model;
listeners.addChained(model.listeners);
this.parent = parent; this.parent = parent;
if (parent == null) { if (parent == null) {
this.path = key == null ? List.of() : List.of(key); 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; assert proxy != null;
synchronized (model.lock) { synchronized (model.lock) {
model.objectCreated(proxy); 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; 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 @Override
public AbstractDebuggerObjectModel getModel() { public AbstractDebuggerObjectModel getModel() {
return model; return model;
@ -252,14 +231,7 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
} }
valid = false; valid = false;
model.objectInvalidated(getProxy()); model.objectInvalidated(getProxy());
listeners.fire.invalidated(getProxy(), branch, reason); broadcast().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;
});
} }
protected void doInvalidateElements(Map<String, ?> elems, String reason) { protected void doInvalidateElements(Map<String, ?> elems, String reason) {
@ -330,19 +302,8 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
} }
} }
/** @Override
* Get the listener set public DebuggerModelListener broadcast() {
* return model.listeners.fire;
* <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;
} }
} }

View file

@ -131,32 +131,6 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
parent.getSchema().getChildSchema(key)); 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 @Override
public CompletableFuture<Void> resync(boolean refreshAttributes, boolean refreshElements) { public CompletableFuture<Void> resync(boolean refreshAttributes, boolean refreshElements) {
return CompletableFuture.allOf(fetchAttributes(refreshAttributes), return CompletableFuture.allOf(fetchAttributes(refreshAttributes),
@ -303,7 +277,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
doInvalidateElements(delta.removed, reason); doInvalidateElements(delta.removed, reason);
if (!delta.isEmpty()) { if (!delta.isEmpty()) {
updateCallbackElements(delta); updateCallbackElements(delta);
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added); broadcast().elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
} }
} }
return delta; return delta;
@ -351,7 +325,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
doInvalidateElements(delta.removed, reason); doInvalidateElements(delta.removed, reason);
if (!delta.isEmpty()) { if (!delta.isEmpty()) {
updateCallbackElements(delta); updateCallbackElements(delta);
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added); broadcast().elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
} }
} }
return delta; return delta;
@ -493,7 +467,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
doInvalidateAttributes(delta.removed, reason); doInvalidateAttributes(delta.removed, reason);
if (!delta.isEmpty()) { if (!delta.isEmpty()) {
updateCallbackAttributes(delta); updateCallbackAttributes(delta);
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added); broadcast().attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
} }
} }
return delta; return delta;
@ -558,7 +532,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
doInvalidateAttributes(delta.removed, reason); doInvalidateAttributes(delta.removed, reason);
if (!delta.isEmpty()/* && !reason.equals("Default")*/) { if (!delta.isEmpty()/* && !reason.equals("Default")*/) {
updateCallbackAttributes(delta); updateCallbackAttributes(delta);
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added); broadcast().attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
} }
} }
return delta; return delta;

View file

@ -15,6 +15,7 @@
*/ */
package ghidra.dbg.agent; package ghidra.dbg.agent;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
public interface SpiTargetObject extends TargetObject, InvalidatableTargetObjectIf { public interface SpiTargetObject extends TargetObject, InvalidatableTargetObjectIf {
@ -35,4 +36,6 @@ public interface SpiTargetObject extends TargetObject, InvalidatableTargetObject
default SpiTargetObject getDelegate() { default SpiTargetObject getDelegate() {
return this; return this;
} }
DebuggerModelListener broadcast();
} }

View file

@ -1084,41 +1084,4 @@ public interface TargetObject extends Comparable<TargetObject> {
public default CompletableFuture<Void> invalidateCaches() { public default CompletableFuture<Void> invalidateCaches() {
return AsyncUtils.NIL; 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; package ghidra.dbg;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.List; import java.util.*;
import java.util.Map;
import org.junit.Test; import org.junit.Test;
@ -45,8 +44,17 @@ public class AnnotatedDebuggerAttributeListenerTest implements DebuggerModelTest
private void testChanged(TargetObject object, String disp) { private void testChanged(TargetObject object, String disp) {
display.set(disp, null); 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"); obj.changeAttributes(List.of(), Map.ofEntries(Map.entry("_test", "Testing")), "Because");
waitOn(display.waitValue("Testing")); waitOn(display.waitValue("Testing"));

View file

@ -309,7 +309,7 @@ public class DefaultDebuggerObjectModelTest implements AsyncTestUtils {
FakeTargetObject fakeA = new FakeTargetObject(model, model.root, "A"); FakeTargetObject fakeA = new FakeTargetObject(model, model.root, "A");
FakeTargetRegisterBank fakeA1rb = new FakeTargetRegisterBank(model, fakeA, "[1]"); 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"); fakeA.setElements(List.of(fakeA1rb), "Init");
model.root.setAttributes(List.of(fakeA), Map.of(), "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"); FakeTargetObject fakeA = new FakeTargetObject(model, model.root, "A");
FakeTargetRegisterBank fakeA1rb = new FakeTargetRegisterBank(model, fakeA, "[1]"); 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"); fakeA.setElements(List.of(fakeA1rb), "Init");
model.root.setAttributes(List.of(fakeA), Map.of(), "Init"); model.root.setAttributes(List.of(fakeA), Map.of(), "Init");
EventRecordingListener listener = new EventRecordingListener(); EventRecordingListener listener = new EventRecordingListener();

View file

@ -64,7 +64,7 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
} }
populateObjectValues(result, "Read registers"); populateObjectValues(result, "Read registers");
return model.gateFuture(descs.getModel().future(result).thenApply(__ -> { return model.gateFuture(descs.getModel().future(result).thenApply(__ -> {
listeners.fire.registersUpdated(this, result); broadcast().registersUpdated(this, result);
return result; return result;
})); }));
} }
@ -90,7 +90,7 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
} }
populateObjectValues(updates, "Write registers"); populateObjectValues(updates, "Write registers");
future.thenAccept(__ -> { future.thenAccept(__ -> {
listeners.fire.registersUpdated(this, updates); broadcast().registersUpdated(this, updates);
}); });
return model.gateFuture(future); return model.gateFuture(future);
} }

View file

@ -75,7 +75,7 @@ public class TestTargetInterpreter
} }
public void output(Channel channel, String line) { public void output(Channel channel, String line) {
listeners.fire.consoleOutput(this, channel, line + "\n"); broadcast().consoleOutput(this, channel, line + "\n");
} }
public void clearCalls() { public void clearCalls() {

View file

@ -52,7 +52,7 @@ public class TestTargetMemory
getMemory(address, data); getMemory(address, data);
CompletableFuture<byte[]> future = getModel().future(data); CompletableFuture<byte[]> future = getModel().future(data);
future.thenAccept(__ -> { future.thenAccept(__ -> {
listeners.fire.memoryUpdated(this, address, data); broadcast().memoryUpdated(this, address, data);
}); });
return future; return future;
} }
@ -67,7 +67,7 @@ public class TestTargetMemory
setMemory(address, data); setMemory(address, data);
CompletableFuture<Void> future = getModel().future(null); CompletableFuture<Void> future = getModel().future(null);
future.thenAccept(__ -> { future.thenAccept(__ -> {
listeners.fire.memoryUpdated(this, address, data); broadcast().memoryUpdated(this, address, data);
}); });
return future; return future;
} }

View file

@ -75,7 +75,7 @@ public class TestTargetSession extends DefaultTargetModelRoot
public void simulateStep(TestTargetThread eventThread) { public void simulateStep(TestTargetThread eventThread) {
eventThread.setState(TargetExecutionState.RUNNING); 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()); "Test thread completed a step", List.of());
eventThread.setState(TargetExecutionState.STOPPED); eventThread.setState(TargetExecutionState.STOPPED);
} }

View file

@ -119,7 +119,10 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
AsyncReference<String, Void> lastOut = new AsyncReference<>(); AsyncReference<String, Void> lastOut = new AsyncReference<>();
DebuggerModelListener l = new DebuggerModelListener() { DebuggerModelListener l = new DebuggerModelListener() {
@Override @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); String str = new String(out);
Msg.debug(this, "Got " + channel + " output: " + str); Msg.debug(this, "Got " + channel + " output: " + str);
for (String line : str.split("\n")) { 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); waitAcc(interpreter);
waitOn(interpreter.execute(cmd)); waitOn(interpreter.execute(cmd));
waitOn(lastOut.waitValue("test")); waitOn(lastOut.waitValue("test"));
@ -150,7 +153,10 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
try (CatchOffThread off = new CatchOffThread()) { try (CatchOffThread off = new CatchOffThread()) {
DebuggerModelListener l = new DebuggerModelListener() { DebuggerModelListener l = new DebuggerModelListener() {
@Override @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); String str = new String(out);
Msg.debug(this, "Got " + channel + " output: " + str); Msg.debug(this, "Got " + channel + " output: " + str);
if (!str.contains("test")) { if (!str.contains("test")) {
@ -159,7 +165,7 @@ public abstract class AbstractDebuggerModelInterpreterTest extends AbstractDebug
off.catching(() -> fail("Unexpected output:" + str)); off.catching(() -> fail("Unexpected output:" + str));
} }
}; };
interpreter.addListener(l); interpreter.getModel().addModelListener(l);
waitAcc(interpreter); waitAcc(interpreter);
String out = waitOn(interpreter.executeCapture(cmd)); String out = waitOn(interpreter.executeCapture(cmd));
// Not the greatest, but allow extra lines // 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 * child of [r1], to guarantee registersUpdated has happened
*/ */
toA.changeElements(List.of(), List.of(toA1), "Test"); 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"); 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 * 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()); FakeTargetRoot root = new FakeTargetRoot(model, "Root", model.getRootSchema());
FakeTargetObject processes = new FakeTargetObject(model, root, "Processes"); FakeTargetObject processes = new FakeTargetObject(model, root, "Processes");
FakeTargetProcess proc1 = new FakeTargetProcess(model, processes, "[1]"); FakeTargetProcess proc1 = new FakeTargetProcess(model, processes, "[1]");
root.getListeners().fire.event(root, null, TargetEventType.PROCESS_CREATED, root.broadcast()
"Process 1 created", List.of(proc1)); .event(root, null, TargetEventType.PROCESS_CREATED, "Process 1 created",
List.of(proc1));
FakeTargetObject p1threads = new FakeTargetObject(model, proc1, "Threads"); FakeTargetObject p1threads = new FakeTargetObject(model, proc1, "Threads");
FakeTargetThread thread1 = new FakeTargetThread(model, p1threads, "[1]"); FakeTargetThread thread1 = new FakeTargetThread(model, p1threads, "[1]");
root.getListeners().fire.event(root, thread1, TargetEventType.THREAD_CREATED, root.broadcast()
"Thread 1 created", List.of()); .event(root, thread1, TargetEventType.THREAD_CREATED, "Thread 1 created",
List.of());
p1threads.changeElements(List.of(), List.of(thread1), "Test"); p1threads.changeElements(List.of(), List.of(thread1), "Test");
proc1.changeAttributes(List.of(), List.of(p1threads), Map.of(), "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()); FakeTargetRoot root = new FakeTargetRoot(model, "Root", model.getRootSchema());
FakeTargetObject processes = new FakeTargetObject(model, root, "Processes"); FakeTargetObject processes = new FakeTargetObject(model, root, "Processes");
FakeTargetProcess proc1 = new FakeTargetProcess(model, processes, "[1]"); FakeTargetProcess proc1 = new FakeTargetProcess(model, processes, "[1]");
root.getListeners().fire.event(root, null, TargetEventType.PROCESS_CREATED, root.broadcast()
"Process 1 created", List.of(proc1)); .event(root, null, TargetEventType.PROCESS_CREATED, "Process 1 created",
List.of(proc1));
FakeTargetObject p1threads = new FakeTargetObject(model, proc1, "Threads"); FakeTargetObject p1threads = new FakeTargetObject(model, proc1, "Threads");
FakeTargetThread thread1 = new FakeTargetThread(model, p1threads, "[1]"); FakeTargetThread thread1 = new FakeTargetThread(model, p1threads, "[1]");
root.getListeners().fire.event(root, thread1, TargetEventType.THREAD_CREATED, root.broadcast()
"Thread 1 created", List.of()); .event(root, thread1, TargetEventType.THREAD_CREATED, "Thread 1 created",
List.of());
FakeTargetThread thread2 = new FakeTargetThread(model, p1threads, "[2]"); FakeTargetThread thread2 = new FakeTargetThread(model, p1threads, "[2]");
root.getListeners().fire.event(root, thread1, TargetEventType.THREAD_CREATED, root.broadcast()
"Thread 2 created", List.of()); .event(root, thread1, TargetEventType.THREAD_CREATED, "Thread 2 created",
List.of());
p1threads.changeElements(List.of(), List.of(thread2), "Test"); p1threads.changeElements(List.of(), List.of(thread2), "Test");
proc1.changeAttributes(List.of(), List.of(p1threads), Map.of(), "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.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory; import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.trace.util.MemoryAdapter; import ghidra.trace.util.MemoryAdapter;
import ghidra.util.LockHold;
import ghidra.util.MathUtilities; import ghidra.util.MathUtilities;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.NotFoundException; import ghidra.util.exception.NotFoundException;
@ -79,9 +80,11 @@ public abstract class AbstractDBTraceProgramViewMemory
} }
} }
protected synchronized void computeFullAdddressSet() { protected void computeFullAdddressSet() {
AddressSet temp = new AddressSet(); AddressSet temp = new AddressSet();
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress())); try (LockHold hold = program.trace.lockRead()) {
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
}
addressSet = temp; addressSet = temp;
} }

View file

@ -24,6 +24,7 @@ import com.google.common.cache.CacheBuilder;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.LockHold;
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
@ -74,10 +75,12 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
} }
@Override @Override
protected synchronized void recomputeAddressSet() { protected void recomputeAddressSet() {
AddressSet temp = new AddressSet(); AddressSet temp = new AddressSet();
// TODO: Performance test this try (LockHold hold = program.trace.lockRead()) {
forVisibleRegions(reg -> temp.add(reg.getRange())); // TODO: Performance test this
forVisibleRegions(reg -> temp.add(reg.getRange()));
}
addressSet = temp; 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 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 Class<P> iface;
private final Executor executor; private final Executor executor;
private Map<K, V> map = createMap(); 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 * 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(); 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() { public void clear() {
map.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();
}
} }