mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-2752: Remove TargetObject.addListener() and related
This commit is contained in:
parent
c301dd2c89
commit
50c7217635
78 changed files with 384 additions and 1261 deletions
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -110,7 +110,7 @@ public class GdbModelTargetStackFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void invalidateRegisterCaches() {
|
protected void invalidateRegisterCaches() {
|
||||||
listeners.fire.invalidateCacheRequested(this);
|
broadcast().invalidateCacheRequested(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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( //
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 + ": " +
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.debug.service.model;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
|
||||||
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
|
|
||||||
import ghidra.async.AsyncLazyMap;
|
|
||||||
import ghidra.async.AsyncUtils;
|
|
||||||
import ghidra.dbg.DebugModelConventions;
|
|
||||||
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
|
|
||||||
import ghidra.dbg.target.*;
|
|
||||||
import ghidra.program.model.address.*;
|
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.TriConsumer;
|
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
|
||||||
|
|
||||||
public class RecorderComposedMemory implements AbstractRecorderMemory {
|
|
||||||
|
|
||||||
private static final int BLOCK_SIZE = 4096;
|
|
||||||
private static final long BLOCK_MASK = -1L << 12;
|
|
||||||
|
|
||||||
protected final RecorderComposedMemory chain;
|
|
||||||
|
|
||||||
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
|
|
||||||
|
|
||||||
protected final Map<TargetMemoryRegion, TargetMemory> byRegion = new HashMap<>();
|
|
||||||
protected final AsyncLazyMap<TargetMemory, AllRequiredAccess> accessibilityByMemory =
|
|
||||||
new AsyncLazyMap<>(new HashMap<>(), this::fetchMemAccessibility) {
|
|
||||||
public AllRequiredAccess remove(TargetMemory key) {
|
|
||||||
AllRequiredAccess acc = super.remove(key);
|
|
||||||
if (acc != null) {
|
|
||||||
acc.removeChangeListener(getMemAccListeners().fire);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected CompletableFuture<AllRequiredAccess> fetchMemAccessibility(TargetMemory mem) {
|
|
||||||
return DebugModelConventions.trackAccessibility(mem).thenApply(acc -> {
|
|
||||||
acc.addChangeListener(getMemAccListeners().fire);
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get accessible memory, as viewed in the trace
|
|
||||||
*
|
|
||||||
* @param pred an additional predicate applied via "AND" with accessibility
|
|
||||||
* @param memMapper target-to-trace mapping utility
|
|
||||||
* @return the computed set
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
|
|
||||||
DebuggerMemoryMapper memMapper) {
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
// TODO: Might accomplish by using listeners and tracking the accessible set
|
|
||||||
AddressSet accessible = new AddressSet();
|
|
||||||
for (Entry<TargetMemoryRegion, TargetMemory> ent : byRegion.entrySet()) {
|
|
||||||
TargetMemory mem = ent.getValue();
|
|
||||||
if (!pred.test(mem)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
AllRequiredAccess acc = accessibilityByMemory.getCompletedMap().get(mem);
|
|
||||||
if (acc == null || !acc.getAllAccessibility()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
AddressRange traceRange = memMapper.targetToTraceTruncated(ent.getKey().getRange());
|
|
||||||
if (traceRange == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
accessible.add(traceRange);
|
|
||||||
}
|
|
||||||
return accessible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
||||||
private final ListenerSet<TriConsumer<Boolean, Boolean, Void>> memAccListeners =
|
|
||||||
new ListenerSet(TriConsumer.class);
|
|
||||||
|
|
||||||
public RecorderComposedMemory(AbstractRecorderMemory memory) {
|
|
||||||
this.chain = (RecorderComposedMemory) memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TargetMemory getMemory(Address address, int length) {
|
|
||||||
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
|
|
||||||
if (floor == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"address " + address + " is not in any known region");
|
|
||||||
}
|
|
||||||
Address max;
|
|
||||||
try {
|
|
||||||
max = address.addNoWrap(length - 1);
|
|
||||||
}
|
|
||||||
catch (AddressOverflowException e) {
|
|
||||||
throw new IllegalArgumentException("read extends beyond the address space");
|
|
||||||
}
|
|
||||||
if (!floor.getValue().getRange().contains(max)) {
|
|
||||||
throw new IllegalArgumentException("read extends beyond a single region");
|
|
||||||
}
|
|
||||||
return byRegion.get(floor.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addMemory(TargetMemory memory) {
|
|
||||||
// Do nothing
|
|
||||||
// This delegates by region, so need regions even if I have memory
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addRegion(TargetMemoryRegion region, TargetMemory memory) {
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
TargetMemory old = byRegion.put(region, memory);
|
|
||||||
assert old == null;
|
|
||||||
byMin.put(region.getRange().getMinAddress(), region);
|
|
||||||
accessibilityByMemory.get(memory).exceptionally(e -> {
|
|
||||||
e = AsyncUtils.unwrapThrowable(e);
|
|
||||||
Msg.error(this, "Could not track memory accessibility: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeMemory(TargetMemory invalid) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean removeRegion(TargetObject invalid) {
|
|
||||||
if (!(invalid instanceof TargetMemoryRegion)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
|
|
||||||
TargetMemory old = byRegion.remove(invRegion);
|
|
||||||
assert old != null;
|
|
||||||
byMin.remove(invRegion.getRange().getMinAddress());
|
|
||||||
if (!old.isValid() || !byRegion.containsValue(old)) {
|
|
||||||
accessibilityByMemory.remove(old);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
protected AllRequiredAccess findChainedMemoryAccess(TargetMemoryRegion region) {
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
TargetMemory mem = byRegion.get(region);
|
|
||||||
if (mem != null) {
|
|
||||||
return accessibilityByMemory.getCompletedMap().get(mem);
|
|
||||||
}
|
|
||||||
return chain == null ? null : chain.findChainedMemoryAccess(region);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
Entry<Address, TargetMemoryRegion> myFloor = byMin.floorEntry(address);
|
|
||||||
Entry<Address, TargetMemoryRegion> byChain =
|
|
||||||
chain == null ? null : chain.findChainedFloor(address);
|
|
||||||
if (byChain == null) {
|
|
||||||
return myFloor;
|
|
||||||
}
|
|
||||||
if (myFloor == null) {
|
|
||||||
return byChain;
|
|
||||||
}
|
|
||||||
int c = myFloor.getKey().compareTo(byChain.getKey());
|
|
||||||
if (c < 0) {
|
|
||||||
return byChain;
|
|
||||||
}
|
|
||||||
return myFloor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AddressRange align(Address address, int length) {
|
|
||||||
AddressSpace space = address.getAddressSpace();
|
|
||||||
long offset = address.getOffset();
|
|
||||||
Address start = space.getAddress(offset & BLOCK_MASK);
|
|
||||||
Address end = space.getAddress(((offset + length - 1) & BLOCK_MASK) + BLOCK_SIZE - 1);
|
|
||||||
return new AddressRangeImpl(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AddressRange alignWithLimit(Address address, int length,
|
|
||||||
TargetMemoryRegion limit) {
|
|
||||||
return align(address, length).intersect(limit.getRange());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AddressRange alignAndLimitToFloor(Address address, int length) {
|
|
||||||
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
|
|
||||||
if (floor == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return alignWithLimit(address, length, floor.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRange alignWithOptionalLimit(Address address, int length,
|
|
||||||
TargetMemoryRegion limit) {
|
|
||||||
if (limit == null) {
|
|
||||||
return alignAndLimitToFloor(address, length);
|
|
||||||
}
|
|
||||||
return alignWithLimit(address, length, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<byte[]> readMemory(Address address, int length) {
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
TargetMemory mem = getMemory(address, length);
|
|
||||||
if (mem != null) {
|
|
||||||
return mem.readMemory(address, length);
|
|
||||||
}
|
|
||||||
return CompletableFuture.completedFuture(new byte[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
|
|
||||||
synchronized (accessibilityByMemory) {
|
|
||||||
TargetMemory mem = getMemory(address, data.length);
|
|
||||||
if (mem != null) {
|
|
||||||
return mem.writeMemory(address, data);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("read starts outside any address space");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenerSet<TriConsumer<Boolean, Boolean, Void>> getMemAccListeners() {
|
|
||||||
return memAccListeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.debug.service.model;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import ghidra.async.AsyncLazyMap;
|
|
||||||
import ghidra.async.AsyncLazyMap.KeyedFuture;
|
|
||||||
import ghidra.async.AsyncUtils;
|
|
||||||
import ghidra.dbg.DebugModelConventions;
|
|
||||||
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
|
||||||
import ghidra.dbg.target.TargetRegisterBank;
|
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.TriConsumer;
|
|
||||||
|
|
||||||
public class RecorderComposedRegisterSet {
|
|
||||||
|
|
||||||
private DefaultTraceRecorder recorder;
|
|
||||||
|
|
||||||
protected final TriConsumer<Boolean, Boolean, Void> listenerRegAccChanged =
|
|
||||||
this::registerAccessibilityChanged;
|
|
||||||
|
|
||||||
protected void registerAccessibilityChanged(boolean old, boolean acc,
|
|
||||||
Void __) {
|
|
||||||
recorder.getListeners().fire.registerAccessibilityChanged(recorder);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final AsyncLazyMap<TargetRegisterBank, AllRequiredAccess> accessibilityByRegBank =
|
|
||||||
new AsyncLazyMap<>(new HashMap<>(), this::fetchRegAccessibility) {
|
|
||||||
public AllRequiredAccess remove(TargetRegisterBank key) {
|
|
||||||
AllRequiredAccess acc = super.remove(key);
|
|
||||||
if (acc != null) {
|
|
||||||
acc.removeChangeListener(listenerRegAccChanged);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected CompletableFuture<AllRequiredAccess> fetchRegAccessibility(
|
|
||||||
TargetRegisterBank bank) {
|
|
||||||
return DebugModelConventions.trackAccessibility(bank).thenApply(acc -> {
|
|
||||||
acc.addChangeListener(listenerRegAccChanged);
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public RecorderComposedRegisterSet(DefaultTraceRecorder recorder) {
|
|
||||||
this.recorder = recorder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateRegisters(TargetRegisterBank newRegs, TargetRegisterBank oldRegs) {
|
|
||||||
synchronized (accessibilityByRegBank) {
|
|
||||||
if (oldRegs != null) {
|
|
||||||
accessibilityByRegBank.remove(oldRegs);
|
|
||||||
}
|
|
||||||
accessibilityByRegBank.get(newRegs).exceptionally(e -> {
|
|
||||||
e = AsyncUtils.unwrapThrowable(e);
|
|
||||||
Msg.error(this, "Could not track register accessibility: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean checkRegistersRemoved(Map<Integer, TargetRegisterBank> regs,
|
|
||||||
TargetObject invalid) {
|
|
||||||
synchronized (accessibilityByRegBank) {
|
|
||||||
if (regs.values().remove(invalid)) {
|
|
||||||
accessibilityByRegBank.remove((TargetRegisterBank) invalid);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
|
|
||||||
if (bank == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
synchronized (accessibilityByRegBank) {
|
|
||||||
KeyedFuture<?, AllRequiredAccess> future = accessibilityByRegBank.get(bank);
|
|
||||||
if (future == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
AllRequiredAccess acc = future.getNow(null);
|
|
||||||
if (acc == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return acc.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,9 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.dbg;
|
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
|
||||||
*
|
*
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue