mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-10-23 13:41:04 +00:00
GP-77: Model schema specification, and GDB's implementation.
This commit is contained in:
parent
c90e12a78b
commit
eb66a90f6c
|
@ -202,4 +202,6 @@ public interface GdbGadpServer extends AutoCloseable {
|
|||
default void close() throws IOException {
|
||||
terminate();
|
||||
}
|
||||
|
||||
void setExitOnClosed(boolean exitOnClosed);
|
||||
}
|
||||
|
|
|
@ -59,4 +59,9 @@ public class GdbGadpServerImpl implements GdbGadpServer {
|
|||
model.terminate();
|
||||
server.terminate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExitOnClosed(boolean exitOnClosed) {
|
||||
server.setExitOnClosed(exitOnClosed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import agent.gdb.manager.impl.cmd.*;
|
|||
import agent.gdb.manager.parsing.GdbMiParser;
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
import ghidra.async.*;
|
||||
import ghidra.dbg.error.DebuggerModelTerminatingException;
|
||||
import ghidra.dbg.util.HandlerMap;
|
||||
import ghidra.dbg.util.PrefixMap;
|
||||
import ghidra.lifecycle.Internal;
|
||||
|
@ -627,29 +628,6 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
}
|
||||
|
||||
private void readStream(InputStream in, Channel channel, Interpreter interpreter) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
try {
|
||||
String line;
|
||||
while (isAlive() && null != (line = reader.readLine())) {
|
||||
String l = line;
|
||||
//Msg.debug(this, channel + ": " + line);
|
||||
submit(() -> {
|
||||
if (LOG_IO) {
|
||||
DBG_LOG.println("<" + interpreter + ": " + l);
|
||||
DBG_LOG.flush();
|
||||
}
|
||||
processLine(l, channel, interpreter);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
terminate();
|
||||
Msg.debug(this, channel + "," + interpreter + " reader exiting because " + e);
|
||||
//throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void terminate() {
|
||||
Msg.debug(this, "Terminating " + this);
|
||||
|
@ -675,15 +653,17 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if (gdb != null) {
|
||||
gdb.destroyForcibly();
|
||||
}
|
||||
cmdLock.dispose("GDB is terminating");
|
||||
state.dispose("GDB is terminating");
|
||||
mi2Prompt.dispose("GDB is terminating");
|
||||
DebuggerModelTerminatingException reason =
|
||||
new DebuggerModelTerminatingException("GDB is terminating");
|
||||
cmdLock.dispose(reason);
|
||||
state.dispose(reason);
|
||||
mi2Prompt.dispose(reason);
|
||||
for (GdbThreadImpl thread : threads.values()) {
|
||||
thread.dispose("GDB is terminating");
|
||||
thread.dispose(reason);
|
||||
}
|
||||
GdbPendingCommand<?> cc = this.curCmd; // read volatile
|
||||
if (cc != null && !cc.isDone()) {
|
||||
cc.completeExceptionally(new IllegalStateException("GDB is terminating"));
|
||||
cc.completeExceptionally(reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -263,10 +263,11 @@ public class GdbThreadImpl implements GdbThread {
|
|||
return execute(new GdbDetachCommand(manager, inferior, id));
|
||||
}
|
||||
|
||||
public void dispose(String reason) {
|
||||
public void dispose(Throwable reason) {
|
||||
state.dispose(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<GdbThreadInfo> getInfo() {
|
||||
return manager.getThreadInfo(id);
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ public class GdbReadRegistersCommand
|
|||
}
|
||||
catch (GdbParseError | AssertionError e) {
|
||||
Msg.warn(this,
|
||||
"Could not figure register value for [" + number + "] = " + value, e);
|
||||
"Could not figure register value for [" + number + "] = " + value/*, e*/);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -28,6 +28,8 @@ import ghidra.dbg.agent.AbstractDebuggerObjectModel;
|
|||
import ghidra.dbg.error.DebuggerUserException;
|
||||
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.AnnotatedSchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
||||
|
@ -35,6 +37,10 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
|||
// The model must convert to and from Ghidra's address space names
|
||||
protected static final String SPACE_NAME = "ram";
|
||||
|
||||
protected static final AnnotatedSchemaContext SCHEMA_CTX = new AnnotatedSchemaContext();
|
||||
protected static final TargetObjectSchema ROOT_SCHEMA =
|
||||
SCHEMA_CTX.getSchemaForClass(GdbModelTargetSession.class);
|
||||
|
||||
protected static <T> T translateEx(Throwable ex) {
|
||||
Throwable t = AsyncUtils.unwrapThrowable(ex);
|
||||
if (t instanceof GdbCommandError) {
|
||||
|
@ -59,13 +65,18 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
|||
|
||||
public GdbModelImpl() {
|
||||
this.gdb = GdbManager.newInstance();
|
||||
this.session = new GdbModelTargetSession(this);
|
||||
this.session = new GdbModelTargetSession(this, ROOT_SCHEMA);
|
||||
|
||||
this.completedSession = CompletableFuture.completedFuture(session);
|
||||
|
||||
gdb.addStateListener(gdbExitListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObjectSchema getRootSchema() {
|
||||
return ROOT_SCHEMA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSpace getAddressSpace(String name) {
|
||||
if (!SPACE_NAME.equals(name)) {
|
||||
|
|
|
@ -22,8 +22,14 @@ import agent.gdb.manager.GdbProcessThreadGroup;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetAttachable;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Attachable", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetAttachable
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetAvailableContainer>
|
||||
implements TargetAttachable<GdbModelTargetAttachable> {
|
||||
|
@ -58,6 +64,7 @@ public class GdbModelTargetAttachable
|
|||
), "Initialized");
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = PID_ATTRIBUTE_NAME, hidden = true)
|
||||
public long getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
|
|
@ -22,17 +22,24 @@ import java.util.stream.Collectors;
|
|||
|
||||
import agent.gdb.manager.GdbProcessThreadGroup;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "AvailableContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetAvailableContainer
|
||||
extends DefaultTargetObject<GdbModelTargetAttachable, GdbModelTargetSession> {
|
||||
public static final String NAME = "Available";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
|
||||
protected final Map<Integer, GdbModelTargetAttachable> attachablesById =
|
||||
new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetAvailableContainer(GdbModelTargetSession session) {
|
||||
super(session.impl, session, "Available", "AvailableContainer");
|
||||
super(session.impl, session, NAME, "AvailableContainer");
|
||||
this.impl = session.impl;
|
||||
changeAttributes(List.of(), Map.of(
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.SOLICITED //
|
||||
|
|
|
@ -28,14 +28,20 @@ import ghidra.async.AsyncFence;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "BreakpointContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetBreakpointContainer
|
||||
extends DefaultTargetObject<GdbModelTargetBreakpointSpec, GdbModelTargetSession>
|
||||
implements TargetBreakpointContainer<GdbModelTargetBreakpointContainer>,
|
||||
GdbEventsListenerAdapter {
|
||||
public static final String NAME = "Breakpoints";
|
||||
|
||||
protected static final TargetBreakpointKindSet SUPPORTED_KINDS =
|
||||
TargetBreakpointKindSet.of(TargetBreakpointKind.values());
|
||||
|
@ -46,7 +52,7 @@ public class GdbModelTargetBreakpointContainer
|
|||
new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetBreakpointContainer(GdbModelTargetSession session) {
|
||||
super(session.impl, session, "Breakpoints", "BreakpointContainer");
|
||||
super(session.impl, session, NAME, "BreakpointContainer");
|
||||
this.impl = session.impl;
|
||||
|
||||
impl.gdb.addEventsListener(this);
|
||||
|
@ -59,7 +65,10 @@ public class GdbModelTargetBreakpointContainer
|
|||
|
||||
@Override
|
||||
public void breakpointCreated(GdbBreakpointInfo info, GdbCause cause) {
|
||||
changeElements(List.of(), List.of(getTargetBreakpointSpec(info)), "Created");
|
||||
GdbModelTargetBreakpointSpec spec = getTargetBreakpointSpec(info);
|
||||
spec.init().thenRun(() -> {
|
||||
changeElements(List.of(), List.of(spec), "Created");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -143,7 +152,8 @@ public class GdbModelTargetBreakpointContainer
|
|||
return specsByNumber.get(number);
|
||||
}
|
||||
|
||||
protected void updateUsingBreakpoints(Map<Long, GdbBreakpointInfo> byNumber) {
|
||||
protected CompletableFuture<Void> updateUsingBreakpoints(
|
||||
Map<Long, GdbBreakpointInfo> byNumber) {
|
||||
List<GdbModelTargetBreakpointSpec> specs;
|
||||
synchronized (this) {
|
||||
specs = byNumber.values()
|
||||
|
@ -151,14 +161,20 @@ public class GdbModelTargetBreakpointContainer
|
|||
.map(this::getTargetBreakpointSpec)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return CompletableFuture
|
||||
.allOf(specs.stream()
|
||||
.map(s -> s.init())
|
||||
.toArray(CompletableFuture[]::new))
|
||||
.thenRun(() -> {
|
||||
setElements(specs, "Refreshed");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
if (!refresh) {
|
||||
updateUsingBreakpoints(impl.gdb.getKnownBreakpoints());
|
||||
return updateUsingBreakpoints(impl.gdb.getKnownBreakpoints());
|
||||
}
|
||||
return impl.gdb.listBreakpoints().thenAccept(this::updateUsingBreakpoints);
|
||||
return impl.gdb.listBreakpoints().thenCompose(this::updateUsingBreakpoints);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,17 @@ import generic.Unique;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.attributes.TargetObjectRefList;
|
||||
import ghidra.dbg.attributes.TargetObjectRefList.DefaultTargetObjectRefList;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointLocation;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "BreakpointLocation", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetBreakpointLocation
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetBreakpointSpec>
|
||||
implements TargetBreakpointLocation<GdbModelTargetBreakpointLocation> {
|
||||
|
@ -53,7 +59,7 @@ public class GdbModelTargetBreakpointLocation
|
|||
|
||||
public GdbModelTargetBreakpointLocation(GdbModelTargetBreakpointSpec spec,
|
||||
GdbBreakpointLocation loc) {
|
||||
super(spec.impl, spec, keyLocation(loc), "EffectiveBreakpoint");
|
||||
super(spec.impl, spec, keyLocation(loc), "BreakpointLocation");
|
||||
this.impl = spec.impl;
|
||||
this.loc = loc;
|
||||
|
||||
|
@ -67,17 +73,20 @@ public class GdbModelTargetBreakpointLocation
|
|||
}
|
||||
|
||||
protected void doChangeAttributes(String reason) {
|
||||
this.changeAttributes(List.of(),
|
||||
Map.of(SPEC_ATTRIBUTE_NAME, parent, AFFECTS_ATTRIBUTE_NAME, affects,
|
||||
ADDRESS_ATTRIBUTE_NAME, address, LENGTH_ATTRIBUTE_NAME, length,
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(), UPDATE_MODE_ATTRIBUTE_NAME,
|
||||
TargetUpdateMode.FIXED //
|
||||
this.changeAttributes(List.of(), Map.of(
|
||||
SPEC_ATTRIBUTE_NAME, parent,
|
||||
AFFECTS_ATTRIBUTE_NAME, affects,
|
||||
ADDRESS_ATTRIBUTE_NAME, address,
|
||||
LENGTH_ATTRIBUTE_NAME, length,
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
), reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize watchpoint attributes via expression evaluation
|
||||
*
|
||||
* <p>
|
||||
* This has to be async because it involves interacting with GDB. GDB does not give the address
|
||||
* or location information for location-specified watchpoints. Instead we take the expression
|
||||
* and ask GDB to evaluate its address and size.
|
||||
|
@ -132,12 +141,12 @@ public class GdbModelTargetBreakpointLocation
|
|||
}
|
||||
|
||||
@Override
|
||||
public TargetObjectRefList<?> getAffects() {
|
||||
public TargetObjectRefList<GdbModelTargetInferior> getAffects() {
|
||||
return affects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedTargetObjectRef<? extends TargetBreakpointSpec<?>> getSpecification() {
|
||||
public GdbModelTargetBreakpointSpec getSpecification() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,16 +23,22 @@ import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
|
|||
import agent.gdb.manager.breakpoint.GdbBreakpointLocation;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec;
|
||||
import ghidra.dbg.target.TargetDeletable;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetBreakpointSpec extends
|
||||
DefaultTargetObject<TargetBreakpointLocation<GdbModelTargetBreakpointLocation>, GdbModelTargetBreakpointContainer>
|
||||
DefaultTargetObject<GdbModelTargetBreakpointLocation, GdbModelTargetBreakpointContainer>
|
||||
implements TargetBreakpointSpec<GdbModelTargetBreakpointSpec>,
|
||||
TargetDeletable<GdbModelTargetBreakpointSpec> {
|
||||
|
||||
|
@ -68,7 +74,15 @@ public class GdbModelTargetBreakpointSpec extends
|
|||
this.impl = breakpoints.impl;
|
||||
this.number = info.getNumber();
|
||||
|
||||
updateInfo(null, info, "Created").exceptionally(ex -> {
|
||||
this.info = info;
|
||||
|
||||
changeAttributes(List.of(), Map.of(
|
||||
CONTAINER_ATTRIBUTE_NAME, breakpoints),
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> init() {
|
||||
return updateInfo(info, info, "Created").exceptionally(ex -> {
|
||||
Msg.info(this, "Initial breakpoint info update failed", ex);
|
||||
return null;
|
||||
});
|
||||
|
@ -243,4 +257,9 @@ public class GdbModelTargetBreakpointSpec extends
|
|||
public String getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GdbModelTargetBreakpointContainer getContainer() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,32 +23,39 @@ import ghidra.async.AsyncFence;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetEnvironment;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Environment", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetEnvironment
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetInferior>
|
||||
implements TargetEnvironment<GdbModelTargetEnvironment> {
|
||||
public static final String NAME = "Environment";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
|
||||
protected String arch = "";
|
||||
protected String os = "";
|
||||
protected String endian = "";
|
||||
protected String arch = "(unknown)";
|
||||
protected String os = "(unknown)";
|
||||
protected String endian = "(unknown)";
|
||||
|
||||
public GdbModelTargetEnvironment(GdbModelTargetInferior inferior) {
|
||||
super(inferior.impl, inferior, "Environment", "Environment");
|
||||
super(inferior.impl, inferior, NAME, "Environment");
|
||||
this.impl = inferior.impl;
|
||||
|
||||
changeAttributes(List.of(), Map.of(
|
||||
DEBUGGER_ATTRIBUTE_NAME, impl.session.debugger,
|
||||
ARCH_ATTRIBUTE_NAME, "(unknown)",
|
||||
OS_ATTRIBUTE_NAME, "(unknown)",
|
||||
ENDIAN_ATTRIBUTE_NAME, "(unknown)",
|
||||
VISIBLE_ARCH_ATTRIBUTE_NAME, "(unknown)",
|
||||
VISIBLE_OS_ATTRIBUTE_NAME, "(unknown)",
|
||||
VISIBLE_ENDIAN_ATTRIBUTE_NAME, "(unknown)",
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED // Attributes may still change
|
||||
), "Initialized");
|
||||
ARCH_ATTRIBUTE_NAME, arch,
|
||||
OS_ATTRIBUTE_NAME, os,
|
||||
ENDIAN_ATTRIBUTE_NAME, endian,
|
||||
VISIBLE_ARCH_ATTRIBUTE_NAME, arch,
|
||||
VISIBLE_OS_ATTRIBUTE_NAME, os,
|
||||
VISIBLE_ENDIAN_ATTRIBUTE_NAME, endian,
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED),
|
||||
"Initialized");
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
@ -163,6 +170,24 @@ public class GdbModelTargetEnvironment
|
|||
});
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = ARCH_ATTRIBUTE_NAME, hidden = true)
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getInvisibleArch() {
|
||||
return arch;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = OS_ATTRIBUTE_NAME, hidden = true)
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getInvisibleOs() {
|
||||
return os;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = ENDIAN_ATTRIBUTE_NAME, hidden = true)
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getInvisibleEndian() {
|
||||
return endian;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDebugger() {
|
||||
return impl.session.debugger;
|
||||
|
|
|
@ -28,10 +28,16 @@ import ghidra.dbg.error.DebuggerModelNoSuchPathException;
|
|||
import ghidra.dbg.error.DebuggerModelTypeException;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Inferior", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetInferior
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetInferiorContainer> implements //
|
||||
TargetProcess<GdbModelTargetInferior>, //
|
||||
|
@ -46,9 +52,11 @@ public class GdbModelTargetInferior
|
|||
TargetSteppable<GdbModelTargetInferior>, //
|
||||
GdbModelSelectableObject {
|
||||
|
||||
public static final String PID_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "pid";
|
||||
public static final String EXIT_CODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "exit_code";
|
||||
|
||||
protected static final TargetAttachKindSet SUPPORTED_KINDS = TargetAttachKindSet.of( //
|
||||
TargetAttachKind.BY_OBJECT_REF, TargetAttachKind.BY_ID);
|
||||
|
||||
protected static String indexInferior(int inferiorId) {
|
||||
return PathUtils.makeIndex(inferiorId);
|
||||
}
|
||||
|
@ -71,6 +79,8 @@ public class GdbModelTargetInferior
|
|||
protected final GdbModelTargetRegisterContainer registers;
|
||||
protected final GdbModelTargetThreadContainer threads;
|
||||
|
||||
protected Long exitCode;
|
||||
|
||||
public GdbModelTargetInferior(GdbModelTargetInferiorContainer inferiors, GdbInferior inferior) {
|
||||
super(inferiors.impl, inferiors, keyInferior(inferior), "Inferior");
|
||||
this.impl = inferiors.impl;
|
||||
|
@ -82,17 +92,46 @@ public class GdbModelTargetInferior
|
|||
this.registers = new GdbModelTargetRegisterContainer(this);
|
||||
this.threads = new GdbModelTargetThreadContainer(this);
|
||||
|
||||
changeAttributes(List.of(), Map.of( //
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE, //
|
||||
DISPLAY_ATTRIBUTE_NAME, updateDisplay(), //
|
||||
TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetCmdLineLauncher.PARAMETERS, //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
|
||||
environment.getName(), environment, //
|
||||
memory.getName(), memory, //
|
||||
modules.getName(), modules, //
|
||||
registers.getName(), registers, //
|
||||
threads.getName(), threads //
|
||||
), "Initialized");
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
environment,
|
||||
memory,
|
||||
modules,
|
||||
registers,
|
||||
threads),
|
||||
Map.of(
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE,
|
||||
DISPLAY_ATTRIBUTE_NAME, updateDisplay(),
|
||||
TargetMethod.PARAMETERS_ATTRIBUTE_NAME, TargetCmdLineLauncher.PARAMETERS,
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED,
|
||||
SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS,
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, GdbModelTargetThread.SUPPORTED_KINDS),
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetEnvironment.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetEnvironment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetProcessMemory.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetProcessMemory getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetModuleContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetModuleContainer getModules() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetRegisterContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetRegisterContainer getRegisters() {
|
||||
return registers;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetThreadContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetThreadContainer getThreads() {
|
||||
return threads;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -202,6 +241,7 @@ public class GdbModelTargetInferior
|
|||
}
|
||||
|
||||
protected void inferiorExited(Long exitCode) {
|
||||
this.exitCode = exitCode;
|
||||
if (exitCode != null) {
|
||||
changeAttributes(List.of(), Map.of( //
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
|
||||
|
@ -257,4 +297,8 @@ public class GdbModelTargetInferior
|
|||
return inferior.select();
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = EXIT_CODE_ATTRIBUTE_NAME)
|
||||
public Long getExitCode() {
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,18 +27,25 @@ import ghidra.dbg.target.TargetEventScope.TargetEventScopeListener;
|
|||
import ghidra.dbg.target.TargetEventScope.TargetEventType;
|
||||
import ghidra.dbg.target.TargetExecutionStateful;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "InferiorContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetInferiorContainer
|
||||
extends DefaultTargetObject<GdbModelTargetInferior, GdbModelTargetSession>
|
||||
implements GdbEventsListenerAdapter {
|
||||
public static final String NAME = "Inferiors";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
|
||||
protected final Map<Integer, GdbModelTargetInferior> inferiorsById = new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetInferiorContainer(GdbModelTargetSession session) {
|
||||
super(session.impl, session, "Inferiors", "InferiorContainer");
|
||||
super(session.impl, session, NAME, "InferiorContainer");
|
||||
this.impl = session.impl;
|
||||
|
||||
impl.gdb.addEventsListener(this);
|
||||
|
|
|
@ -20,11 +20,17 @@ import java.util.Map;
|
|||
|
||||
import agent.gdb.manager.impl.GdbMemoryMapping;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetMemoryRegion;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "MemoryRegion", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetMemoryRegion
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetProcessMemory>
|
||||
implements TargetMemoryRegion<GdbModelTargetMemoryRegion> {
|
||||
|
@ -49,6 +55,8 @@ public class GdbModelTargetMemoryRegion
|
|||
}
|
||||
|
||||
protected AddressRangeImpl range;
|
||||
protected final String objfile;
|
||||
protected final long offset;
|
||||
protected final String display;
|
||||
|
||||
public GdbModelTargetMemoryRegion(GdbModelTargetProcessMemory memory,
|
||||
|
@ -68,8 +76,8 @@ public class GdbModelTargetMemoryRegion
|
|||
READABLE_ATTRIBUTE_NAME, isReadable(), //
|
||||
WRITABLE_ATTRIBUTE_NAME, isWritable(), //
|
||||
EXECUTABLE_ATTRIBUTE_NAME, isExecutable(), //
|
||||
OBJFILE_ATTRIBUTE_NAME, mapping.getObjfile(), //
|
||||
OFFSET_ATTRIBUTE_NAME, mapping.getOffset().longValue(), //
|
||||
OBJFILE_ATTRIBUTE_NAME, objfile = mapping.getObjfile(), //
|
||||
OFFSET_ATTRIBUTE_NAME, offset = mapping.getOffset().longValue(), //
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(mapping), //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
), "Initialized");
|
||||
|
@ -92,7 +100,7 @@ public class GdbModelTargetMemoryRegion
|
|||
}
|
||||
|
||||
@Override
|
||||
public TypedTargetObjectRef<? extends TargetMemory<?>> getMemory() {
|
||||
public GdbModelTargetProcessMemory getMemory() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
@ -112,4 +120,14 @@ public class GdbModelTargetMemoryRegion
|
|||
public boolean isExecutable() {
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = OBJFILE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
public String getObjfile() {
|
||||
return objfile;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = OFFSET_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,16 @@ import agent.gdb.manager.GdbModule;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Module", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetModule
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetModuleContainer>
|
||||
implements TargetModule<GdbModelTargetModule> {
|
||||
|
@ -47,6 +53,8 @@ public class GdbModelTargetModule
|
|||
protected final GdbModelTargetSymbolContainer symbols;
|
||||
// TODO Types? See GDB's ptype, but that'll require some C parsing
|
||||
|
||||
protected AddressRange range;
|
||||
|
||||
public GdbModelTargetModule(GdbModelTargetModuleContainer modules, GdbModule module) {
|
||||
super(modules.impl, modules, keyModule(module), "Module");
|
||||
this.impl = modules.impl;
|
||||
|
@ -56,11 +64,18 @@ public class GdbModelTargetModule
|
|||
this.sections = new GdbModelTargetSectionContainer(this);
|
||||
this.symbols = new GdbModelTargetSymbolContainer(this);
|
||||
|
||||
range = doGetRange(); // Likely [0,0]
|
||||
changeAttributes(List.of(),
|
||||
Map.of(sections.getName(), sections, symbols.getName(), symbols,
|
||||
MODULE_NAME_ATTRIBUTE_NAME, module.getName(), UPDATE_MODE_ATTRIBUTE_NAME,
|
||||
TargetUpdateMode.FIXED, DISPLAY_ATTRIBUTE_NAME, module.getName() //
|
||||
), "Initialized");
|
||||
List.of(
|
||||
sections,
|
||||
symbols),
|
||||
Map.of(
|
||||
VISIBLE_RANGE_ATTRIBUTE_NAME, range,
|
||||
RANGE_ATTRIBUTE_NAME, range,
|
||||
MODULE_NAME_ATTRIBUTE_NAME, module.getName(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED,
|
||||
DISPLAY_ATTRIBUTE_NAME, module.getName()),
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> init() {
|
||||
|
@ -70,6 +85,16 @@ public class GdbModelTargetModule
|
|||
});
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetSectionContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetSectionContainer getSections() {
|
||||
return sections;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetSymbolContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetSymbolContainer getSymbols() {
|
||||
return symbols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplay() {
|
||||
return module.getName();
|
||||
|
@ -78,7 +103,7 @@ public class GdbModelTargetModule
|
|||
protected AddressRange doGetRange() {
|
||||
Long base = module.getKnownBase();
|
||||
Long max = module.getKnownMax();
|
||||
max = max == null ? base : max - 1; // GDB gives end+1
|
||||
max = max == null ? base : (Long) (max - 1); // GDB gives end+1
|
||||
if (base == null) {
|
||||
Address addr = impl.space.getMinAddress();
|
||||
return new AddressRangeImpl(addr, addr);
|
||||
|
@ -88,8 +113,20 @@ public class GdbModelTargetModule
|
|||
|
||||
public void sectionsRefreshed() {
|
||||
AddressRange range = doGetRange();
|
||||
changeAttributes(List.of(), Map.of(RANGE_ATTRIBUTE_NAME, range, //
|
||||
VISIBLE_RANGE_ATTRIBUTE_NAME, range //
|
||||
), "Sections Refreshed");
|
||||
changeAttributes(List.of(), Map.of(
|
||||
RANGE_ATTRIBUTE_NAME, range,
|
||||
VISIBLE_RANGE_ATTRIBUTE_NAME, range),
|
||||
"Sections Refreshed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
@TargetAttributeType(name = RANGE_ATTRIBUTE_NAME)
|
||||
public AddressRange getInvisibleRange() {
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,19 @@ import ghidra.dbg.agent.DefaultTargetObject;
|
|||
import ghidra.dbg.error.DebuggerUserException;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetModuleContainer;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "ModuleContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetModuleContainer
|
||||
extends DefaultTargetObject<GdbModelTargetModule, GdbModelTargetInferior>
|
||||
implements TargetModuleContainer<GdbModelTargetModuleContainer> {
|
||||
// NOTE: -file-list-shared-libraries omits the main module and system-supplied DSO.
|
||||
public static final String NAME = "Modules";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbInferior inferior;
|
||||
|
@ -42,7 +48,7 @@ public class GdbModelTargetModuleContainer
|
|||
protected final Map<String, GdbModelTargetModule> modulesByName = new HashMap<>();
|
||||
|
||||
public GdbModelTargetModuleContainer(GdbModelTargetInferior inferior) {
|
||||
super(inferior.impl, inferior, "Modules", "ModuleContainer");
|
||||
super(inferior.impl, inferior, NAME, "ModuleContainer");
|
||||
this.impl = inferior.impl;
|
||||
this.inferior = inferior.inferior;
|
||||
}
|
||||
|
|
|
@ -32,13 +32,19 @@ import ghidra.async.AsyncUtils;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
||||
import ghidra.dbg.target.TargetMemory;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Memory", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetProcessMemory
|
||||
extends DefaultTargetObject<GdbModelTargetMemoryRegion, GdbModelTargetInferior>
|
||||
implements TargetMemory<GdbModelTargetProcessMemory> {
|
||||
public static final String NAME = "Memory";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbInferior inferior;
|
||||
|
@ -47,7 +53,7 @@ public class GdbModelTargetProcessMemory
|
|||
new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetProcessMemory(GdbModelTargetInferior inferior) {
|
||||
super(inferior.impl, inferior, "Memory", "ProcessMemory");
|
||||
super(inferior.impl, inferior, NAME, "ProcessMemory");
|
||||
this.impl = inferior.impl;
|
||||
this.inferior = inferior.inferior;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,14 @@ import agent.gdb.manager.GdbRegister;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetRegister;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "RegisterDescriptor", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetRegister
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetRegisterContainer>
|
||||
implements TargetRegister<GdbModelTargetRegister> {
|
||||
|
@ -69,4 +75,9 @@ public class GdbModelTargetRegister
|
|||
public String getDisplay() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GdbModelTargetRegisterContainer getContainer() {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,18 @@ import agent.gdb.manager.*;
|
|||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetRegisterContainer;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "RegisterContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetRegisterContainer
|
||||
extends DefaultTargetObject<GdbModelTargetRegister, GdbModelTargetInferior>
|
||||
implements TargetRegisterContainer<GdbModelTargetRegisterContainer> {
|
||||
public static final String NAME = "Registers";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbInferior inferior;
|
||||
|
@ -38,7 +44,7 @@ public class GdbModelTargetRegisterContainer
|
|||
new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetRegisterContainer(GdbModelTargetInferior inferior) {
|
||||
super(inferior.impl, inferior, "Registers", "RegisterContainer");
|
||||
super(inferior.impl, inferior, NAME, "RegisterContainer");
|
||||
this.impl = inferior.impl;
|
||||
this.inferior = inferior.inferior;
|
||||
}
|
||||
|
|
|
@ -20,11 +20,17 @@ import java.util.Map;
|
|||
|
||||
import agent.gdb.manager.GdbModuleSection;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetSection;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Section", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetSection
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetSectionContainer>
|
||||
implements TargetSection<GdbModelTargetSection> {
|
||||
|
@ -50,15 +56,17 @@ public class GdbModelTargetSection
|
|||
|
||||
this.module = module;
|
||||
this.range = doGetRange();
|
||||
this.changeAttributes(List.of(),
|
||||
Map.of(MODULE_ATTRIBUTE_NAME, module, RANGE_ATTRIBUTE_NAME, range,
|
||||
VISIBLE_RANGE_ATTRIBUTE_NAME, range, DISPLAY_ATTRIBUTE_NAME, section.getName(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
), "Initialized");
|
||||
this.changeAttributes(List.of(), List.of(), Map.of(
|
||||
MODULE_ATTRIBUTE_NAME, module,
|
||||
RANGE_ATTRIBUTE_NAME, range,
|
||||
VISIBLE_RANGE_ATTRIBUTE_NAME, range,
|
||||
DISPLAY_ATTRIBUTE_NAME, section.getName(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED),
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedTargetObjectRef<? extends TargetModule<?>> getModule() {
|
||||
public GdbModelTargetModule getModule() {
|
||||
return module;
|
||||
}
|
||||
|
||||
|
@ -77,6 +85,12 @@ public class GdbModelTargetSection
|
|||
return range;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
@TargetAttributeType(name = RANGE_ATTRIBUTE_NAME)
|
||||
public AddressRange getInvisibleRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplay() {
|
||||
return section.getName();
|
||||
|
|
|
@ -22,17 +22,24 @@ import java.util.stream.Collectors;
|
|||
|
||||
import agent.gdb.manager.GdbModuleSection;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "SectionContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetSectionContainer
|
||||
extends DefaultTargetObject<GdbModelTargetSection, GdbModelTargetModule> {
|
||||
public static final String NAME = "Sections";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbModelTargetModule module;
|
||||
|
||||
protected final Map<String, GdbModelTargetSection> sectionsByName = new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetSectionContainer(GdbModelTargetModule module) {
|
||||
super(module.impl, module, "Sections", "SectionContainer");
|
||||
super(module.impl, module, NAME, "SectionContainer");
|
||||
this.impl = module.impl;
|
||||
this.module = module;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,15 @@ import ghidra.dbg.attributes.TypedTargetObjectRef;
|
|||
import ghidra.dbg.error.DebuggerIllegalArgumentException;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Session", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetSession extends DefaultTargetModelRoot implements //
|
||||
TargetAccessConditioned<GdbModelTargetSession>,
|
||||
TargetAttacher<GdbModelTargetSession>,
|
||||
|
@ -56,8 +62,8 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot implements //
|
|||
|
||||
protected String debugger = "gdb"; // Used by GdbModelTargetEnvironment
|
||||
|
||||
public GdbModelTargetSession(GdbModelImpl impl) {
|
||||
super(impl, "Session");
|
||||
public GdbModelTargetSession(GdbModelImpl impl, TargetObjectSchema schema) {
|
||||
super(impl, "Session", schema);
|
||||
this.impl = impl;
|
||||
|
||||
this.inferiors = new GdbModelTargetInferiorContainer(this);
|
||||
|
@ -80,6 +86,21 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot implements //
|
|||
getVersion();
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetInferiorContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetInferiorContainer getInferiors() {
|
||||
return inferiors;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetAvailableContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetAvailableContainer getAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetBreakpointContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetBreakpointContainer getBreakpoints() {
|
||||
return breakpoints;
|
||||
}
|
||||
|
||||
protected void getVersion() {
|
||||
impl.gdb.waitForPrompt().thenCompose(__ -> {
|
||||
return impl.gdb.consoleCapture("show version");
|
||||
|
|
|
@ -25,12 +25,19 @@ import agent.gdb.manager.GdbThread;
|
|||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetStack;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Stack", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetStack
|
||||
extends DefaultTargetObject<GdbModelTargetStackFrame, GdbModelTargetThread>
|
||||
implements TargetStack<GdbModelTargetStack> {
|
||||
public static final String NAME = "Stack";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbModelTargetInferior inferior;
|
||||
protected final GdbThread thread;
|
||||
|
@ -38,7 +45,7 @@ public class GdbModelTargetStack
|
|||
protected final Map<Integer, GdbModelTargetStackFrame> framesByLevel = new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetStack(GdbModelTargetThread thread, GdbModelTargetInferior inferior) {
|
||||
super(thread.impl, thread, "Stack", "Stack");
|
||||
super(thread.impl, thread, NAME, "Stack");
|
||||
this.impl = thread.impl;
|
||||
this.inferior = inferior;
|
||||
this.thread = thread.thread;
|
||||
|
|
|
@ -24,12 +24,18 @@ import agent.gdb.manager.GdbStackFrame;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.error.DebuggerRegisterAccessException;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.ConversionUtils;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "StackFrame", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject, GdbModelTargetStack>
|
||||
implements TargetStackFrame<GdbModelTargetStackFrame>,
|
||||
TargetRegisterBank<GdbModelTargetStackFrame>, GdbModelSelectableObject {
|
||||
|
@ -59,7 +65,7 @@ public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject,
|
|||
protected String func;
|
||||
protected String display;
|
||||
|
||||
private GdbModelTargetStackFrameRegisterContainer registers;
|
||||
private final GdbModelTargetStackFrameRegisterContainer registers;
|
||||
|
||||
public GdbModelTargetStackFrame(GdbModelTargetStack stack, GdbModelTargetThread thread,
|
||||
GdbModelTargetInferior inferior, GdbStackFrame frame) {
|
||||
|
@ -70,16 +76,22 @@ public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject,
|
|||
|
||||
this.registers = new GdbModelTargetStackFrameRegisterContainer(this);
|
||||
|
||||
changeAttributes(List.of(), List.of( //
|
||||
registers //
|
||||
), Map.of( //
|
||||
DESCRIPTIONS_ATTRIBUTE_NAME, getDescriptions(), //
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame), //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
), "Initialized");
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
registers),
|
||||
Map.of(
|
||||
DESCRIPTIONS_ATTRIBUTE_NAME, getDescriptions(),
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED),
|
||||
"Initialized");
|
||||
setFrame(frame);
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetStackFrameRegisterContainer.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetStackFrameRegisterContainer getRegisters() {
|
||||
return registers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GdbModelTargetRegisterContainer getDescriptions() {
|
||||
return inferior.registers;
|
||||
|
@ -166,4 +178,8 @@ public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject,
|
|||
return frame.select();
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME)
|
||||
public String getFunction() {
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,17 @@ import java.util.Map;
|
|||
import agent.gdb.manager.GdbRegister;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetRegister;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "RegisterValue", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetStackFrameRegister
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetStackFrameRegisterContainer>
|
||||
implements TargetRegister<GdbModelTargetStackFrameRegister> {
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetStackFrameRegisterContainer> {
|
||||
|
||||
protected static String indexRegister(GdbRegister register) {
|
||||
String name = register.getName();
|
||||
|
@ -44,42 +49,36 @@ public class GdbModelTargetStackFrameRegister
|
|||
protected final GdbModelImpl impl;
|
||||
protected final GdbRegister register;
|
||||
|
||||
protected final int bitLength;
|
||||
private BigInteger value;
|
||||
|
||||
public GdbModelTargetStackFrameRegister(GdbModelTargetStackFrameRegisterContainer registers,
|
||||
GdbRegister register) {
|
||||
super(registers.impl, registers, keyRegister(register), "Register");
|
||||
this.impl = registers.impl;
|
||||
this.register = register;
|
||||
|
||||
this.bitLength = register.getSize() * 8;
|
||||
|
||||
changeAttributes(List.of(), Map.of( //
|
||||
CONTAINER_ATTRIBUTE_NAME, registers, //
|
||||
LENGTH_ATTRIBUTE_NAME, bitLength, //
|
||||
DISPLAY_ATTRIBUTE_NAME, getName(), //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
), "Initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBitLength() {
|
||||
return bitLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplay() {
|
||||
return getCachedAttribute(DISPLAY_ATTRIBUTE_NAME).toString();
|
||||
}
|
||||
|
||||
public void setModified(boolean modified) {
|
||||
changeAttributes(List.of(), Map.of( //
|
||||
MODIFIED_ATTRIBUTE_NAME, modified //
|
||||
), "Refreshed");
|
||||
if (modified) {
|
||||
listeners.fire.displayChanged(this, getDisplay());
|
||||
}
|
||||
}
|
||||
public void updateValue(BigInteger bigNewVal) {
|
||||
String value = bigNewVal.toString(16);
|
||||
Object oldval = getCachedAttributes().get(VALUE_ATTRIBUTE_NAME);
|
||||
boolean modified = (bigNewVal.longValue() != 0 && value.equals(oldval));
|
||||
|
||||
String newval = getName() + " : " + value;
|
||||
Delta<?, ?> delta = changeAttributes(List.of(), Map.of(
|
||||
VALUE_ATTRIBUTE_NAME, value,
|
||||
DISPLAY_ATTRIBUTE_NAME, newval,
|
||||
MODIFIED_ATTRIBUTE_NAME, modified),
|
||||
"Value Updated");
|
||||
if (delta.added.containsKey(DISPLAY_ATTRIBUTE_NAME)) {
|
||||
listeners.fire.displayChanged(this, newval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,17 @@ import java.util.*;
|
|||
|
||||
import agent.gdb.manager.GdbRegister;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetRegisterContainer;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
// NB. The canonical container, but of no recognized interface
|
||||
@TargetObjectSchemaInfo(name = "RegisterValueContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetStackFrameRegisterContainer
|
||||
extends DefaultTargetObject<GdbModelTargetStackFrameRegister, GdbModelTargetStackFrame>
|
||||
implements TargetRegisterContainer<GdbModelTargetStackFrameRegisterContainer> {
|
||||
extends DefaultTargetObject<GdbModelTargetStackFrameRegister, GdbModelTargetStackFrame> {
|
||||
public static final String NAME = "Registers";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbModelTargetStackFrame frame;
|
||||
|
@ -35,7 +40,7 @@ public class GdbModelTargetStackFrameRegisterContainer
|
|||
new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetStackFrameRegisterContainer(GdbModelTargetStackFrame frame) {
|
||||
super(frame.impl, frame, "Registers", "StackFrameRegisterContainer");
|
||||
super(frame.impl, frame, NAME, "StackFrameRegisterContainer");
|
||||
this.impl = frame.impl;
|
||||
this.frame = frame;
|
||||
this.thread = frame.thread;
|
||||
|
@ -53,21 +58,9 @@ public class GdbModelTargetStackFrameRegisterContainer
|
|||
GdbModelTargetStackFrameRegister reg = getTargetRegister(gdbreg);
|
||||
registers.add(reg);
|
||||
}
|
||||
changeElements(List.of(), registers, "Refreshed");
|
||||
for (GdbModelTargetStackFrameRegister reg : registers) {
|
||||
String value = values.get(reg.register).toString(16);
|
||||
String oldval = (String) reg.getCachedAttributes().get(VALUE_ATTRIBUTE_NAME);
|
||||
reg.changeAttributes(List.of(), Map.of( //
|
||||
VALUE_ATTRIBUTE_NAME, value //
|
||||
), "Refreshed");
|
||||
if (values.get(reg.register).longValue() != 0) {
|
||||
String newval = reg.getName() + " : " + value;
|
||||
reg.changeAttributes(List.of(), Map.of( //
|
||||
DISPLAY_ATTRIBUTE_NAME, newval //
|
||||
), "Refreshed");
|
||||
reg.setModified(!value.equals(oldval));
|
||||
listeners.fire.displayChanged(this, newval);
|
||||
}
|
||||
reg.updateValue(values.get(reg.register));
|
||||
}
|
||||
changeElements(List.of(), registers, "Refreshed");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,15 @@ import agent.gdb.manager.impl.GdbMinimalSymbol;
|
|||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetSymbol;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Symbol", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetSymbol
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetSymbolContainer>
|
||||
implements TargetSymbol<GdbModelTargetSymbol> {
|
||||
|
@ -38,7 +44,7 @@ public class GdbModelTargetSymbol
|
|||
|
||||
protected final boolean constant;
|
||||
protected final Address value;
|
||||
protected final int size;
|
||||
protected final long size;
|
||||
|
||||
public GdbModelTargetSymbol(GdbModelTargetSymbolContainer symbols, GdbMinimalSymbol symbol) {
|
||||
super(symbols.impl, symbols, keySymbol(symbol), "Symbol");
|
||||
|
@ -47,10 +53,18 @@ public class GdbModelTargetSymbol
|
|||
this.size = 0;
|
||||
|
||||
changeAttributes(List.of(), Map.of(
|
||||
NAMESPACE_ATTRIBUTE_NAME, symbols,
|
||||
// TODO: DATA_TYPE
|
||||
VALUE_ATTRIBUTE_NAME, value, SIZE_ATTRIBUTE_NAME, size, DISPLAY_ATTRIBUTE_NAME,
|
||||
symbol.getName(), UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED //
|
||||
), "Initialized");
|
||||
VALUE_ATTRIBUTE_NAME, value,
|
||||
SIZE_ATTRIBUTE_NAME, size,
|
||||
DISPLAY_ATTRIBUTE_NAME, symbol.getName(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED),
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GdbModelTargetSymbolContainer getNamespace() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,18 +23,25 @@ import java.util.stream.Collectors;
|
|||
import agent.gdb.manager.impl.GdbMinimalSymbol;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetSymbolNamespace;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "SymbolContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetSymbolContainer
|
||||
extends DefaultTargetObject<GdbModelTargetSymbol, GdbModelTargetModule>
|
||||
implements TargetSymbolNamespace<GdbModelTargetSymbolContainer> {
|
||||
public static final String NAME = "Symbols";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbModelTargetModule module;
|
||||
|
||||
protected final Map<String, GdbModelTargetSymbol> symbolsByName = new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetSymbolContainer(GdbModelTargetModule module) {
|
||||
super(module.impl, module, "Symbols", "SymbolContainer");
|
||||
super(module.impl, module, NAME, "SymbolContainer");
|
||||
this.impl = module.impl;
|
||||
this.module = module;
|
||||
}
|
||||
|
|
|
@ -27,10 +27,16 @@ import agent.gdb.manager.reason.GdbBreakpointHitReason;
|
|||
import agent.gdb.manager.reason.GdbReason;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Thread", elements = {
|
||||
@TargetElementType(type = Void.class)
|
||||
}, attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
})
|
||||
public class GdbModelTargetThread
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements
|
||||
TargetThread<GdbModelTargetThread>, TargetExecutionStateful<GdbModelTargetThread>,
|
||||
|
@ -69,13 +75,16 @@ public class GdbModelTargetThread
|
|||
|
||||
this.stack = new GdbModelTargetStack(this, inferior);
|
||||
|
||||
changeAttributes(List.of(), Map.of( //
|
||||
STATE_ATTRIBUTE_NAME, convertState(thread.getState()), //
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, //
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(), //
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED, //
|
||||
stack.getName(), stack //
|
||||
), "Initialized");
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
stack),
|
||||
Map.of(
|
||||
STATE_ATTRIBUTE_NAME, convertState(thread.getState()),
|
||||
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS,
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(),
|
||||
UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.FIXED,
|
||||
stack.getName(), stack),
|
||||
"Initialized");
|
||||
|
||||
updateInfo().exceptionally(ex -> {
|
||||
Msg.error(this, "Could not initialize thread info");
|
||||
|
@ -83,6 +92,11 @@ public class GdbModelTargetThread
|
|||
});
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = GdbModelTargetStack.NAME, required = true, fixed = true)
|
||||
public GdbModelTargetStack getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> updateInfo() {
|
||||
return thread.getInfo().thenAccept(res -> {
|
||||
this.info = res;
|
||||
|
|
|
@ -23,18 +23,24 @@ import agent.gdb.manager.*;
|
|||
import agent.gdb.manager.reason.GdbReason;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
// TODO: Should TargetThreadContainer be a thing?
|
||||
@TargetObjectSchemaInfo(name = "ThreadContainer", attributes = {
|
||||
@TargetAttributeType(type = Void.class)
|
||||
}, canonicalContainer = true)
|
||||
public class GdbModelTargetThreadContainer
|
||||
extends DefaultTargetObject<GdbModelTargetThread, GdbModelTargetInferior> {
|
||||
public static final String NAME = "Threads";
|
||||
|
||||
protected final GdbModelImpl impl;
|
||||
protected final GdbInferior inferior;
|
||||
|
||||
protected final Map<Integer, GdbModelTargetThread> threadsById = new WeakValueHashMap<>();
|
||||
|
||||
public GdbModelTargetThreadContainer(GdbModelTargetInferior inferior) {
|
||||
super(inferior.impl, inferior, "Threads", "ThreadContainer");
|
||||
super(inferior.impl, inferior, NAME, "ThreadContainer");
|
||||
this.impl = inferior.impl;
|
||||
this.inferior = inferior.inferior;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
|||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetObject.TargetObjectListener;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -1019,4 +1021,18 @@ public abstract class AbstractModelForGdbTest
|
|||
focusSeq.peekLast());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeSchema() throws Throwable {
|
||||
try (ModelHost m = modelHost()) {
|
||||
DebuggerObjectModel model = m.getModel();
|
||||
init(m);
|
||||
|
||||
TargetObjectSchema rootSchema = model.getRootSchema();
|
||||
String serialized = XmlSchemaContext.serialize(rootSchema.getContext());
|
||||
System.out.println(serialized);
|
||||
|
||||
assertEquals("Session", rootSchema.getName().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ public class GadpForGdbTest extends AbstractModelForGdbTest {
|
|||
/*.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS)*/;
|
||||
addr = server.getLocalAddress();
|
||||
|
||||
server.setExitOnClosed(false);
|
||||
|
||||
socket = AsynchronousSocketChannel.open();
|
||||
client = new GadpClient("Test", socket);
|
||||
}
|
||||
|
|
|
@ -19,12 +19,10 @@ import java.util.*;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.gadp.client.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
@ -40,16 +38,12 @@ public enum GadpRegistry {
|
|||
CompletableFuture<Message.Builder> invoke(TargetObject object, M msg);
|
||||
}
|
||||
|
||||
public static final BidiMap<String, Class<? extends TargetObject>> INTERFACE_REGISTRY =
|
||||
new DualHashBidiMap<>();
|
||||
|
||||
public static final Map<Class<? extends TargetObject>, Class<? extends TargetObject>> MIXIN_REGISTRY =
|
||||
new HashMap<>();
|
||||
|
||||
public static <T extends TargetObject, U extends T> void registerInterface(Class<T> iface,
|
||||
Class<? extends T> mixin) {
|
||||
String name = DebuggerObjectModel.requireIfaceName(iface);
|
||||
INTERFACE_REGISTRY.put(name, iface);
|
||||
MIXIN_REGISTRY.put(iface, mixin);
|
||||
}
|
||||
|
||||
|
@ -94,22 +88,6 @@ public enum GadpRegistry {
|
|||
registerInterface(TargetThread.class, GadpClientTargetThread.class);
|
||||
}
|
||||
|
||||
public static List<Class<? extends TargetObject>> getInterfacesByName(
|
||||
Collection<String> names) {
|
||||
return names.stream()
|
||||
.filter(INTERFACE_REGISTRY::containsKey)
|
||||
.map(INTERFACE_REGISTRY::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Class<? extends TargetObject>> getMixinsByName(List<String> names) {
|
||||
return names.stream()
|
||||
.filter(INTERFACE_REGISTRY::containsKey)
|
||||
.map(INTERFACE_REGISTRY::get)
|
||||
.map(MIXIN_REGISTRY::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Class<? extends TargetObject>> getMixins(
|
||||
List<Class<? extends TargetObject>> ifaces) {
|
||||
return ifaces.stream().map(MIXIN_REGISTRY::get).collect(Collectors.toList());
|
||||
|
@ -118,15 +96,11 @@ public enum GadpRegistry {
|
|||
public static List<String> getInterfaceNames(TargetObject obj) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Class<?> parent : ReflectionUtilities.getAllParents(obj.getClass())) {
|
||||
String name = getName(parent);
|
||||
if (name != null) {
|
||||
result.add(name);
|
||||
DebuggerTargetObjectIface annot = parent.getAnnotation(DebuggerTargetObjectIface.class);
|
||||
if (annot != null) {
|
||||
result.add(annot.value());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> String getName(Class<T> cls) {
|
||||
return INTERFACE_REGISTRY.getKey(cls);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import ghidra.dbg.target.*;
|
|||
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility;
|
||||
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibilityListener;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointAction;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
@ -177,7 +178,7 @@ public class DelegateGadpClientTargetObject implements GadpClientTargetObject {
|
|||
|
||||
protected static GadpClientTargetObject makeModelProxy(GadpClient client, List<String> path,
|
||||
String typeHint, List<String> ifaceNames) {
|
||||
List<Class<? extends TargetObject>> ifaces = GadpRegistry.getInterfacesByName(ifaceNames);
|
||||
List<Class<? extends TargetObject>> ifaces = TargetObject.getInterfacesByName(ifaceNames);
|
||||
List<Class<? extends TargetObject>> mixins = GadpRegistry.getMixins(ifaces);
|
||||
return new DelegateGadpClientTargetObject(client, path, typeHint, ifaceNames, ifaces,
|
||||
mixins).proxy;
|
||||
|
@ -188,6 +189,7 @@ public class DelegateGadpClientTargetObject implements GadpClientTargetObject {
|
|||
protected final Cleanable cleanable;
|
||||
|
||||
private final GadpClientTargetObject proxy;
|
||||
private TargetObjectSchema schema; // lazily evaluated
|
||||
private final String typeHint;
|
||||
private final List<String> ifaceNames;
|
||||
private final List<Class<? extends TargetObject>> ifaces;
|
||||
|
@ -258,6 +260,14 @@ public class DelegateGadpClientTargetObject implements GadpClientTargetObject {
|
|||
return state.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObjectSchema getSchema() {
|
||||
if (schema == null) {
|
||||
schema = getModel().getRootSchema().getSuccessorSchema(getPath());
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeHint() {
|
||||
return typeHint;
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.google.protobuf.Message;
|
||||
|
@ -43,6 +44,8 @@ import ghidra.dbg.gadp.protocol.Gadp.*;
|
|||
import ghidra.dbg.gadp.util.*;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetObject.TargetUpdateMode;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.dbg.util.PathUtils.PathComparator;
|
||||
import ghidra.lifecycle.Internal;
|
||||
|
@ -267,6 +270,8 @@ public class GadpClient implements DebuggerObjectModel {
|
|||
protected AsyncReference<ChannelState, DebuggerModelClosedReason> channelState =
|
||||
new AsyncReference<>(ChannelState.INACTIVE);
|
||||
protected GadpVersion activeVersion = null;
|
||||
protected XmlSchemaContext schemaContext;
|
||||
protected TargetObjectSchema rootSchema;
|
||||
|
||||
protected final ListenerSet<DebuggerModelListener> listenersClient =
|
||||
new ListenerSet<>(DebuggerModelListener.class);
|
||||
|
@ -461,6 +466,13 @@ public class GadpClient implements DebuggerObjectModel {
|
|||
GadpVersion version = GadpVersion.getByName(rep.getVersion());
|
||||
synchronized (this) {
|
||||
activeVersion = version;
|
||||
try {
|
||||
schemaContext = XmlSchemaContext.deserialize(rep.getSchemaContext());
|
||||
}
|
||||
catch (JDOMException e) {
|
||||
throw new GadpMessageException("Invalid schema context XML", e);
|
||||
}
|
||||
rootSchema = schemaContext.getSchema(schemaContext.name(rep.getRootSchema()));
|
||||
}
|
||||
channelState.set(ChannelState.ACTIVE, null);
|
||||
// launches in background in parallel, with its own error reporting
|
||||
|
@ -500,6 +512,11 @@ public class GadpClient implements DebuggerObjectModel {
|
|||
return channelState.get() == ChannelState.ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObjectSchema getRootSchema() {
|
||||
return rootSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> ping(String content) {
|
||||
Gadp.PingRequest.Builder req = Gadp.PingRequest.newBuilder().setContent(content);
|
||||
|
|
|
@ -60,7 +60,7 @@ public interface GadpClientTargetBreakpointSpec
|
|||
}
|
||||
|
||||
default boolean enabledFromObj(Object obj) {
|
||||
return ValueUtils.expectBoolean(obj, this, ENABLED_ATTRIBUTE_NAME, false);
|
||||
return ValueUtils.expectBoolean(obj, this, ENABLED_ATTRIBUTE_NAME, false, true);
|
||||
}
|
||||
|
||||
@GadpAttributeChangeCallback(ENABLED_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -24,7 +24,7 @@ public interface GadpClientTargetExecutionStateful
|
|||
|
||||
default TargetExecutionState stateFromObj(Object obj) {
|
||||
return ValueUtils.expectType(obj, TargetExecutionState.class, this,
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE);
|
||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.INACTIVE, true);
|
||||
}
|
||||
|
||||
@GadpAttributeChangeCallback(STATE_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -45,7 +45,8 @@ public interface GadpClientTargetFocusScope
|
|||
}
|
||||
|
||||
default TargetObjectRef refFromObj(Object obj) {
|
||||
return ValueUtils.expectType(obj, TargetObjectRef.class, this, FOCUS_ATTRIBUTE_NAME, this);
|
||||
return ValueUtils.expectType(obj, TargetObjectRef.class, this, FOCUS_ATTRIBUTE_NAME, this,
|
||||
true);
|
||||
}
|
||||
|
||||
@GadpAttributeChangeCallback(FOCUS_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -48,7 +48,7 @@ public interface GadpClientTargetInterpreter
|
|||
}
|
||||
|
||||
default String promptFromObj(Object obj) {
|
||||
return ValueUtils.expectType(obj, String.class, this, PROMPT_ATTRIBUTE_NAME, ">");
|
||||
return ValueUtils.expectType(obj, String.class, this, PROMPT_ATTRIBUTE_NAME, ">", true);
|
||||
}
|
||||
|
||||
@GadpAttributeChangeCallback(PROMPT_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -63,7 +63,8 @@ public interface GadpClientTargetObject extends TargetObject {
|
|||
}
|
||||
|
||||
default String displayFromObj(Object obj) {
|
||||
return ValueUtils.expectType(obj, String.class, this, DISPLAY_ATTRIBUTE_NAME, getName());
|
||||
return ValueUtils.expectType(obj, String.class, this, DISPLAY_ATTRIBUTE_NAME, getName(),
|
||||
false);
|
||||
}
|
||||
|
||||
@GadpAttributeChangeCallback(DISPLAY_ATTRIBUTE_NAME)
|
||||
|
|
|
@ -32,6 +32,7 @@ public abstract class AbstractGadpServer
|
|||
public static final String LISTENING_ON = "GADP Server listening on ";
|
||||
|
||||
protected final DebuggerObjectModel model;
|
||||
private boolean exitOnClosed = true;
|
||||
|
||||
public AbstractGadpServer(DebuggerObjectModel model, SocketAddress addr) throws IOException {
|
||||
super(addr);
|
||||
|
@ -70,6 +71,20 @@ public abstract class AbstractGadpServer
|
|||
@Override
|
||||
public void modelClosed(DebuggerModelClosedReason reason) {
|
||||
System.err.println("Model closed: " + reason);
|
||||
if (exitOnClosed) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, the GADP server will terminate the VM when the model is closed
|
||||
*
|
||||
* <p>
|
||||
* For testing purposes, it may be useful to disable this action.
|
||||
*
|
||||
* @param exitOnClosed true to terminate the VM on close, false to remain running
|
||||
*/
|
||||
public void setExitOnClosed(boolean exitOnClosed) {
|
||||
this.exitOnClosed = exitOnClosed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ import ghidra.dbg.target.TargetInterpreter.TargetInterpreterListener;
|
|||
import ghidra.dbg.target.TargetMemory.TargetMemoryListener;
|
||||
import ghidra.dbg.target.TargetObject.TargetObjectListener;
|
||||
import ghidra.dbg.target.TargetRegisterBank.TargetRegisterBankListener;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -375,9 +377,13 @@ public class GadpClientHandler
|
|||
if (!req.getVersionList().contains(ver)) {
|
||||
throw new GadpErrorException(ErrorCode.NO_VERSION, "No listed version is supported");
|
||||
}
|
||||
TargetObjectSchema rootSchema = model.getRootSchema();
|
||||
return channel.write(Gadp.RootMessage.newBuilder()
|
||||
.setSequence(seqno)
|
||||
.setConnectReply(Gadp.ConnectReply.newBuilder().setVersion(ver))
|
||||
.setConnectReply(Gadp.ConnectReply.newBuilder()
|
||||
.setVersion(ver)
|
||||
.setSchemaContext(XmlSchemaContext.serialize(rootSchema.getContext()))
|
||||
.setRootSchema(rootSchema.getName().toString()))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@ import ghidra.dbg.gadp.client.GadpClient;
|
|||
import ghidra.dbg.gadp.protocol.Gadp;
|
||||
import ghidra.dbg.gadp.protocol.Gadp.ModelObjectDelta;
|
||||
import ghidra.dbg.gadp.protocol.Gadp.ModelObjectInfo;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetEventScope.TargetEventType;
|
||||
|
@ -78,6 +80,12 @@ public enum GadpValueUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static Gadp.BreakKindsSet makeBreakKindSet(Set<TargetBreakpointKind> set) {
|
||||
return Gadp.BreakKindsSet.newBuilder()
|
||||
.addAllK(set.stream().map(k -> makeBreakKind(k)).collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Gadp.BreakKind makeBreakKind(TargetBreakpointKind kind) {
|
||||
switch (kind) {
|
||||
case READ:
|
||||
|
@ -93,6 +101,39 @@ public enum GadpValueUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static TargetAttachKindSet getAttachKindSet(Gadp.AttachKindSet set) {
|
||||
return TargetAttachKindSet.copyOf(
|
||||
set.getKList().stream().map(k -> getAttachKind(k)).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
public static TargetAttachKind getAttachKind(Gadp.AttachKind kind) {
|
||||
switch (kind) {
|
||||
case BY_OBJECT_REF:
|
||||
return TargetAttachKind.BY_OBJECT_REF;
|
||||
case BY_ID:
|
||||
return TargetAttachKind.BY_ID;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Gadp.AttachKindSet makeAttachKindSet(Set<TargetAttachKind> set) {
|
||||
return Gadp.AttachKindSet.newBuilder()
|
||||
.addAllK(set.stream().map(k -> makeAttachKind(k)).collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Gadp.AttachKind makeAttachKind(TargetAttachKind kind) {
|
||||
switch (kind) {
|
||||
case BY_OBJECT_REF:
|
||||
return Gadp.AttachKind.BY_OBJECT_REF;
|
||||
case BY_ID:
|
||||
return Gadp.AttachKind.BY_ID;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static TargetStepKindSet getStepKindSet(Gadp.StepKindsSet set) {
|
||||
return TargetStepKindSet.copyOf(
|
||||
set.getKList().stream().map(k -> getStepKind(k)).collect(Collectors.toSet()));
|
||||
|
@ -304,12 +345,6 @@ public enum GadpValueUtils {
|
|||
.build();
|
||||
}
|
||||
|
||||
public static Gadp.BreakKindsSet makeBreakKindSet(Set<TargetBreakpointKind> set) {
|
||||
return Gadp.BreakKindsSet.newBuilder()
|
||||
.addAllK(set.stream().map(k -> makeBreakKind(k)).collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Gadp.RegisterValue makeRegisterValue(Map.Entry<String, byte[]> e) {
|
||||
return Gadp.RegisterValue.newBuilder()
|
||||
.setName(e.getKey())
|
||||
|
@ -569,6 +604,9 @@ public enum GadpValueUtils {
|
|||
else if (value instanceof AddressRange) {
|
||||
b.setRangeValue(makeRange((AddressRange) value));
|
||||
}
|
||||
else if (value instanceof TargetAttachKindSet) {
|
||||
b.setAttachKindsValue(makeAttachKindSet((TargetAttachKindSet) value));
|
||||
}
|
||||
else if (value instanceof TargetBreakpointKindSet) {
|
||||
b.setBreakKindsValue(makeBreakKindSet((TargetBreakpointKindSet) value));
|
||||
}
|
||||
|
@ -653,6 +691,8 @@ public enum GadpValueUtils {
|
|||
return getAddress(model, value.getAddressValue());
|
||||
case RANGE_VALUE:
|
||||
return getAddressRange(model, value.getRangeValue());
|
||||
case ATTACH_KINDS_VALUE:
|
||||
return getAttachKindSet(value.getAttachKindsValue());
|
||||
case BREAK_KINDS_VALUE:
|
||||
return getBreakKindSet(value.getBreakKindsValue());
|
||||
case EXEC_STATE_VALUE:
|
||||
|
|
|
@ -46,6 +46,8 @@ message ConnectRequest {
|
|||
|
||||
message ConnectReply {
|
||||
string version = 1;
|
||||
string schema_context = 2;
|
||||
string root_schema = 3;
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
|
@ -106,6 +108,15 @@ message StringList {
|
|||
repeated string s = 1;
|
||||
}
|
||||
|
||||
enum AttachKind {
|
||||
BY_OBJECT_REF = 0;
|
||||
BY_ID = 1;
|
||||
}
|
||||
|
||||
message AttachKindSet {
|
||||
repeated AttachKind k = 1;
|
||||
}
|
||||
|
||||
enum ExecutionState {
|
||||
INACTIVE = 0;
|
||||
ALIVE = 1;
|
||||
|
@ -181,6 +192,7 @@ enum ValueType {
|
|||
VT_PATH = 17;
|
||||
VT_PATH_LIST = 18;
|
||||
VT_TYPE = 19;
|
||||
VT_ATTACH_KIND_SET = 20;
|
||||
}
|
||||
|
||||
message Parameter {
|
||||
|
@ -226,6 +238,7 @@ message Value {
|
|||
ModelObjectStub object_stub = 20;
|
||||
ParameterList parameters_value = 21;
|
||||
ValueType type_value = 22;
|
||||
AttachKindSet attach_kinds_value = 23;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
|||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetObject.TargetObjectFetchingListener;
|
||||
import ghidra.dbg.target.TargetObject.TargetObjectListener;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.dbg.util.AttributesChangedListener.AttributesChangedInvocation;
|
||||
import ghidra.dbg.util.ElementsChangedListener.ElementsChangedInvocation;
|
||||
|
@ -239,6 +241,7 @@ public class GadpClientServerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Session")
|
||||
public class TestGadpTargetSession extends DefaultTargetModelRoot
|
||||
implements TargetFocusScope<TestGadpTargetSession> {
|
||||
protected final TestGadpTargetAvailableContainer available =
|
||||
|
@ -254,6 +257,16 @@ public class GadpClientServerTest {
|
|||
changeAttributes(List.of(), List.of(available, processes), Map.of(), "Initialized");
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = "Available", required = true, fixed = true)
|
||||
public TestGadpTargetAvailableContainer getAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = "Processes", required = true, fixed = true)
|
||||
public TestGadpTargetProcessContainer getProcesses() {
|
||||
return processes;
|
||||
}
|
||||
|
||||
public void addLinks() {
|
||||
assertNotNull(available.fetchElements().getNow(null));
|
||||
links.setElements(List.of(), Map.of(
|
||||
|
|
|
@ -128,7 +128,7 @@ public abstract class SctlTargetNamedDataType<T extends SctlTargetNamedDataType<
|
|||
}
|
||||
|
||||
@Override
|
||||
public NamedDataTypeKind getKind() {
|
||||
public NamedDataTypeKind getTypeKind() {
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ public class AsyncLock {
|
|||
protected final Deque<CompletableFuture<Hold>> queue = new LinkedList<>();
|
||||
protected WeakReference<Hold> curHold;
|
||||
protected int reentries = 0;
|
||||
protected String disposalReason;
|
||||
protected Throwable disposalReason;
|
||||
protected boolean dead = false;
|
||||
protected final String debugName;
|
||||
|
||||
|
@ -242,7 +242,7 @@ public class AsyncLock {
|
|||
Hold strongHold = null;
|
||||
synchronized (this) {
|
||||
if (disposalReason != null) {
|
||||
throw new RuntimeException(disposalReason);
|
||||
return CompletableFuture.failedFuture(disposalReason);
|
||||
}
|
||||
if (dead) {
|
||||
throw new IllegalStateException("This lock is dead! " +
|
||||
|
@ -318,7 +318,7 @@ public class AsyncLock {
|
|||
/**
|
||||
* Destroy this lock, causing all pending actions to complete exceptionally
|
||||
*/
|
||||
public void dispose(String reason) {
|
||||
public void dispose(Throwable reason) {
|
||||
List<CompletableFuture<?>> copy;
|
||||
synchronized (this) {
|
||||
disposalReason = reason;
|
||||
|
@ -326,7 +326,7 @@ public class AsyncLock {
|
|||
queue.clear();
|
||||
}
|
||||
for (CompletableFuture<?> future : copy) {
|
||||
future.completeExceptionally(new RuntimeException(reason));
|
||||
future.completeExceptionally(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,14 @@ import ghidra.util.TriConsumer;
|
|||
/**
|
||||
* An observable reference useful for asynchronous computations
|
||||
*
|
||||
* <p>
|
||||
* The reference supports the usual set and get operations. The set operation accepts an optional
|
||||
* "cause" argument which is forwarded to some observers. The set operation may also be intercepted
|
||||
* by an optional filter. The filter function is provided a copy of the current value, proposed
|
||||
* value, and cause. The value it returns becomes the new value. If that value is different than the
|
||||
* current value, the observers are notified. The default filter returns the new value, always.
|
||||
*
|
||||
* <p>
|
||||
* The reference provides three types of observation callbacks. The first is to listen for all
|
||||
* changes. This follows the listener pattern. When the value changes, i.e., is set to a value
|
||||
* different than the current value, all change listener are invoked with a copy of the new value
|
||||
|
@ -53,7 +55,7 @@ public class AsyncReference<T, C> {
|
|||
private final Map<T, CompletableFuture<Void>> waitsFor = new HashMap<>();
|
||||
private final List<WaitUntilFuture<T>> waitsUntil = new ArrayList<>();
|
||||
private FilterFunction<T, ? super C> filter = (cur, set, cause) -> set;
|
||||
private String disposalReason;
|
||||
private Throwable disposalReason;
|
||||
|
||||
/**
|
||||
* A function to filter updates to an {@link AsyncReference}
|
||||
|
@ -106,6 +108,7 @@ public class AsyncReference<T, C> {
|
|||
/**
|
||||
* Apply a filter function to all subsequent updates
|
||||
*
|
||||
* <p>
|
||||
* The given function replaces the current function.
|
||||
*
|
||||
* @param newFilter the filter
|
||||
|
@ -261,6 +264,7 @@ public class AsyncReference<T, C> {
|
|||
/**
|
||||
* Add a listener for any change to this reference's value
|
||||
*
|
||||
* <p>
|
||||
* Updates that get "filtered out" do not cause a change listener to fire.
|
||||
*
|
||||
* @param listener the listener, which is passed the new value (post-filter) and cause
|
||||
|
@ -296,7 +300,7 @@ public class AsyncReference<T, C> {
|
|||
*/
|
||||
public synchronized CompletableFuture<T> waitChanged() {
|
||||
if (disposalReason != null) {
|
||||
return CompletableFuture.failedFuture(new IllegalStateException(disposalReason));
|
||||
return CompletableFuture.failedFuture(disposalReason);
|
||||
}
|
||||
if (changePromise == null) {
|
||||
changePromise = new CompletableFuture<>();
|
||||
|
@ -307,6 +311,7 @@ public class AsyncReference<T, C> {
|
|||
/**
|
||||
* Wait for this reference to accept a particular value (post-filter)
|
||||
*
|
||||
* <p>
|
||||
* If the reference already has the given value, a completed future is returned.
|
||||
*
|
||||
* @param t the expected value to wait on
|
||||
|
@ -314,7 +319,7 @@ public class AsyncReference<T, C> {
|
|||
*/
|
||||
public synchronized CompletableFuture<Void> waitValue(T t) {
|
||||
if (disposalReason != null) {
|
||||
return CompletableFuture.failedFuture(new IllegalStateException(disposalReason));
|
||||
return CompletableFuture.failedFuture(disposalReason);
|
||||
}
|
||||
if (Objects.equals(this.val, t)) {
|
||||
return AsyncUtils.NIL;
|
||||
|
@ -330,6 +335,7 @@ public class AsyncReference<T, C> {
|
|||
/**
|
||||
* Wait for this reference to accept the first value meeting the given condition (post-filter)
|
||||
*
|
||||
* <p>
|
||||
* If the current value already meets the condition, a completed future is returned.
|
||||
*
|
||||
* @param predicate the condition to meet
|
||||
|
@ -337,7 +343,7 @@ public class AsyncReference<T, C> {
|
|||
*/
|
||||
public synchronized CompletableFuture<T> waitUntil(Predicate<T> predicate) {
|
||||
if (disposalReason != null) {
|
||||
return CompletableFuture.failedFuture(new IllegalStateException(disposalReason));
|
||||
return CompletableFuture.failedFuture(disposalReason);
|
||||
}
|
||||
if (predicate.test(val)) {
|
||||
return CompletableFuture.completedFuture(val);
|
||||
|
@ -350,9 +356,9 @@ public class AsyncReference<T, C> {
|
|||
/**
|
||||
* Clear out the queues of future, completing each exceptionally
|
||||
*
|
||||
* @param reason the reason for disposal (message in the exception)
|
||||
* @param reason the reason for disposal
|
||||
*/
|
||||
public void dispose(String reason) {
|
||||
public void dispose(Throwable reason) {
|
||||
List<CompletableFuture<?>> toExcept = new ArrayList<>();
|
||||
synchronized (this) {
|
||||
disposalReason = reason;
|
||||
|
@ -440,17 +446,21 @@ public class AsyncReference<T, C> {
|
|||
/**
|
||||
* Obtain a new {@link AsyncReference} whose value is updated after this reference has settled
|
||||
*
|
||||
* <p>
|
||||
* The original {@link AsyncReference} continues to behave as usual, except that is has an
|
||||
* additional listener on it. When this reference is updated, the update is passed through an
|
||||
* {@link AsyncDebouncer} configured with the given timer and window. When the debouncer
|
||||
* settles, the debounced reference is updated.
|
||||
*
|
||||
* <p>
|
||||
* Directly updating, i.e., calling {@link #set(Object, Object)} on, the debounced reference
|
||||
* subverts the debouncing mechanism, and will result in an exception. Only the original
|
||||
* reference should be updated directly.
|
||||
*
|
||||
* <p>
|
||||
* Setting a filter on the debounced reference may have undefined behavior.
|
||||
*
|
||||
* <p>
|
||||
* If the original reference changes value rapidly, settling on the debounced reference's
|
||||
* current value, no update event is produced by the debounced reference. If the original
|
||||
* reference changes value rapidly, settling on a value different from the debounced reference's
|
||||
|
|
|
@ -26,6 +26,8 @@ import ghidra.dbg.error.DebuggerModelNoSuchPathException;
|
|||
import ghidra.dbg.error.DebuggerModelTypeException;
|
||||
import ghidra.dbg.target.TargetMemory;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
|
@ -165,6 +167,21 @@ public interface DebuggerObjectModel {
|
|||
*/
|
||||
public boolean isAlive();
|
||||
|
||||
/**
|
||||
* Get the schema of this model, i.e., the schema of its root object.
|
||||
*
|
||||
* <p>
|
||||
* The schema may not be known until the model has been successfully opened. Some factories will
|
||||
* ensure success before providing the model, but this may not always be the case. Callers
|
||||
* should listen for {@link DebuggerModelListener#modelOpened()} or retrieve the root object
|
||||
* first.
|
||||
*
|
||||
* @return the root schema
|
||||
*/
|
||||
public default TargetObjectSchema getRootSchema() {
|
||||
return EnumerableTargetObjectSchema.OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the debugger agent is alive (optional operation)
|
||||
*
|
||||
|
|
|
@ -21,6 +21,8 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
|
@ -49,13 +51,15 @@ public abstract class AbstractTargetObject<P extends TargetObject>
|
|||
protected final List<String> path;
|
||||
protected final int hash;
|
||||
protected final String typeHint;
|
||||
protected final TargetObjectSchema schema;
|
||||
|
||||
protected boolean valid = true;
|
||||
|
||||
protected final ListenerSet<TargetObjectListener> listeners =
|
||||
new ListenerSet<>(TargetObjectListener.class);
|
||||
|
||||
public AbstractTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
|
||||
public AbstractTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint,
|
||||
TargetObjectSchema schema) {
|
||||
this.model = model;
|
||||
this.parent = parent;
|
||||
this.completedParent = CompletableFuture.completedFuture(parent);
|
||||
|
@ -67,6 +71,45 @@ public abstract class AbstractTargetObject<P extends TargetObject>
|
|||
}
|
||||
this.hash = computeHashCode();
|
||||
this.typeHint = typeHint;
|
||||
|
||||
this.schema = schema;
|
||||
schema.validateTypeAndInterfaces(getProxy(), null, enforcesStrictSchema());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an alternative for {@code this} when invoking the listeners.
|
||||
*
|
||||
* <p>
|
||||
* Some implementations may use on a proxy-delegate pattern to implement target objects with
|
||||
* various combinations of supported interfaces. When this pattern is employed, the delegate
|
||||
* will extend {@link DefaultTargetObject}, causing {@code this} to refer to the delegate rather
|
||||
* than the proxy. When invoking listeners, the proxy given by this method is used instead. By
|
||||
* default, it simply returns {@code this}, providing the expected behavior for typical
|
||||
* implementations. The proxy is also used for schema interface validation.
|
||||
*
|
||||
* @return the proxy or this
|
||||
*/
|
||||
public TargetObject getProxy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this object strictly conforms to the schema
|
||||
*
|
||||
* <p>
|
||||
* This method exists to support the transition to schemas. If this method returns false, schema
|
||||
* violations are logged, but whatever changes were requested that caused the violation are
|
||||
* still allowed to occur. If it returns true, then any schema violation will cause an
|
||||
* {@link AssertionError}. Because schema violations are presumed to be programming errors,
|
||||
* there is no guarantee of consistency after an exception is thrown. In general, models without
|
||||
* explicit schemas should not fail validation, since objects will likely be assigned
|
||||
* {@link EnumerableTargetObjectSchema#ANY}. When developing a schema for an existing model, it
|
||||
* may be useful to override this to return false in the interim.
|
||||
*
|
||||
* @return true to throw exceptions on schema violations.
|
||||
*/
|
||||
protected boolean enforcesStrictSchema() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,8 +124,8 @@ public abstract class AbstractTargetObject<P extends TargetObject>
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<Local " + getClass().getSimpleName() + ": " + path + " in " +
|
||||
getModel() + ">";
|
||||
return String.format("<%s: path=%s model=%s schema=%s>",
|
||||
getClass().getSimpleName(), path, getModel(), schema.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -90,6 +133,11 @@ public abstract class AbstractTargetObject<P extends TargetObject>
|
|||
return typeHint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObjectSchema getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
|
|
|
@ -18,11 +18,18 @@ package ghidra.dbg.agent;
|
|||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.TargetAggregate;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
|
||||
public class DefaultTargetModelRoot extends DefaultTargetObject<TargetObject, TargetObject>
|
||||
implements TargetAggregate {
|
||||
|
||||
public DefaultTargetModelRoot(DebuggerObjectModel model, String typeHint) {
|
||||
super(model, null, null, typeHint);
|
||||
this(model, typeHint, EnumerableTargetObjectSchema.OBJECT);
|
||||
}
|
||||
|
||||
public DefaultTargetModelRoot(DebuggerObjectModel model, String typeHint,
|
||||
TargetObjectSchema schema) {
|
||||
super(model, null, null, typeHint, schema);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.async.AsyncUtils;
|
|||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.attributes.TargetObjectRef;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||
|
@ -48,6 +49,20 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
new TreeMap<>(TargetObjectKeyComparator.ATTRIBUTE);
|
||||
protected CompletableFuture<Void> curAttrsRequest;
|
||||
|
||||
/**
|
||||
* Construct a new default target object whose schema is derived from the parent
|
||||
*
|
||||
* @see #DefaultTargetObject(DebuggerObjectModel, TargetObject, String, String,
|
||||
* TargetObjectSchema)
|
||||
* @param model the model to which the object belongs
|
||||
* @param parent the (non-null) parent of this object
|
||||
* @param key the key (attribute name or element index) of this object
|
||||
* @param typeHint the type hint for this object
|
||||
*/
|
||||
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
|
||||
this(model, parent, key, typeHint, parent.getSchema().getChildSchema(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new default target object
|
||||
*
|
||||
|
@ -71,31 +86,16 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
* @param parent the parent of this object
|
||||
* @param key the key (attribute name or element index) of this object
|
||||
* @param typeHint the type hint for this object
|
||||
* @param schema the schema of this object
|
||||
*/
|
||||
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
|
||||
super(model, parent, key, typeHint);
|
||||
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint,
|
||||
TargetObjectSchema schema) {
|
||||
super(model, parent, key, typeHint, schema);
|
||||
changeAttributes(List.of(), List.of(), Map.of(DISPLAY_ATTRIBUTE_NAME,
|
||||
key == null ? "<root>" : key, UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED),
|
||||
"Initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an alternative for {@code this} when invoking the listeners.
|
||||
*
|
||||
* <p>
|
||||
* Some implementations may use on a proxy-delegate pattern to implement target objects with
|
||||
* various combinations of supported interfaces. When this pattern is employed, the delegate
|
||||
* will extend {@link DefaultTargetObject}, causing {@code this} to refer to the delegate rather
|
||||
* than the proxy. When invoking listeners, the proxy given by this method is used instead. By
|
||||
* default, it simply returns {@code this}, providing the expected behavior for typical
|
||||
* implementations.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public TargetObject getProxy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this object is being observed
|
||||
*
|
||||
|
@ -237,6 +237,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
synchronized (this.elements) {
|
||||
delta = Delta.computeAndSet(this.elements, elements, Delta.SAME);
|
||||
}
|
||||
getSchema().validateElementDelta(getProxy(), delta, enforcesStrictSchema());
|
||||
doInvalidateElements(delta.removed.values(), reason);
|
||||
if (!delta.isEmpty()) {
|
||||
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
@ -278,6 +279,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
synchronized (elements) {
|
||||
delta = Delta.apply(this.elements, remove, add, Delta.SAME);
|
||||
}
|
||||
getSchema().validateElementDelta(getProxy(), delta, enforcesStrictSchema());
|
||||
doInvalidateElements(delta.removed.values(), reason);
|
||||
if (!delta.isEmpty()) {
|
||||
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
@ -393,6 +395,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
synchronized (this.attributes) {
|
||||
delta = Delta.computeAndSet(this.attributes, attributes, Delta.EQUAL);
|
||||
}
|
||||
getSchema().validateAttributeDelta(getProxy(), delta, enforcesStrictSchema());
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
@ -434,6 +437,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
|
|||
synchronized (attributes) {
|
||||
delta = Delta.apply(this.attributes, remove, add, Delta.EQUAL);
|
||||
}
|
||||
getSchema().validateAttributeDelta(getProxy(), delta, enforcesStrictSchema());
|
||||
doInvalidateAttributes(delta.removed, reason);
|
||||
if (!delta.isEmpty()) {
|
||||
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/* ###
|
||||
* 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.dbg.error;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
||||
/**
|
||||
* An exception which should not alert the user
|
||||
*
|
||||
* <p>
|
||||
* At most, it can be logged, probably without a stack trace. These are sorts of soft warnings,
|
||||
* which might be issued when an exception is a normal occurrence. One example is when a model is
|
||||
* shutting down. It's common for requests in the queue to be rejected once the model beings
|
||||
* shutting down. Exceptions raised by those requests can likely be ignored. Please note, clients
|
||||
* will likely need to apply {@link AsyncUtils#unwrapThrowable(Throwable)} in order to determine
|
||||
* whether the exception is ignorable. Alternatively, use {@link #isIgnorable(Throwable)}.
|
||||
*/
|
||||
public class DebuggerIgnorableException extends DebuggerRuntimeException {
|
||||
public static boolean isIgnorable(Throwable ex) {
|
||||
Throwable u = AsyncUtils.unwrapThrowable(ex);
|
||||
return u instanceof DebuggerIgnorableException;
|
||||
}
|
||||
|
||||
public DebuggerIgnorableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DebuggerIgnorableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg.error;
|
||||
|
||||
/**
|
||||
* A request was rejected, because the model is shutting down
|
||||
*/
|
||||
public class DebuggerModelTerminatingException extends DebuggerIgnorableException {
|
||||
public DebuggerModelTerminatingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DebuggerModelTerminatingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -18,12 +18,14 @@ package ghidra.dbg.target;
|
|||
import org.apache.commons.lang3.reflect.TypeLiteral;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.ValueUtils;
|
||||
import ghidra.lifecycle.Internal;
|
||||
|
||||
/**
|
||||
* A target object which may not be accessible
|
||||
*
|
||||
* <p>
|
||||
* Depending on the state of the debugger, it may not be able to process commands for certain target
|
||||
* objects. Objects which may not be accessible should support this interface. Note, that the
|
||||
* granularity of accessibility is the entire object, including its children (excluding links). If,
|
||||
|
@ -45,6 +47,9 @@ public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
|
|||
TypeLiteral<TargetAccessConditioned<?>> type = new TypeLiteral<>() {};
|
||||
String ACCESSIBLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "accessible";
|
||||
|
||||
/**
|
||||
* TODO: I'm seriously considering removing this
|
||||
*/
|
||||
public enum TargetAccessibility {
|
||||
ACCESSIBLE, INACCESSIBLE;
|
||||
|
||||
|
@ -59,7 +64,13 @@ public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
|
|||
return TargetAccessibility.ACCESSIBLE;
|
||||
}
|
||||
return TargetAccessibility
|
||||
.fromBool(ValueUtils.expectBoolean(obj, this, ACCESSIBLE_ATTRIBUTE_NAME, true));
|
||||
.fromBool(
|
||||
ValueUtils.expectBoolean(obj, this, ACCESSIBLE_ATTRIBUTE_NAME, true, true));
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = ACCESSIBLE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default Boolean isAccessible() {
|
||||
return getTypedAttributeNowByName(ACCESSIBLE_ATTRIBUTE_NAME, Boolean.class, true);
|
||||
}
|
||||
|
||||
public default TargetAccessibility getAccessibility() {
|
||||
|
@ -69,7 +80,6 @@ public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
|
|||
public interface TargetAccessibilityListener extends TargetObjectListener {
|
||||
default void accessibilityChanged(TargetAccessConditioned<?> object,
|
||||
TargetAccessibility accessibility) {
|
||||
System.err.println("default");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
/**
|
||||
* A marker interface which indicates its attributes represent the object as a whole
|
||||
*
|
||||
* <p>
|
||||
* Often applied to processes and sessions, this causes ancestry traversals to include this object's
|
||||
* children when visited.
|
||||
*/
|
||||
|
|
|
@ -20,12 +20,13 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet.ImmutableTargetStepKindSet;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.CollectionUtils;
|
||||
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
|
||||
|
||||
/**
|
||||
* An object which is capable of attaching to a {@link TargetAttachable}
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Attacher")
|
||||
public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTargetObject<T> {
|
||||
enum Private {
|
||||
|
@ -62,18 +63,18 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
|
|||
return EMPTY;
|
||||
}
|
||||
|
||||
public static TargetStepKindSet of(TargetStepKind... kinds) {
|
||||
return new ImmutableTargetStepKindSet(kinds);
|
||||
public static TargetAttachKindSet of(TargetAttachKind... kinds) {
|
||||
return new ImmutableTargetAttachKindSet(kinds);
|
||||
}
|
||||
|
||||
public static TargetStepKindSet copyOf(Set<TargetStepKind> set) {
|
||||
return new ImmutableTargetStepKindSet(set);
|
||||
public static TargetAttachKindSet copyOf(Set<TargetAttachKind> set) {
|
||||
return new ImmutableTargetAttachKindSet(set);
|
||||
}
|
||||
}
|
||||
|
||||
enum TargetAttachKind {
|
||||
/**
|
||||
* Use an "attachable" object
|
||||
* Use a {@link TargetAttachable} object
|
||||
*/
|
||||
BY_OBJECT_REF,
|
||||
/**
|
||||
|
@ -87,11 +88,13 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
|
|||
/**
|
||||
* Get the kinds of multi-stepping implemented by the debugger
|
||||
*
|
||||
* Different debuggers may provide similar, but slightly different vocabularies of stepping.
|
||||
* This method queries the connected debugger for its supported step kinds.
|
||||
* <p>
|
||||
* Different debuggers provide varying methods of attaching. This attribute describes which are
|
||||
* supported. NOTE: This should be replaced by generic method invocation.
|
||||
*
|
||||
* @return the set of supported multi-step operations
|
||||
* @return the set of supported attach operations
|
||||
*/
|
||||
@TargetAttributeType(name = SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default TargetAttachKindSet getSupportedAttachKinds() {
|
||||
return getTypedAttributeNowByName(SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME,
|
||||
TargetAttachKindSet.class, TargetAttachKindSet.of());
|
||||
|
@ -100,6 +103,7 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
|
|||
/**
|
||||
* Attach to the given {@link TargetAttachable} or reference
|
||||
*
|
||||
* <p>
|
||||
* This is mostly applicable to user-space contexts, in which case, this usually means to attach
|
||||
* to a process.
|
||||
*
|
||||
|
@ -111,6 +115,7 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
|
|||
/**
|
||||
* Attach to the given id
|
||||
*
|
||||
* <p>
|
||||
* This is mostly applicable to user-space contexts, in which case, this usually means to attach
|
||||
* to a process using its pid.
|
||||
*
|
||||
|
@ -118,5 +123,4 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
|
|||
* @return a future which completes when the command is confirmed
|
||||
*/
|
||||
public CompletableFuture<Void> attach(long id);
|
||||
|
||||
}
|
||||
|
|
|
@ -22,10 +22,18 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
import ghidra.dbg.attributes.TargetObjectRef;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
|
||||
import ghidra.dbg.util.CollectionUtils.AbstractNSet;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
/**
|
||||
* A container for breakpoint specifications and/or locations
|
||||
*
|
||||
* <p>
|
||||
* This interface provides for the placment (creation) of breakpoints and as a listening point for
|
||||
* breakpoint events. Typically, it is implemented by an object whose elements are breakpoints.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("BreakpointContainer")
|
||||
public interface TargetBreakpointContainer<T extends TargetBreakpointContainer<T>>
|
||||
extends TypedTargetObject<T> {
|
||||
|
@ -74,17 +82,60 @@ public interface TargetBreakpointContainer<T extends TargetBreakpointContainer<T
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kinds of supported breakpoints
|
||||
*
|
||||
* <p>
|
||||
* Different debuggers have differing vocabularies of breakpoints, and may only support a subset
|
||||
* of those recognized by Ghidra. This attribute describes those supported.
|
||||
*
|
||||
* @return the set of supported kinds
|
||||
*/
|
||||
@TargetAttributeType(name = SUPPORTED_BREAK_KINDS_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default TargetBreakpointKindSet getSupportedBreakpointKinds() {
|
||||
return getTypedAttributeNowByName(SUPPORTED_BREAK_KINDS_ATTRIBUTE_NAME,
|
||||
TargetBreakpointKindSet.class, TargetBreakpointKindSet.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a breakpoint having the given expression and kinds
|
||||
*
|
||||
* <p>
|
||||
* Certain combinations of kinds and expression may not be reasonable. In those cases, the
|
||||
* debugger may choose to reject, split, and/or adjust the request.
|
||||
*
|
||||
* @param expression the expression, in the native debugger's syntax
|
||||
* @param kinds the desired set of kinds
|
||||
* @return a future which completes when the request is processed
|
||||
*/
|
||||
public CompletableFuture<Void> placeBreakpoint(String expression,
|
||||
Set<TargetBreakpointKind> kinds);
|
||||
|
||||
/**
|
||||
* Specify a breakpoint having the given range and kinds
|
||||
*
|
||||
* <p>
|
||||
* Certain combinations of kinds and range may not be reasonable. In those cases, the debugger
|
||||
* may choose to reject, split, and/or adjust the request.
|
||||
*
|
||||
* @param range the range of addresses for the breakpoint
|
||||
* @param kinds the desired set of kinds
|
||||
* @return a future which completes when the request is processed
|
||||
*/
|
||||
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
|
||||
Set<TargetBreakpointKind> kinds);
|
||||
|
||||
/**
|
||||
* Specify a breakpoint having the given address and kinds
|
||||
*
|
||||
* <p>
|
||||
* Certain combinations of kinds may not be reasonable. In those cases, the debugger may choose
|
||||
* to reject, split, and/or adjust the request.
|
||||
*
|
||||
* @param expression the expression, in the native debugger's syntax
|
||||
* @param kinds the desired set of kinds
|
||||
* @return a future which completes when the request is processed
|
||||
*/
|
||||
public default CompletableFuture<Void> placeBreakpoint(Address address,
|
||||
Set<TargetBreakpointKind> kinds) {
|
||||
return placeBreakpoint(new AddressRangeImpl(address, address), kinds);
|
||||
|
|
|
@ -18,8 +18,16 @@ package ghidra.dbg.target;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetObjectRefList;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
/**
|
||||
* The location of a breakpoint
|
||||
*
|
||||
* <p>
|
||||
* If the native debugger does not separate the concepts of specification and location, then
|
||||
* breakpoint objects should implement both the specification and location interfaces.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("BreakpointLocation")
|
||||
public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
|
||||
extends TypedTargetObject<T> {
|
||||
|
@ -39,26 +47,43 @@ public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
|
|||
String LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length";
|
||||
String SPEC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "spec";
|
||||
|
||||
/**
|
||||
* The minimum address of this location
|
||||
*
|
||||
* @return the address
|
||||
*/
|
||||
@TargetAttributeType(name = ADDRESS_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default Address getAddress() {
|
||||
return getTypedAttributeNowByName(ADDRESS_ATTRIBUTE_NAME, Address.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of object to which this breakpoint applies
|
||||
*
|
||||
* <p>
|
||||
* This list may be empty, in which case, this location is conventionally assumed to apply
|
||||
* everywhere its container's location/scope suggests.
|
||||
*
|
||||
* @return the list of affected objects' references
|
||||
*/
|
||||
@TargetAttributeType(name = AFFECTS_ATTRIBUTE_NAME, hidden = true)
|
||||
public default TargetObjectRefList<?> getAffects() {
|
||||
return getTypedAttributeNowByName(AFFECTS_ATTRIBUTE_NAME, TargetObjectRefList.class,
|
||||
TargetObjectRefList.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* If available, get the length in bytes, of the range covered by the
|
||||
* breakpoint.
|
||||
* If available, get the length in bytes, of the range covered by the breakpoint.
|
||||
*
|
||||
* In most cases, where the length is not available, a length of 1 should be
|
||||
* presumed.
|
||||
* <p>
|
||||
* In most cases, where the length is not available, a length of 1 should be presumed.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Should this be Long?
|
||||
*
|
||||
* @return the length, or {@code null} if not known
|
||||
*/
|
||||
@TargetAttributeType(name = LENGTH_ATTRIBUTE_NAME, hidden = true)
|
||||
public default Integer getLength() {
|
||||
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, null);
|
||||
}
|
||||
|
@ -70,14 +95,13 @@ public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
|
|||
/**
|
||||
* Get a reference to the specification which generated this breakpoint.
|
||||
*
|
||||
* If the debugger does not separate specifications from actual breakpoints,
|
||||
* then the "specification" is this breakpoint. Otherwise, this
|
||||
* specification is the parent. The default implementation distinguishes the
|
||||
* cases by examining the implemented interfaces. Implementors may slightly
|
||||
* increase efficiency by overriding this method.
|
||||
* <p>
|
||||
* If the debugger does not separate specifications from actual breakpoints, then the
|
||||
* "specification" is this breakpoint. Otherwise, the specification is the parent.
|
||||
*
|
||||
* @return the reference to the specification
|
||||
*/
|
||||
@TargetAttributeType(name = SPEC_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default TypedTargetObjectRef<? extends TargetBreakpointSpec<?>> getSpecification() {
|
||||
return getTypedRefAttributeNowByName(SPEC_ATTRIBUTE_NAME, TargetBreakpointSpec.tclass,
|
||||
null);
|
||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
import ghidra.dbg.attributes.TargetObjectRef;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* The specification of a breakpoint applied to a target object
|
||||
|
@ -68,6 +69,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
|
|||
*
|
||||
* @return a reference to the container
|
||||
*/
|
||||
@TargetAttributeType(name = CONTAINER_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default TypedTargetObjectRef<? extends TargetBreakpointContainer<?>> getContainer() {
|
||||
return getTypedRefAttributeNowByName(CONTAINER_ATTRIBUTE_NAME,
|
||||
TargetBreakpointContainer.tclass, null);
|
||||
|
@ -82,6 +84,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
|
|||
*
|
||||
* @return the expression
|
||||
*/
|
||||
@TargetAttributeType(name = EXPRESSION_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default String getExpression() {
|
||||
return getTypedAttributeNowByName(EXPRESSION_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
@ -91,6 +94,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
|
|||
*
|
||||
* @return the kinds
|
||||
*/
|
||||
@TargetAttributeType(name = KINDS_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default TargetBreakpointKindSet getKinds() {
|
||||
return getTypedAttributeNowByName(KINDS_ATTRIBUTE_NAME, TargetBreakpointKindSet.class,
|
||||
TargetBreakpointKindSet.EMPTY);
|
||||
|
@ -101,6 +105,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
|
|||
*
|
||||
* @return true if enabled, false otherwise
|
||||
*/
|
||||
@TargetAttributeType(name = ENABLED_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default boolean isEnabled() {
|
||||
return getTypedAttributeNowByName(ENABLED_ATTRIBUTE_NAME, Boolean.class, false);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,23 @@ import java.nio.charset.Charset;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.lifecycle.Experimental;
|
||||
|
||||
/**
|
||||
* A user-facing console
|
||||
*
|
||||
* <p>
|
||||
* This could be a CLI for the native debugger, or I/O for a target, or anything else the model
|
||||
* might like to expose in terminal-like fashion.
|
||||
*
|
||||
* <p>
|
||||
* This is still an experimental concept and has not been implemented in any model. While it seems
|
||||
* like an abstract case of {@link TargetInterpreter}, their specifications don't seem to line up.
|
||||
* E.g., implementing the CLI as a {@link TargetConsole} requires the server to buffer and parse
|
||||
* line input; whereas, implementing the CLI as a {@link TargetInterpreter} requires the client to
|
||||
* parse line input.
|
||||
*/
|
||||
@Experimental
|
||||
@DebuggerTargetObjectIface("Console")
|
||||
public interface TargetConsole<T extends TargetConsole<T>> extends TypedTargetObject<T> {
|
||||
Charset CHARSET = Charset.forName("utf-8");
|
||||
|
|
|
@ -17,7 +17,20 @@ package ghidra.dbg.target;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetDataType;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||
|
||||
/**
|
||||
* A member of another data type
|
||||
*
|
||||
* <p>
|
||||
* This is usually the child of a {@link TargetNamedDataType}, and its role in determined
|
||||
* conventionally by the actual type of the parent, and of this member's key.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Document the conventions. Most, if not all, are implemented in
|
||||
* {@link TargetDataTypeConverter}.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("TypeMember")
|
||||
public interface TargetDataTypeMember<T extends TargetDataTypeMember<T>>
|
||||
extends TypedTargetObject<T> {
|
||||
|
@ -38,20 +51,47 @@ public interface TargetDataTypeMember<T extends TargetDataTypeMember<T>>
|
|||
/**
|
||||
* The position of the member in the composite
|
||||
*
|
||||
* <p>
|
||||
* A position of -1 implies the parent is not a composite or this member's role is special,
|
||||
* e.g., the return type of a function.
|
||||
*
|
||||
* @return the position
|
||||
*/
|
||||
@TargetAttributeType(name = POSITION_ATTRIBUTE_NAME, hidden = true)
|
||||
default int getPosition() {
|
||||
return getTypedAttributeNowByName(POSITION_ATTRIBUTE_NAME, Integer.class, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the member in the composite
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@TargetAttributeType(name = MEMBER_NAME_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
default String getMemberName() {
|
||||
return getTypedAttributeNowByName(MEMBER_NAME_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* The offset of the member in the composite
|
||||
*
|
||||
* <p>
|
||||
* For structs, this should be the offset in bytes from the base of the struct. For unions, this
|
||||
* should likely be 0. For others, this should be absent or -1.
|
||||
*
|
||||
* @return the offset
|
||||
*/
|
||||
@TargetAttributeType(name = OFFSET_ATTRIBUTE_NAME, hidden = true)
|
||||
default long getOffset() {
|
||||
return getTypedAttributeNowByName(OFFSET_ATTRIBUTE_NAME, Long.class, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of this member
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
@TargetAttributeType(name = DATA_TYPE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
default TargetDataType getDataType() {
|
||||
return getTypedAttributeNowByName(DATA_TYPE_ATTRIBUTE_NAME, TargetDataType.class, null);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
/**
|
||||
* A container of data types
|
||||
*
|
||||
* The debugger should present these in as granular of unit as possible. Consider a desktop
|
||||
* <p>
|
||||
* The debugger should present these in as granular of units as possible. Consider a desktop
|
||||
* application, for example. The debugger should present each module as a namespace rather than the
|
||||
* entire target (or worse, the entire session) as a single namespace.
|
||||
*/
|
||||
|
@ -43,6 +44,7 @@ public interface TargetDataTypeNamespace<T extends TargetDataTypeNamespace<T>>
|
|||
/**
|
||||
* Get the types in this namespace
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for types to be immediate children of the namespace, that is not
|
||||
* necessarily the case.
|
||||
*
|
||||
|
|
|
@ -19,6 +19,9 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
|
||||
/**
|
||||
* An object which can be removed by the user
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Deletable")
|
||||
public interface TargetDeletable<T extends TargetDeletable<T>> extends TypedTargetObject<T> {
|
||||
enum Private {
|
||||
|
@ -30,5 +33,10 @@ public interface TargetDeletable<T extends TargetDeletable<T>> extends TypedTarg
|
|||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
Class<Private.Cls> tclass = (Class) TargetDeletable.class;
|
||||
|
||||
/**
|
||||
* Remove the object
|
||||
*
|
||||
* @return a future which completes when the request is processed
|
||||
*/
|
||||
public CompletableFuture<Void> delete();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
|
||||
/**
|
||||
* A target which can be detached from the debugger
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Detachable")
|
||||
public interface TargetDetachable<T extends TargetDetachable<T>> extends TypedTargetObject<T> {
|
||||
enum Private {
|
||||
|
@ -33,7 +36,7 @@ public interface TargetDetachable<T extends TargetDetachable<T>> extends TypedTa
|
|||
/**
|
||||
* Detach from this target
|
||||
*
|
||||
* @return a future which completes upon successfully detaching
|
||||
* @return a future which completes when the request is processed
|
||||
*/
|
||||
public CompletableFuture<Void> detach();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.dbg.target;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* Provides information about a given target object
|
||||
|
@ -65,8 +66,9 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
|
|||
*
|
||||
* @return the target architecture
|
||||
*/
|
||||
@TargetAttributeType(name = VISIBLE_ARCH_ATTRIBUTE_NAME)
|
||||
default String getArchitecture() {
|
||||
return getTypedAttributeNowByName(ARCH_ATTRIBUTE_NAME, String.class, "");
|
||||
return getTypedAttributeNowByName(VISIBLE_ARCH_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,6 +84,7 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
|
|||
*
|
||||
* @return the host debugger
|
||||
*/
|
||||
@TargetAttributeType(name = DEBUGGER_ATTRIBUTE_NAME, hidden = true)
|
||||
default String getDebugger() {
|
||||
return getTypedAttributeNowByName(DEBUGGER_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
@ -98,8 +101,9 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
|
|||
*
|
||||
* @return the target operating system
|
||||
*/
|
||||
@TargetAttributeType(name = VISIBLE_OS_ATTRIBUTE_NAME)
|
||||
default String getOperatingSystem() {
|
||||
return getTypedAttributeNowByName(OS_ATTRIBUTE_NAME, String.class, "");
|
||||
return getTypedAttributeNowByName(VISIBLE_OS_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,8 +116,9 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
|
|||
*
|
||||
* @return the target endianness
|
||||
*/
|
||||
@TargetAttributeType(name = VISIBLE_ENDIAN_ATTRIBUTE_NAME)
|
||||
default String getEndian() {
|
||||
return getTypedAttributeNowByName(ENDIAN_ATTRIBUTE_NAME, String.class, "");
|
||||
return getTypedAttributeNowByName(VISIBLE_ENDIAN_ATTRIBUTE_NAME, String.class, "");
|
||||
}
|
||||
|
||||
// TODO: Devices? File System?
|
||||
|
|
|
@ -19,9 +19,10 @@ import java.util.List;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* The object can emit events affecting itself and its successors
|
||||
* An object that can emit events affecting itself and its successors
|
||||
*
|
||||
* <p>
|
||||
* Most often, this interface is supported by the (root) session.
|
||||
|
@ -120,6 +121,36 @@ public interface TargetEventScope<T extends TargetEventScope<T>> extends TypedTa
|
|||
SIGNAL,
|
||||
}
|
||||
|
||||
/**
|
||||
* If applicable, get the process producing the last reported event
|
||||
*
|
||||
* <p>
|
||||
* TODO: This is currently the hexadecimal PID. It should really be a ref to the process object.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Since the event thread will be a successor of the event process, this may not be
|
||||
* needed, but perhaps keep it for convenience.
|
||||
*
|
||||
* @return the process or reference
|
||||
*/
|
||||
@TargetAttributeType(name = EVENT_PROCESS_ATTRIBUTE_NAME, hidden = true)
|
||||
public default /*TODO: TypedTargetObjectRef<? extends TargetProcess<?>>*/ String getEventProcess() {
|
||||
return getTypedAttributeNowByName(EVENT_PROCESS_ATTRIBUTE_NAME, String.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* If applicable, get the thread producing the last reported event
|
||||
*
|
||||
* <p>
|
||||
* TODO: This is currently the hexadecimal TID. It should really be a ref to the thread object.
|
||||
*
|
||||
* @return the thread or reference
|
||||
*/
|
||||
@TargetAttributeType(name = EVENT_THREAD_ATTRIBUTE_NAME, hidden = true)
|
||||
public default /*TODO: TypedTargetObjectRef<? extends TargetThread<?>>*/ String getEventThread() {
|
||||
return getTypedAttributeNowByName(EVENT_THREAD_ATTRIBUTE_NAME, String.class, null);
|
||||
}
|
||||
|
||||
public interface TargetEventScopeListener extends TargetObjectListener {
|
||||
/**
|
||||
* An event affecting a target in this scope has occurred
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
package ghidra.dbg.target;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* An object which has an execution life cycle
|
||||
*/
|
||||
@DebuggerTargetObjectIface("ExecutionStateful")
|
||||
public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
|
||||
extends TypedTargetObject<T> {
|
||||
|
@ -101,6 +105,7 @@ public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
|
|||
/**
|
||||
* The object is alive and executing
|
||||
*
|
||||
* <p>
|
||||
* "Running" is loosely defined. For example, with respect to a thread, it may indicate the
|
||||
* thread is currently executing, waiting on an event, or scheduled for execution. It does
|
||||
* not necessarily mean it is executing on a CPU at this exact moment.
|
||||
|
@ -125,6 +130,7 @@ public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
|
|||
/**
|
||||
* The object is no longer alive
|
||||
*
|
||||
* <p>
|
||||
* The object still exists but no longer represents something alive. This could be used for
|
||||
* stale handles to objects which may still be queried (e.g., for a process exit code), or
|
||||
* e.g., a GDB "Inferior" which could be re-used to launch or attach to another process.
|
||||
|
@ -146,19 +152,46 @@ public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this state implies the object is alive
|
||||
*
|
||||
* @return true if alive
|
||||
*/
|
||||
public abstract boolean isAlive();
|
||||
|
||||
/**
|
||||
* Check if this state implies the object is running
|
||||
*
|
||||
* @return true if running
|
||||
*/
|
||||
public abstract boolean isRunning();
|
||||
|
||||
/**
|
||||
* Check if this state implies the object is stopped
|
||||
*
|
||||
* @return true if stopped
|
||||
*/
|
||||
public abstract boolean isStopped();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current execution state of this object
|
||||
*
|
||||
* @return the state
|
||||
*/
|
||||
@TargetAttributeType(name = STATE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default TargetExecutionState getExecutionState() {
|
||||
return getTypedAttributeNowByName(STATE_ATTRIBUTE_NAME, TargetExecutionState.class,
|
||||
TargetExecutionState.STOPPED);
|
||||
}
|
||||
|
||||
public interface TargetExecutionStateListener extends TargetObjectListener {
|
||||
/**
|
||||
* The object has entered a different execution state
|
||||
*
|
||||
* @param object the object
|
||||
* @param state the new state
|
||||
*/
|
||||
default void executionStateChanged(TargetExecutionStateful<?> object,
|
||||
TargetExecutionState state) {
|
||||
}
|
||||
|
|
|
@ -20,7 +20,16 @@ import java.util.concurrent.CompletableFuture;
|
|||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* An object having a designated "focus"
|
||||
*
|
||||
* <p>
|
||||
* Focus is usually communicated via various UI hints, but also semantically implies that actions
|
||||
* taken within this scope apply to the focused object. The least confusing option is to implement
|
||||
* this at the root, but that need not always be the case.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("FocusScope")
|
||||
public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTargetObject<T> {
|
||||
enum Private {
|
||||
|
@ -37,9 +46,10 @@ public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTa
|
|||
/**
|
||||
* Focus on the given object
|
||||
*
|
||||
* -obj- must be successor of this scope. The debugger may reject or ignore the request for any
|
||||
* reason. If the debugger cannot focus the given object, it should attempt to do so for each
|
||||
* ancestor until it succeeds or reaches this focus scope.
|
||||
* <p>
|
||||
* {@code obj} must be successor of this scope. The debugger may reject or ignore the request
|
||||
* for any reason. If the debugger cannot focus the given object, it should attempt to do so for
|
||||
* each ancestor until it succeeds or reaches this focus scope.
|
||||
*
|
||||
* @param obj the object to receive focus
|
||||
* @return a future which completes upon successfully changing focus.
|
||||
|
@ -51,6 +61,7 @@ public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTa
|
|||
*
|
||||
* @return a reference to the focused object or {@code null} if no object is focused.
|
||||
*/
|
||||
@TargetAttributeType(name = FOCUS_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
default TargetObjectRef getFocus() {
|
||||
return getTypedAttributeNowByName(FOCUS_ATTRIBUTE_NAME, TargetObjectRef.class, null);
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ import java.util.concurrent.CompletableFuture;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.TargetConsole.Channel;
|
||||
import ghidra.dbg.target.TargetConsole.TargetTextConsoleListener;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* A command interpreter, usually that of an external debugger
|
||||
* A command interpreter, usually that of a native debugger
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Interpreter")
|
||||
public interface TargetInterpreter<T extends TargetInterpreter<T>> extends TypedTargetObject<T> {
|
||||
|
@ -76,6 +77,7 @@ public interface TargetInterpreter<T extends TargetInterpreter<T>> extends Typed
|
|||
*
|
||||
* @return the current prompt
|
||||
*/
|
||||
@TargetAttributeType(name = PROMPT_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default String getPrompt() {
|
||||
return getTypedAttributeNowByName(PROMPT_ATTRIBUTE_NAME, String.class, ">");
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ import java.util.concurrent.CompletableFuture;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
|
||||
/**
|
||||
* A target that can be interrupted
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Interruptible")
|
||||
public interface TargetInterruptible<T extends TargetInterruptible<T>>
|
||||
extends TypedTargetObject<T> {
|
||||
|
@ -35,6 +38,7 @@ public interface TargetInterruptible<T extends TargetInterruptible<T>>
|
|||
/**
|
||||
* Interrupt the target object
|
||||
*
|
||||
* <p>
|
||||
* Typically, this breaks, i.e., stops, all target objects in scope of the receiver. Note the
|
||||
* command completes when the interrupt has been sent, whether or not it actually stopped
|
||||
* anything. Users wishing to confirm execution has stopped should wait for the target object to
|
||||
|
|
|
@ -19,6 +19,9 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
|
||||
/**
|
||||
* A target which can be killed (terminated)
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Killable")
|
||||
public interface TargetKillable<T extends TargetKillable<T>> extends TypedTargetObject<T> {
|
||||
enum Private {
|
||||
|
|
|
@ -22,9 +22,10 @@ import java.util.concurrent.CompletableFuture;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* An interface which indicates this object is capable of launching targets.
|
||||
* An interface which indicates this object is capable of launching targets
|
||||
*
|
||||
* <p>
|
||||
* The targets this launcher creates ought to appear in its successors.
|
||||
|
@ -130,6 +131,7 @@ public interface TargetLauncher<T extends TargetLauncher<T>> extends TypedTarget
|
|||
}
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = TargetMethod.PARAMETERS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default public TargetParameterMap getParameters() {
|
||||
return TargetMethod.getParameters(this);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.dbg.target;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
|
||||
@DebuggerTargetObjectIface("MemoryRegion")
|
||||
|
@ -36,18 +37,44 @@ public interface TargetMemoryRegion<T extends TargetMemoryRegion<T>> extends Typ
|
|||
String EXECUTABLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "executable";
|
||||
String MEMORY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "memory";
|
||||
|
||||
/**
|
||||
* Get the address range representing this region
|
||||
*
|
||||
* @return the range
|
||||
*/
|
||||
@TargetAttributeType(name = RANGE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default AddressRange getRange() {
|
||||
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this region is readable
|
||||
*
|
||||
* @return true if read is permitted
|
||||
*/
|
||||
@TargetAttributeType(name = READABLE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default boolean isReadable() {
|
||||
return getTypedAttributeNowByName(READABLE_ATTRIBUTE_NAME, Boolean.class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this region is writable
|
||||
*
|
||||
* @return true if write is permitted
|
||||
*/
|
||||
@TargetAttributeType(name = WRITABLE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
|
||||
public default boolean isWritable() {
|
||||
return getTypedAttributeNowByName(WRITABLE_ATTRIBUTE_NAME, Boolean.class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this region is executable
|
||||
*
|
||||
* @return true if execute is permitted
|
||||
*/
|
||||
@TargetAttributeType(name = EXECUTABLE_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
|
||||
public default boolean isExecutable() {
|
||||
return getTypedAttributeNowByName(EXECUTABLE_ATTRIBUTE_NAME, Boolean.class, false);
|
||||
}
|
||||
|
@ -66,6 +93,7 @@ public interface TargetMemoryRegion<T extends TargetMemoryRegion<T>> extends Typ
|
|||
*
|
||||
* @return a reference to the memory
|
||||
*/
|
||||
@TargetAttributeType(name = MEMORY_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
public default TypedTargetObjectRef<? extends TargetMemory<?>> getMemory() {
|
||||
return getTypedRefAttributeNowByName(MEMORY_ATTRIBUTE_NAME, TargetMemory.tclass, null);
|
||||
}
|
||||
|
|
|
@ -22,12 +22,14 @@ import java.util.stream.Stream;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.error.DebuggerIllegalArgumentException;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.CollectionUtils.AbstractEmptyMap;
|
||||
import ghidra.dbg.util.CollectionUtils.AbstractNMap;
|
||||
|
||||
/**
|
||||
* A marker interface which indicates a method on an object
|
||||
* An object which can be invoked as a method
|
||||
*
|
||||
* TODO: Should parameters and return type be something incorporated into Schemas?
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Method")
|
||||
public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObject<T> {
|
||||
|
@ -47,19 +49,42 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
|
|||
* A description of a method parameter
|
||||
*
|
||||
* <p>
|
||||
* TODO: Most of this should probably eventually go into {@link TargetMethod}
|
||||
* <p>
|
||||
* TODO: For convenience, these should be programmable via annotations.
|
||||
* <P>
|
||||
* TODO: Should this be incorporated into schemas?
|
||||
*
|
||||
* @param <T> the type of the parameter
|
||||
*/
|
||||
class ParameterDescription<T> {
|
||||
/**
|
||||
* Create a parameter
|
||||
*
|
||||
* @param <T> the type of the parameter
|
||||
* @param type the class representing the type of the parameter
|
||||
* @param name the name of the parameter
|
||||
* @param required true if this parameter must be provided
|
||||
* @param defaultValue the default value of this parameter
|
||||
* @param display the human-readable name of this parameter
|
||||
* @param description the human-readable description of this parameter
|
||||
* @return the new parameter description
|
||||
*/
|
||||
public static <T> ParameterDescription<T> create(Class<T> type, String name,
|
||||
boolean required, T defaultValue, String display, String description) {
|
||||
return new ParameterDescription<>(type, name, required, defaultValue, display,
|
||||
description, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a parameter having enumerated choices
|
||||
*
|
||||
* @param <T> the type of the parameter
|
||||
* @param type the class representing the type of the parameter
|
||||
* @param name the name of the parameter
|
||||
* @param choices the non-empty set of choices
|
||||
* @param display the human-readable name of this parameter
|
||||
* @param description the human-readable description of this parameter
|
||||
* @return the new parameter description
|
||||
*/
|
||||
public static <T> ParameterDescription<T> choices(Class<T> type, String name,
|
||||
Collection<T> choices, String display, String description) {
|
||||
T defaultValue = choices.iterator().next();
|
||||
|
@ -266,6 +291,13 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
|
|||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method used by {@link TargetLauncher} as a stopgap until "launch" becomes a
|
||||
* {@link TargetMethod}.
|
||||
*
|
||||
* @param obj the object having a "parameters" attribute.
|
||||
* @return the parameter map
|
||||
*/
|
||||
static TargetParameterMap getParameters(TargetObject obj) {
|
||||
return obj.getTypedAttributeNowByName(PARAMETERS_ATTRIBUTE_NAME,
|
||||
TargetParameterMap.class, TargetParameterMap.of());
|
||||
|
@ -274,11 +306,9 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
|
|||
/**
|
||||
* Get the parameter descriptions of this method
|
||||
*
|
||||
* <p>
|
||||
* TODO: This attribute needs better type checking. Probably delay for schemas.
|
||||
*
|
||||
* @return the name-description map of parameters
|
||||
*/
|
||||
@TargetAttributeType(name = PARAMETERS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default public TargetParameterMap getParameters() {
|
||||
return getParameters(this);
|
||||
}
|
||||
|
@ -293,22 +323,13 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
|
|||
*
|
||||
* @return the return type
|
||||
*/
|
||||
@TargetAttributeType(name = RETURN_TYPE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default public Class<?> getReturnType() {
|
||||
return getTypedAttributeNowByName(RETURN_TYPE_ATTRIBUTE_NAME, Class.class,
|
||||
Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if extra parameters are allowed
|
||||
*
|
||||
* <p>
|
||||
* If not allowed, any named parameter not in the descriptions is considered an error.
|
||||
*
|
||||
* @return true if extras allowed, false if not
|
||||
*/
|
||||
default public boolean allowsExtra() {
|
||||
return false;
|
||||
}
|
||||
// TODO: Allow extra parameters, i.e., varargs?
|
||||
|
||||
/**
|
||||
* Invoke the method with the given arguments
|
||||
|
|
|
@ -17,10 +17,15 @@ package ghidra.dbg.target;
|
|||
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
|
||||
/**
|
||||
* A binary module loaded by the debugger
|
||||
* A binary module loaded by the target and/or debugger
|
||||
*
|
||||
* <p>
|
||||
* If the debugger cares to parse the modules for section information, those sections should be
|
||||
* presented as successors to the module.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Module")
|
||||
public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObject<T> {
|
||||
|
@ -49,8 +54,9 @@ public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObje
|
|||
*
|
||||
* @return the base address, or {@code null}
|
||||
*/
|
||||
@TargetAttributeType(name = VISIBLE_RANGE_ATTRIBUTE_NAME, required = true)
|
||||
public default AddressRange getRange() {
|
||||
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
||||
return getTypedAttributeNowByName(VISIBLE_RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,6 +64,7 @@ public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObje
|
|||
*
|
||||
* @return the module name
|
||||
*/
|
||||
@TargetAttributeType(name = MODULE_NAME_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default String getModuleName() {
|
||||
return getTypedAttributeNowByName(MODULE_NAME_ATTRIBUTE_NAME, String.class, null);
|
||||
}
|
||||
|
|
|
@ -18,17 +18,20 @@ package ghidra.dbg.target;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.lifecycle.Experimental;
|
||||
|
||||
/**
|
||||
* A place for modules to reside
|
||||
*
|
||||
* <p>
|
||||
* Also a hint interface which helps the user of the client locate modules which apply to a given
|
||||
* target object
|
||||
*
|
||||
* <p>
|
||||
* TODO: Experiment with the idea of "synthetic modules" as presented by {@code dbgeng.dll}. Is
|
||||
* there a similar idea in GDB? This could allow us to expose Ghidra's symbol table to the connected
|
||||
* debugger.
|
||||
* there a similar idea in GDB? This could allow us to expose Ghidra's symbol table and types to the
|
||||
* native debugger.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("ModuleContainer")
|
||||
public interface TargetModuleContainer<T extends TargetModuleContainer<T>>
|
||||
|
@ -45,6 +48,7 @@ public interface TargetModuleContainer<T extends TargetModuleContainer<T>>
|
|||
String SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME =
|
||||
PREFIX_INVISIBLE + "supports_synthetic_modules";
|
||||
|
||||
@TargetAttributeType(name = SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME, fixed = true, hidden = true)
|
||||
@Experimental
|
||||
public default boolean supportsSyntheticModules() {
|
||||
return getTypedAttributeNowByName(SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME, Boolean.class,
|
||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.dbg.DebugModelConventions;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetNamedDataTypeRef;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||
|
||||
/**
|
||||
|
@ -37,8 +38,10 @@ import ghidra.dbg.util.TargetDataTypeConverter;
|
|||
* <li>{@code union}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Other types, e.g., pointers, arrays, are modeled as attributes.
|
||||
*
|
||||
* <p>
|
||||
* See {@link TargetDataTypeConverter} to get a grasp of the conventions
|
||||
*
|
||||
* @param <T> the type of this object
|
||||
|
@ -61,7 +64,7 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
|
|||
ENUM, FUNCTION, STRUCT, TYPEDEF, UNION;
|
||||
}
|
||||
|
||||
String NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kind";
|
||||
String NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "type_kind";
|
||||
String ENUM_BYTE_LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "byte_length";
|
||||
String NAMESPACE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "namespace";
|
||||
|
||||
|
@ -71,6 +74,7 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
|
|||
/**
|
||||
* Get the members of this data type in order.
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for members to be immediate children of the type, that is not
|
||||
* necessarily the case.
|
||||
*
|
||||
|
@ -85,12 +89,14 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
|
|||
/**
|
||||
* Get the namespace for this data type.
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for a data type to be an immediate child of its namespace, that is
|
||||
* not necessarily the case. This method is a reliable and type-safe means of obtaining that
|
||||
* namespace.
|
||||
*
|
||||
* @return a reference to the namespace
|
||||
*/
|
||||
@TargetAttributeType(name = NAMESPACE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default TypedTargetObjectRef<? extends TargetDataTypeNamespace<?>> getNamespace() {
|
||||
return getTypedRefAttributeNowByName(NAMESPACE_ATTRIBUTE_NAME,
|
||||
TargetDataTypeNamespace.tclass, null);
|
||||
|
@ -101,8 +107,14 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
|
|||
*
|
||||
* @return the kind
|
||||
*/
|
||||
default NamedDataTypeKind getKind() {
|
||||
@TargetAttributeType(name = NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default NamedDataTypeKind getTypeKind() {
|
||||
return getTypedAttributeNowByName(NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME,
|
||||
NamedDataTypeKind.class, null);
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = ENUM_BYTE_LENGTH_ATTRIBUTE_NAME, fixed = true, hidden = true)
|
||||
default Integer getEnumByteLength() {
|
||||
return getTypedAttributeNowByName(ENUM_BYTE_LENGTH_ATTRIBUTE_NAME, Integer.class, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.dbg.target;
|
|||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
@ -25,9 +26,12 @@ import ghidra.dbg.attributes.TargetObjectRef;
|
|||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.error.DebuggerModelTypeException;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||
import ghidra.dbg.util.ValueUtils;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
|
@ -157,15 +161,79 @@ import ghidra.util.Msg;
|
|||
*/
|
||||
public interface TargetObject extends TargetObjectRef {
|
||||
|
||||
Set<Class<? extends TargetObject>> ALL_INTERFACES = Set.of(
|
||||
TargetAccessConditioned.class,
|
||||
TargetAggregate.class,
|
||||
TargetAttachable.class,
|
||||
TargetAttacher.class,
|
||||
TargetBreakpointContainer.class,
|
||||
TargetBreakpointSpec.class,
|
||||
TargetDataTypeMember.class,
|
||||
TargetDataTypeNamespace.class,
|
||||
TargetDeletable.class,
|
||||
TargetDetachable.class,
|
||||
TargetBreakpointLocation.class,
|
||||
TargetEnvironment.class,
|
||||
TargetEventScope.class,
|
||||
TargetExecutionStateful.class,
|
||||
TargetFocusScope.class,
|
||||
TargetInterpreter.class,
|
||||
TargetInterruptible.class,
|
||||
TargetKillable.class,
|
||||
TargetLauncher.class,
|
||||
TargetMethod.class,
|
||||
TargetMemory.class,
|
||||
TargetMemoryRegion.class,
|
||||
TargetModule.class,
|
||||
TargetModuleContainer.class,
|
||||
TargetNamedDataType.class,
|
||||
TargetProcess.class,
|
||||
TargetRegister.class,
|
||||
TargetRegisterBank.class,
|
||||
TargetRegisterContainer.class,
|
||||
TargetResumable.class,
|
||||
TargetSection.class,
|
||||
TargetStack.class,
|
||||
TargetStackFrame.class,
|
||||
TargetSteppable.class,
|
||||
TargetSymbol.class,
|
||||
TargetSymbolNamespace.class,
|
||||
TargetThread.class);
|
||||
Map<String, Class<? extends TargetObject>> INTERFACES_BY_NAME = initInterfacesByName();
|
||||
|
||||
/**
|
||||
* Initializer for {@link #INTERFACES_BY_NAME}
|
||||
*
|
||||
* @return interfaces indexed by name
|
||||
*/
|
||||
@Internal
|
||||
static Map<String, Class<? extends TargetObject>> initInterfacesByName() {
|
||||
return ALL_INTERFACES.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(
|
||||
DebuggerObjectModel::requireIfaceName, i -> i));
|
||||
}
|
||||
|
||||
static List<Class<? extends TargetObject>> getInterfacesByName(
|
||||
Collection<String> names) {
|
||||
return names.stream()
|
||||
.filter(INTERFACES_BY_NAME::containsKey)
|
||||
.map(INTERFACES_BY_NAME::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
String PREFIX_INVISIBLE = "_";
|
||||
|
||||
String DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "display";
|
||||
String SHORT_DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "short_display";
|
||||
String KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kind";
|
||||
String UPDATE_MODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "update_mode";
|
||||
String ORDER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "order";
|
||||
|
||||
// TODO: Should these belong to a new TargetValue interface?
|
||||
String MODIFIED_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "modified";
|
||||
String TYPE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "type";
|
||||
String UPDATE_MODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "update_mode";
|
||||
String VALUE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "value";
|
||||
String ORDER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "order";
|
||||
|
||||
enum Protected {
|
||||
;
|
||||
|
@ -270,6 +338,15 @@ public interface TargetObject extends TargetObjectRef {
|
|||
*/
|
||||
public String getTypeHint();
|
||||
|
||||
/**
|
||||
* Get this object's schema.
|
||||
*
|
||||
* @return the schema
|
||||
*/
|
||||
public default TargetObjectSchema getSchema() {
|
||||
return EnumerableTargetObjectSchema.OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interfaces this object actually supports, and that the client recognizes.
|
||||
*
|
||||
|
@ -349,6 +426,7 @@ public interface TargetObject extends TargetObjectRef {
|
|||
*
|
||||
* @return the display description
|
||||
*/
|
||||
@TargetAttributeType(name = DISPLAY_ATTRIBUTE_NAME, hidden = true)
|
||||
public default String getDisplay() {
|
||||
return getTypedAttributeNowByName(DISPLAY_ATTRIBUTE_NAME, String.class, getName());
|
||||
}
|
||||
|
@ -358,10 +436,26 @@ public interface TargetObject extends TargetObjectRef {
|
|||
*
|
||||
* @return the display description
|
||||
*/
|
||||
@TargetAttributeType(name = SHORT_DISPLAY_ATTRIBUTE_NAME, hidden = true)
|
||||
public default String getShortDisplay() {
|
||||
return getTypedAttributeNowByName(SHORT_DISPLAY_ATTRIBUTE_NAME, String.class, getDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hint to the "kind" of object this represents.
|
||||
*
|
||||
* <p>
|
||||
* This is useful when the native debugger presents a comparable tree-like model. If this object
|
||||
* is simply proxying an object from that model, and that model provides additional type
|
||||
* information that would not otherwise be encoded in this model.
|
||||
*
|
||||
* @return the kind of the object
|
||||
*/
|
||||
@TargetAttributeType(name = KIND_ATTRIBUTE_NAME, fixed = true, hidden = true)
|
||||
public default String getKind() {
|
||||
return getTypedAttributeNowByName(KIND_ATTRIBUTE_NAME, String.class, getDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element update mode for this object
|
||||
*
|
||||
|
@ -382,11 +476,77 @@ public interface TargetObject extends TargetObjectRef {
|
|||
*
|
||||
* @return the update mode
|
||||
*/
|
||||
@TargetAttributeType(name = UPDATE_MODE_ATTRIBUTE_NAME, hidden = true)
|
||||
public default TargetUpdateMode getUpdateMode() {
|
||||
return getTypedAttributeNowByName(UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.class,
|
||||
TargetUpdateMode.UNSOLICITED);
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom ordinal for positioning this item on screen
|
||||
*
|
||||
* <p>
|
||||
* Ordinarily, children are ordered by key, attributes followed by elements. The built-in
|
||||
* comparator does a decent job ordering them, so long as indices keep a consistent format among
|
||||
* siblings. In some cases, however, especially with query-style methods, the same objects (and
|
||||
* thus keys) need to be presented with an alternative ordering. This attribute can be used by
|
||||
* model implementations to recommend an alternative ordering, where siblings are instead sorted
|
||||
* according to this ordinal.
|
||||
*
|
||||
* @return the recommended display position for this element
|
||||
*/
|
||||
@TargetAttributeType(name = ORDER_ATTRIBUTE_NAME, hidden = true)
|
||||
public default Integer getOrder() {
|
||||
return getTypedAttributeNowByName(ORDER_ATTRIBUTE_NAME, Integer.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* For values, check if it was "recently" modified
|
||||
*
|
||||
* <p>
|
||||
* TODO: This should probably be moved to a new {@code TargetType} interface.
|
||||
*
|
||||
* <p>
|
||||
* "Recently" generally means since (or as a result of ) the last event affecting a target in
|
||||
* the same scope. This is mostly used as a UI hint, to bring the user's attention to modified
|
||||
* values.
|
||||
*
|
||||
* @return true if modified, false if not
|
||||
*/
|
||||
@TargetAttributeType(name = MODIFIED_ATTRIBUTE_NAME, hidden = true)
|
||||
public default Boolean isModified() {
|
||||
return getTypedAttributeNowByName(MODIFIED_ATTRIBUTE_NAME, Boolean.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* For values, get the type of the value
|
||||
*
|
||||
* <p>
|
||||
* TODO: This should probably be moved to a new {@code TargetType} interface. How does this
|
||||
* differ from "kind" when both are present? Can this be a {@link TargetNamedDataType} instead?
|
||||
* Though, I suppose that would imply the object is a value in the target execution state, e.g.,
|
||||
* a register, variable, field, etc.
|
||||
*
|
||||
* @return the name of the type
|
||||
*/
|
||||
@TargetAttributeType(name = TYPE_ATTRIBUTE_NAME, hidden = true)
|
||||
public default String getType() {
|
||||
return getTypedAttributeNowByName(TYPE_ATTRIBUTE_NAME, String.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* For values, get the actual value
|
||||
*
|
||||
* <p>
|
||||
* TODO: This should probably be moved to a new {@code TargetType} interface.
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
@TargetAttributeType(name = VALUE_ATTRIBUTE_NAME, hidden = true)
|
||||
public default Object getValue() {
|
||||
return getCachedAttribute(VALUE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
default CompletableFuture<? extends TargetObject> fetch() {
|
||||
return CompletableFuture.completedFuture(this);
|
||||
|
@ -452,8 +612,8 @@ public interface TargetObject extends TargetObjectRef {
|
|||
* Cast the named attribute to the given type, if possible
|
||||
*
|
||||
* <p>
|
||||
* If the attribute value is {@code null} or cannot be cast to the given type, an error message
|
||||
* is printed, and the fallback value is returned.
|
||||
* If the attribute value is {@code null} or cannot be cast to the given type, the fallback
|
||||
* value is returned.
|
||||
*
|
||||
* @param <T> the expected type of the attribute
|
||||
* @param name the name of the attribute
|
||||
|
@ -462,8 +622,9 @@ public interface TargetObject extends TargetObjectRef {
|
|||
* @return the value casted to the expected type, or the fallback value
|
||||
*/
|
||||
public default <T> T getTypedAttributeNowByName(String name, Class<T> cls, T fallback) {
|
||||
AttributeSchema as = getSchema().getAttributeSchema(name);
|
||||
Object obj = getCachedAttribute(name);
|
||||
return ValueUtils.expectType(obj, cls, this, name, fallback);
|
||||
return ValueUtils.expectType(obj, cls, this, name, fallback, as.isRequired());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,13 +17,16 @@ package ghidra.dbg.target;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* A marker interface which indicates a process, usually on a host operating system
|
||||
*
|
||||
* <p>
|
||||
* If this object does not support {@link TargetExecutionStateful}, then its mere existence in the
|
||||
* model implies that it is {@link TargetExecutionState#ALIVE}. TODO: Should allow association, but
|
||||
* that may have to wait until schemas are introduced.
|
||||
* model implies that it is {@link TargetExecutionState#ALIVE}. TODO: Should allow association via
|
||||
* convention to a different {@link TargetExecutionStateful}, but that may have to wait until
|
||||
* schemas are introduced.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Process")
|
||||
public interface TargetProcess<T extends TargetProcess<T>> extends TypedTargetObject<T> {
|
||||
|
@ -37,4 +40,9 @@ public interface TargetProcess<T extends TargetProcess<T>> extends TypedTargetOb
|
|||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
Class<Private.Cls> tclass = (Class) TargetProcess.class;
|
||||
|
||||
@TargetAttributeType(name = PID_ATTRIBUTE_NAME, hidden = true)
|
||||
public default Long getPid() {
|
||||
return getTypedAttributeNowByName(PID_ATTRIBUTE_NAME, Long.class, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,13 @@ package ghidra.dbg.target;
|
|||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
/**
|
||||
* This is a description of a register
|
||||
*
|
||||
* <p>
|
||||
* This describes a register abstractly. It does not represent the actual value of a register. For
|
||||
* values, see {@link TargetRegisterBank}. The description and values are separated, since the
|
||||
* descriptions typically apply to the entire platform, and so can be presented just once.
|
||||
|
@ -45,6 +47,7 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
|
|||
/**
|
||||
* Get the container of this register.
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for a register descriptor to be an immediate child of its container,
|
||||
* that is not necessarily the case. In fact, some models may present sub-registers as children
|
||||
* of another register. This method is a reliable and type-safe means of obtaining the
|
||||
|
@ -52,6 +55,7 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
|
|||
*
|
||||
* @return a reference to the container
|
||||
*/
|
||||
@TargetAttributeType(name = CONTAINER_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default TypedTargetObjectRef<? extends TargetRegisterContainer<?>> getContainer() {
|
||||
return getTypedRefAttributeNowByName(CONTAINER_ATTRIBUTE_NAME,
|
||||
TargetRegisterContainer.tclass, null);
|
||||
|
@ -62,6 +66,7 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
|
|||
*
|
||||
* @return the length of the register
|
||||
*/
|
||||
@TargetAttributeType(name = LENGTH_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
default int getBitLength() {
|
||||
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
|
||||
}
|
||||
|
@ -70,11 +75,10 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
|
|||
public default String getIndex() {
|
||||
return PathUtils.isIndex(getPath()) ? PathUtils.getIndex(getPath())
|
||||
: PathUtils.getKey(getPath());
|
||||
//return PathUtils.getIndex(getPath());
|
||||
}
|
||||
|
||||
// TODO: Any typical type assignment or structure definition?
|
||||
// TODO: (Related) Should describe if typically a pointer?
|
||||
|
||||
// TODO: What if the register is memory-mapped?
|
||||
// TODO: What if the register is memory-mapped? Probably map client-side.
|
||||
}
|
||||
|
|
|
@ -23,10 +23,15 @@ import java.util.stream.Collectors;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.error.DebuggerRegisterAccessException;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A bank of registers on the debug target
|
||||
*
|
||||
* <p>
|
||||
* The bank allows access to registers' <em>values</em>; whereas, a {@link TargetRegisterContainer}
|
||||
* allows reflection of the registers' names and structures.
|
||||
*/
|
||||
@DebuggerTargetObjectIface("RegisterBank")
|
||||
public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends TypedTargetObject<T> {
|
||||
|
@ -46,6 +51,7 @@ public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends Typ
|
|||
*
|
||||
* @return a future which completes with object
|
||||
*/
|
||||
@TargetAttributeType(name = DESCRIPTIONS_ATTRIBUTE_NAME)
|
||||
@SuppressWarnings("unchecked")
|
||||
public default TypedTargetObjectRef<? extends TargetRegisterContainer<?>> getDescriptions() {
|
||||
return getTypedRefAttributeNowByName(DESCRIPTIONS_ATTRIBUTE_NAME,
|
||||
|
@ -216,6 +222,6 @@ public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends Typ
|
|||
* @param updates a name-value map of updated registers
|
||||
*/
|
||||
default void registersUpdated(TargetRegisterBank<?> bank, Map<String, byte[]> updates) {
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ public interface TargetRegisterContainer<T extends TargetRegisterContainer<T>>
|
|||
/**
|
||||
* Get the register descriptions in this container
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for registers to be immediate children of the container, that is not
|
||||
* necessarily the case. In fact, some models may present sub-registers as children of another
|
||||
* register. This method must return all registers (including sub-registers, if applicable) in
|
||||
|
|
|
@ -36,6 +36,7 @@ public interface TargetResumable<T extends TargetResumable<T>> extends TypedTarg
|
|||
/**
|
||||
* Resume execution of this object
|
||||
*
|
||||
* <p>
|
||||
* Note, this would be called "continue" if it weren't a Java reserved word :( .
|
||||
*
|
||||
* @return a future which completes upon successful resumption
|
||||
|
|
|
@ -17,17 +17,20 @@ package ghidra.dbg.target;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
|
||||
/**
|
||||
* An allocated section of a binary module
|
||||
*
|
||||
* <p>
|
||||
* Note that the model should only present those sections which are allocated in memory. Otherwise
|
||||
* strange things may happen, such as zero-length ranges (which AddressRange hates), or overlapping
|
||||
* ranges (which Trace hates).
|
||||
*
|
||||
* TODO: Present all sections, but include start, length, isAllocated instead?
|
||||
* <p>
|
||||
* TODO: Present all sections, but include isAllocated?
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Section")
|
||||
public interface TargetSection<T extends TargetSection<T>> extends TypedTargetObject<T> {
|
||||
|
@ -49,6 +52,7 @@ public interface TargetSection<T extends TargetSection<T>> extends TypedTargetOb
|
|||
*
|
||||
* @return the owning module
|
||||
*/
|
||||
@TargetAttributeType(name = MODULE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
@SuppressWarnings("unchecked")
|
||||
public default TypedTargetObjectRef<? extends TargetModule<?>> getModule() {
|
||||
return getTypedRefAttributeNowByName(MODULE_ATTRIBUTE_NAME, TargetModule.class, null);
|
||||
|
@ -62,8 +66,9 @@ public interface TargetSection<T extends TargetSection<T>> extends TypedTargetOb
|
|||
*
|
||||
* @return the range
|
||||
*/
|
||||
@TargetAttributeType(name = VISIBLE_RANGE_ATTRIBUTE_NAME, required = true, fixed = true)
|
||||
public default AddressRange getRange() {
|
||||
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
||||
return getTypedAttributeNowByName(VISIBLE_RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
/**
|
||||
* Represents the execution stack, as unwound into frames by the debugger
|
||||
*
|
||||
* <p>
|
||||
* Conventionally, if the debugger can also unwind register values, then each frame should present a
|
||||
* register bank. Otherwise, the same object presenting this stack should present the register bank.
|
||||
*/
|
||||
|
@ -41,6 +42,7 @@ public interface TargetStack<T extends TargetStack<T>> extends TypedTargetObject
|
|||
/**
|
||||
* Get the frames in this stack
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for frames to be immediate children of the stack, that is not
|
||||
* necessarily the case.
|
||||
*
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
package ghidra.dbg.target;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
/**
|
||||
* One frame of an execution stack
|
||||
* One frame (call record) of an execution stack
|
||||
*/
|
||||
@DebuggerTargetObjectIface("StackFrame")
|
||||
public interface TargetStackFrame<T extends TargetStackFrame<T>> extends TypedTargetObject<T> {
|
||||
|
@ -37,10 +38,12 @@ public interface TargetStackFrame<T extends TargetStackFrame<T>> extends TypedTa
|
|||
/**
|
||||
* Get the program counter for the frame
|
||||
*
|
||||
* <p>
|
||||
* Note for some platforms, this may differ from the value in the program counter register.
|
||||
*
|
||||
* @return a future completing with the address of the executing (or next) instruction.
|
||||
*/
|
||||
@TargetAttributeType(name = PC_ATTRIBUTE_NAME, required = true, hidden = true)
|
||||
public default Address getProgramCounter() {
|
||||
return getTypedAttributeNowByName(PC_ATTRIBUTE_NAME, Address.class, Address.NO_ADDRESS);
|
||||
}
|
||||
|
|
|
@ -22,10 +22,14 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.CollectionUtils;
|
||||
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
|
||||
import ghidra.lifecycle.Experimental;
|
||||
|
||||
/**
|
||||
* A target whose execution can be single stepped
|
||||
*/
|
||||
@DebuggerTargetObjectIface("Steppable")
|
||||
public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTargetObject<T> {
|
||||
enum Private {
|
||||
|
@ -202,6 +206,7 @@ public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTarg
|
|||
*
|
||||
* @return the set of supported multi-step operations
|
||||
*/
|
||||
@TargetAttributeType(name = SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
public default TargetStepKindSet getSupportedStepKinds() {
|
||||
return getTypedAttributeNowByName(SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME,
|
||||
TargetStepKindSet.class, TargetStepKindSet.of());
|
||||
|
|
|
@ -21,6 +21,7 @@ import ghidra.async.TypeSpec;
|
|||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetDataType;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.DataType;
|
||||
|
@ -52,6 +53,7 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
*
|
||||
* @return a future completing with the type
|
||||
*/
|
||||
@TargetAttributeType(name = DATA_TYPE_ATTRIBUTE_NAME, fixed = true, hidden = true)
|
||||
public default TargetDataType getDataType() {
|
||||
return getTypedAttributeNowByName(DATA_TYPE_ATTRIBUTE_NAME, TargetDataType.class,
|
||||
TargetDataType.UNDEFINED1);
|
||||
|
@ -70,8 +72,9 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
/**
|
||||
* Get the type of this symbol converted to a Ghidra data type
|
||||
*
|
||||
* Each call to this variant creates a new {@link TargetDataTypeConverter}, and so does not take
|
||||
* full advantage of its internal cache.
|
||||
* <p>
|
||||
* WARNING: Each call to this variant creates a new {@link TargetDataTypeConverter}, and so does
|
||||
* not take full advantage of its internal cache.
|
||||
*
|
||||
* @see #getGhidraDataType(TargetDataTypeConverter)
|
||||
*/
|
||||
|
@ -83,6 +86,7 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
* Get the type of this symbol converted to a Ghidra data type, without using a
|
||||
* {@link DataTypeManager}
|
||||
*
|
||||
* <p>
|
||||
* It is better to use variants with a {@link DataTypeManager} directly, rather than using no
|
||||
* manager and cloning to one later. The former will select types suited to the data
|
||||
* organization of the destination manager. Using no manager and cloning later will use
|
||||
|
@ -97,6 +101,7 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
/**
|
||||
* Determine whether the symbol has a constant value
|
||||
*
|
||||
* <p>
|
||||
* Constant symbols include but are not limited to C enumeration constants. Otherwise, the
|
||||
* symbol's value refers to an address, which stores a presumably non-constant value.
|
||||
*
|
||||
|
@ -109,11 +114,14 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
/**
|
||||
* Get the value of the symbol
|
||||
*
|
||||
* <p>
|
||||
* If the symbol is a constant, then the returned address will be in the constant space.
|
||||
*
|
||||
* @return the address or constant value of the symbol, or {@link Address#NO_ADDRESS} if
|
||||
* unspecified
|
||||
*/
|
||||
@Override
|
||||
// NB. TargetObject defines this attribute
|
||||
public default Address getValue() {
|
||||
return getTypedAttributeNowByName(VALUE_ATTRIBUTE_NAME, Address.class, Address.NO_ADDRESS);
|
||||
}
|
||||
|
@ -121,11 +129,13 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
/**
|
||||
* If known, get the size of the symbol in bytes
|
||||
*
|
||||
* <p>
|
||||
* The size of a symbol is usually not required at runtime, so a user should be grateful if this
|
||||
* is known. If it is not known, or the symbol does not have a size, this method returns 0.
|
||||
*
|
||||
* @return the size of the symbol, or 0 if unspecified
|
||||
*/
|
||||
@TargetAttributeType(name = SIZE_ATTRIBUTE_NAME, fixed = true, hidden = true)
|
||||
public default long getSize() {
|
||||
return getTypedAttributeNowByName(SIZE_ATTRIBUTE_NAME, Long.class, 0L);
|
||||
}
|
||||
|
@ -133,12 +143,14 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
|
|||
/**
|
||||
* Get the namespace for this symbol.
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for a symbol to be an immediate child of its namespace, that is not
|
||||
* necessarily the case. This method is a reliable and type-safe means of obtaining that
|
||||
* namespace.
|
||||
*
|
||||
* @return a reference to the namespace
|
||||
*/
|
||||
@TargetAttributeType(name = NAMESPACE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
|
||||
public default TypedTargetObjectRef<? extends TargetSymbolNamespace<?>> getNamespace() {
|
||||
return getTypedRefAttributeNowByName(NAMESPACE_ATTRIBUTE_NAME, TargetSymbolNamespace.tclass,
|
||||
null);
|
||||
|
|
|
@ -24,7 +24,8 @@ import ghidra.dbg.DebuggerTargetObjectIface;
|
|||
/**
|
||||
* A container of symbols
|
||||
*
|
||||
* The debugger should present these in as granular of unit as possible. Consider a desktop
|
||||
* <p>
|
||||
* The debugger should present these in as granular of units as possible. Consider a desktop
|
||||
* application, for example. The debugger should present each module as a namespace rather than the
|
||||
* entire target (or worse, the entire session) as a single namespace.
|
||||
*/
|
||||
|
@ -43,6 +44,7 @@ public interface TargetSymbolNamespace<T extends TargetSymbolNamespace<T>>
|
|||
/**
|
||||
* Get the symbols in this namespace.
|
||||
*
|
||||
* <p>
|
||||
* While it is most common for symbols to be immediate children of the namespace, that is not
|
||||
* necessarily the case.
|
||||
*
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
package ghidra.dbg.target;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
|
||||
/**
|
||||
* A marker interface which indicates a thread, usually within a process
|
||||
*
|
||||
* <p>
|
||||
* This object must be associated with a suitable {@link TargetExecutionStateful}. In most cases,
|
||||
* the object should just implement it.
|
||||
*/
|
||||
|
@ -35,4 +37,9 @@ public interface TargetThread<T extends TargetThread<T>> extends TypedTargetObje
|
|||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
Class<Private.Cls> tclass = (Class) TargetThread.class;
|
||||
|
||||
@TargetAttributeType(name = TID_ATTRIBUTE_NAME, hidden = true)
|
||||
public default Integer getTid() {
|
||||
return getTypedAttributeNowByName(TID_ATTRIBUTE_NAME, Integer.class, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
|
||||
/**
|
||||
* A common interface for all {@link TargetObject} interfaces, as well as implementations of
|
||||
* {@link TargetObject}, so that {@link #fetch()} simply returns this object.
|
||||
*
|
||||
* @param <T> the type of this object. It should be a type variable extending this object's type if
|
||||
* the class is meant to be further extended.
|
||||
*/
|
||||
public interface TypedTargetObject<T extends TypedTargetObject<T>>
|
||||
extends TargetObject, TypedTargetObjectRef<T> {
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.reflect.TypeUtils;
|
||||
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetObjectRef;
|
||||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
public class AnnotatedSchemaContext extends DefaultSchemaContext {
|
||||
|
||||
static <T> Stream<Class<? extends T>> filterBounds(Class<T> base, Stream<Class<?>> bounds) {
|
||||
return bounds.filter(base::isAssignableFrom).map(c -> c.asSubclass(base));
|
||||
}
|
||||
|
||||
static Stream<Class<?>> resolveUpperBounds(Class<? extends TargetObjectRef> cls, Type type) {
|
||||
if (type == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
if (type instanceof Class<?>) {
|
||||
return Stream.of((Class<?>) type);
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
return resolveUpperBounds(cls, pt.getRawType());
|
||||
}
|
||||
if (type instanceof WildcardType) {
|
||||
WildcardType wt = (WildcardType) type;
|
||||
return Stream.of(TypeUtils.getImplicitUpperBounds(wt))
|
||||
.flatMap(t -> resolveUpperBounds(cls, t));
|
||||
}
|
||||
if (type instanceof TypeVariable) {
|
||||
TypeVariable<?> tv = (TypeVariable<?>) type;
|
||||
Object decl = tv.getGenericDeclaration();
|
||||
if (decl instanceof Class<?>) {
|
||||
Class<?> declCls = (Class<?>) decl;
|
||||
Map<TypeVariable<?>, Type> args = TypeUtils.getTypeArguments(cls, declCls);
|
||||
Type argTv = args.get(tv);
|
||||
if (argTv != null) {
|
||||
return resolveUpperBounds(cls, argTv);
|
||||
}
|
||||
}
|
||||
return Stream.of(TypeUtils.getImplicitBounds(tv))
|
||||
.flatMap(t -> resolveUpperBounds(cls, t));
|
||||
}
|
||||
/**
|
||||
* NB. This method is always called with a type taken from "T extends TargetObject" So, an
|
||||
* array should never be possible.
|
||||
*/
|
||||
throw new AssertionError("Cannot handle type: " + type);
|
||||
}
|
||||
|
||||
static Set<Class<? extends TargetObject>> getBoundsOfFetchElements(
|
||||
Class<? extends TargetObjectRef> cls) {
|
||||
try {
|
||||
Method method = cls.getMethod("fetchElements", new Class<?>[] { boolean.class });
|
||||
Type ret = method.getGenericReturnType();
|
||||
Map<TypeVariable<?>, Type> argsCf =
|
||||
TypeUtils.getTypeArguments(ret, CompletableFuture.class);
|
||||
Type typeCfT = argsCf.get(CompletableFuture.class.getTypeParameters()[0]);
|
||||
|
||||
Map<TypeVariable<?>, Type> argsMap = TypeUtils.getTypeArguments(typeCfT, Map.class);
|
||||
Type typeCfMapV = argsMap.get(Map.class.getTypeParameters()[1]);
|
||||
|
||||
return filterBounds(TargetObject.class, resolveUpperBounds(cls, typeCfMapV))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Set<Class<? extends TargetObject>> getBoundsOfObjectAttributeGetter(
|
||||
Class<? extends TargetObject> cls, Method getter) {
|
||||
Class<?> retCls = getter.getReturnType();
|
||||
if (TargetObject.class.isAssignableFrom(retCls)) {
|
||||
return Set.of(retCls.asSubclass(TargetObject.class));
|
||||
}
|
||||
Type ret = getter.getGenericReturnType();
|
||||
Map<TypeVariable<?>, Type> argsTtor =
|
||||
TypeUtils.getTypeArguments(ret, TypedTargetObjectRef.class);
|
||||
if (argsTtor != null) {
|
||||
Type typeTtorT = argsTtor.get(TypedTargetObjectRef.class.getTypeParameters()[0]);
|
||||
|
||||
return filterBounds(TargetObject.class, resolveUpperBounds(cls, typeTtorT))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
if (TargetObjectRef.class.isAssignableFrom(retCls)) {
|
||||
return Set.of(TargetObject.class);
|
||||
}
|
||||
throw new IllegalArgumentException("Getter " + getter +
|
||||
" for attribute must return primitive or subclass of " +
|
||||
TargetObjectRef.class);
|
||||
}
|
||||
|
||||
protected final Map<Class<? extends TargetObject>, SchemaName> namesByClass =
|
||||
new LinkedHashMap<>();
|
||||
protected final Map<Class<? extends TargetObject>, TargetObjectSchema> schemasByClass =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
protected SchemaName nameFromAnnotatedClass(Class<? extends TargetObject> cls) {
|
||||
synchronized (namesByClass) {
|
||||
return namesByClass.computeIfAbsent(cls, c -> {
|
||||
TargetObjectSchemaInfo info = cls.getAnnotation(TargetObjectSchemaInfo.class);
|
||||
if (info == null) {
|
||||
// TODO: Compile-time validation?
|
||||
throw new IllegalArgumentException("Class " + cls + " is not annotated with @" +
|
||||
TargetObjectSchemaInfo.class.getSimpleName());
|
||||
}
|
||||
String name = info.name();
|
||||
if (name.equals("")) {
|
||||
return new SchemaName(cls.getSimpleName());
|
||||
}
|
||||
return new SchemaName(name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void addPublicMethodsFromClass(SchemaBuilder builder,
|
||||
Class<? extends TargetObject> declCls, Class<? extends TargetObject> cls) {
|
||||
for (Method declMethod : declCls.getDeclaredMethods()) {
|
||||
int mod = declMethod.getModifiers();
|
||||
if (!Modifier.isPublic(mod)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TargetAttributeType at = declMethod.getAnnotation(TargetAttributeType.class);
|
||||
if (at == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// In case it was overridden with a more-specific return type
|
||||
Method method;
|
||||
try {
|
||||
method = cls.getMethod(declMethod.getName(), declMethod.getParameterTypes());
|
||||
}
|
||||
catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
AttributeSchema attrSchema;
|
||||
try {
|
||||
attrSchema =
|
||||
attributeSchemaFromAnnotatedMethod(declCls, method, at);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not get schema name for attribute accessor " + method + " in " + cls, e);
|
||||
}
|
||||
if (attrSchema != null) {
|
||||
builder.addAttributeSchema(attrSchema, declMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected TargetObjectSchema fromAnnotatedClass(Class<? extends TargetObject> cls) {
|
||||
synchronized (namesByClass) {
|
||||
SchemaName name = nameFromAnnotatedClass(cls);
|
||||
return schemasByClass.computeIfAbsent(cls, c -> {
|
||||
TargetObjectSchemaInfo info = cls.getAnnotation(TargetObjectSchemaInfo.class);
|
||||
SchemaBuilder builder = builder(name);
|
||||
|
||||
Set<Class<?>> allParents = ReflectionUtilities.getAllParents(cls);
|
||||
for (Class<?> parent : allParents) {
|
||||
DebuggerTargetObjectIface ifaceAnnot =
|
||||
parent.getAnnotation(DebuggerTargetObjectIface.class);
|
||||
if (ifaceAnnot != null) {
|
||||
builder.addInterface(parent.asSubclass(TargetObject.class));
|
||||
}
|
||||
}
|
||||
|
||||
builder.setCanonicalContainer(info.canonicalContainer());
|
||||
|
||||
boolean sawDefaultElementType = false;
|
||||
for (TargetElementType et : info.elements()) {
|
||||
if (et.index().equals("")) {
|
||||
sawDefaultElementType = true;
|
||||
}
|
||||
builder.addElementSchema(et.index(), nameFromClass(et.type()), et);
|
||||
}
|
||||
if (!sawDefaultElementType) {
|
||||
Set<Class<? extends TargetObject>> bounds = getBoundsOfFetchElements(cls);
|
||||
if (bounds.size() != 1) {
|
||||
// TODO: Compile-time validation?
|
||||
throw new IllegalArgumentException(
|
||||
"Could not identify unique element class: " + bounds);
|
||||
}
|
||||
else {
|
||||
Class<? extends TargetObject> bound = bounds.iterator().next();
|
||||
SchemaName schemaName;
|
||||
try {
|
||||
schemaName = nameFromClass(bound);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not get schema name from bound " + bound + " of " +
|
||||
cls + ".fetchElements()",
|
||||
e);
|
||||
}
|
||||
builder.setDefaultElementSchema(schemaName);
|
||||
}
|
||||
}
|
||||
|
||||
addPublicMethodsFromClass(builder, cls, cls);
|
||||
for (Class<?> parent : allParents) {
|
||||
if (TargetObject.class.isAssignableFrom(parent)) {
|
||||
addPublicMethodsFromClass(builder, parent.asSubclass(TargetObject.class),
|
||||
cls);
|
||||
}
|
||||
}
|
||||
for (TargetAttributeType at : info.attributes()) {
|
||||
AttributeSchema attrSchema = attributeSchemaFromAnnotation(at);
|
||||
builder.addAttributeSchema(attrSchema, at);
|
||||
}
|
||||
|
||||
return builder.buildAndAdd();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected String attributeNameFromBean(String beanName, boolean isBool) {
|
||||
beanName = isBool
|
||||
? StringUtils.removeStartIgnoreCase(beanName, "is")
|
||||
: StringUtils.removeStartIgnoreCase(beanName, "get");
|
||||
if (beanName.equals("")) {
|
||||
throw new IllegalArgumentException("Attribute getter must have a name");
|
||||
}
|
||||
return beanName
|
||||
.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2")
|
||||
.replaceAll("([a-z])([A-Z])", "$1_$2")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
protected AttributeSchema attributeSchemaFromAnnotation(TargetAttributeType at) {
|
||||
return new DefaultAttributeSchema(at.name(), nameFromClass(at.type()),
|
||||
at.required(), at.fixed(), at.hidden());
|
||||
}
|
||||
|
||||
protected AttributeSchema attributeSchemaFromAnnotatedMethod(Class<? extends TargetObject> cls,
|
||||
Method method, TargetAttributeType at) {
|
||||
if (method.getParameterCount() != 0) {
|
||||
// TODO: Compile-time validation?
|
||||
throw new IllegalArgumentException(
|
||||
"Non-getter method " + method + " is annotated with @" +
|
||||
TargetAttributeType.class.getSimpleName());
|
||||
}
|
||||
String name = at.name();
|
||||
Class<?> ret = method.getReturnType();
|
||||
if (name.equals("")) {
|
||||
name = attributeNameFromBean(method.getName(),
|
||||
EnumerableTargetObjectSchema.BOOL.getTypes().contains(ret));
|
||||
}
|
||||
SchemaName primitiveName =
|
||||
EnumerableTargetObjectSchema.nameForPrimitive(ret);
|
||||
if (primitiveName != null) {
|
||||
return new DefaultAttributeSchema(name, primitiveName,
|
||||
at.required(), at.fixed(), at.hidden());
|
||||
}
|
||||
Set<Class<? extends TargetObject>> bounds = getBoundsOfObjectAttributeGetter(cls, method);
|
||||
if (bounds.size() != 1) {
|
||||
// TODO: Compile-time validation?
|
||||
throw new IllegalArgumentException(
|
||||
"Could not identify unique attribute class for method " + method + ": " + bounds);
|
||||
}
|
||||
return new DefaultAttributeSchema(name, nameFromClass(bounds.iterator().next()),
|
||||
at.required(), at.fixed(), at.hidden());
|
||||
}
|
||||
|
||||
protected SchemaName nameFromClass(Class<?> cls) {
|
||||
SchemaName name = EnumerableTargetObjectSchema.nameForPrimitive(cls);
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
if (TargetObject.class.isAssignableFrom(cls)) {
|
||||
return nameFromAnnotatedClass(cls.asSubclass(TargetObject.class));
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot figure schema from class: " + cls);
|
||||
}
|
||||
|
||||
protected void fillDependencies() {
|
||||
while (fillDependenciesRound()) {
|
||||
// Action is side-effect of predicate
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean fillDependenciesRound() {
|
||||
Set<Class<? extends TargetObject>> classes = new HashSet<>(namesByClass.keySet());
|
||||
classes.removeAll(schemasByClass.keySet());
|
||||
if (classes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (Class<? extends TargetObject> cls : classes) {
|
||||
fromAnnotatedClass(cls);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public TargetObjectSchema getSchemaForClass(Class<? extends TargetObject> cls) {
|
||||
TargetObjectSchema schema = fromAnnotatedClass(cls);
|
||||
fillDependencies();
|
||||
return schema;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
|
||||
public class DefaultSchemaContext implements SchemaContext {
|
||||
private final Map<SchemaName, TargetObjectSchema> schemas = new LinkedHashMap<>();
|
||||
|
||||
public DefaultSchemaContext() {
|
||||
for (EnumerableTargetObjectSchema schema : EnumerableTargetObjectSchema.values()) {
|
||||
schemas.put(schema.getName(), schema);
|
||||
}
|
||||
}
|
||||
|
||||
public SchemaBuilder builder(SchemaName name) {
|
||||
return new SchemaBuilder(this, name);
|
||||
}
|
||||
|
||||
public synchronized void putSchema(TargetObjectSchema schema) {
|
||||
if (schemas.containsKey(schema.getName())) {
|
||||
throw new IllegalArgumentException("Name already in context: " + schema.getName());
|
||||
}
|
||||
schemas.put(schema.getName(), schema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized TargetObjectSchema getSchema(SchemaName name) {
|
||||
return Objects.requireNonNull(schemas.get(name), "No such schema name: " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Set<TargetObjectSchema> getAllSchemas() {
|
||||
// Set.copyOf does not preserve iteration order
|
||||
return Collections.unmodifiableSet(new LinkedHashSet<>(schemas.values()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (TargetObjectSchema s : schemas.values()) {
|
||||
sb.append(s + "\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof DefaultSchemaContext) {
|
||||
DefaultSchemaContext that = (DefaultSchemaContext) obj;
|
||||
return Objects.equals(this.schemas, that.schemas);
|
||||
}
|
||||
if (obj instanceof SchemaContext) {
|
||||
SchemaContext that = (SchemaContext) obj;
|
||||
return this.schemas.values().equals(that.getAllSchemas());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return schemas.hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
public class DefaultTargetObjectSchema
|
||||
implements TargetObjectSchema, Comparable<DefaultTargetObjectSchema> {
|
||||
private static final String INDENT = " ";
|
||||
|
||||
protected static class DefaultAttributeSchema
|
||||
implements AttributeSchema, Comparable<DefaultAttributeSchema> {
|
||||
private final String name;
|
||||
private final SchemaName schema;
|
||||
private final boolean isRequired;
|
||||
private final boolean isFixed;
|
||||
private final boolean isHidden;
|
||||
|
||||
public DefaultAttributeSchema(String name, SchemaName schema, boolean isRequired,
|
||||
boolean isFixed, boolean isHidden) {
|
||||
if (name.equals("") && isRequired) {
|
||||
throw new IllegalArgumentException(
|
||||
"The default attribute schema cannot be required");
|
||||
}
|
||||
this.name = name;
|
||||
this.schema = schema;
|
||||
this.isRequired = isRequired;
|
||||
this.isFixed = isFixed;
|
||||
this.isHidden = isHidden;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<attr name=%s schema=%s required=%s fixed=%s hidden=%s>",
|
||||
name, schema, isRequired, isFixed, isHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Generally speaking, object identity is sufficient for checking equality in production;
|
||||
* however, this method is provided for testing equality between an actual and expected
|
||||
* attribute schema.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DefaultAttributeSchema)) {
|
||||
return false;
|
||||
}
|
||||
DefaultAttributeSchema that = (DefaultAttributeSchema) obj;
|
||||
if (!Objects.equals(this.name, that.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.schema, that.schema)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isRequired != that.isRequired) {
|
||||
return false;
|
||||
}
|
||||
if (this.isFixed != that.isFixed) {
|
||||
return false;
|
||||
}
|
||||
if (this.isHidden != that.isHidden) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DefaultAttributeSchema o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaName getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
return isRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFixed() {
|
||||
return isFixed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden() {
|
||||
return isHidden;
|
||||
}
|
||||
}
|
||||
|
||||
private final SchemaContext context;
|
||||
private final SchemaName name;
|
||||
private final Class<?> type;
|
||||
private final Set<Class<? extends TargetObject>> interfaces;
|
||||
private final boolean isCanonicalContainer;
|
||||
private final Map<String, SchemaName> elementSchemas;
|
||||
private final SchemaName defaultElementSchema;
|
||||
private final Map<String, AttributeSchema> attributeSchemas;
|
||||
private final AttributeSchema defaultAttributeSchema;
|
||||
|
||||
DefaultTargetObjectSchema(SchemaContext context, SchemaName name, Class<?> type,
|
||||
Set<Class<? extends TargetObject>> interfaces, boolean isCanonicalContainer,
|
||||
Map<String, SchemaName> elementSchemas, SchemaName defaultElementSchema,
|
||||
Map<String, AttributeSchema> attributeSchemas, AttributeSchema defaultAttributeSchema) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.interfaces = Collections.unmodifiableSet(new LinkedHashSet<>(interfaces));
|
||||
this.isCanonicalContainer = isCanonicalContainer;
|
||||
this.elementSchemas = Collections.unmodifiableMap(new LinkedHashMap<>(elementSchemas));
|
||||
this.defaultElementSchema = defaultElementSchema;
|
||||
this.attributeSchemas = Collections.unmodifiableMap(new LinkedHashMap<>(attributeSchemas));
|
||||
this.defaultAttributeSchema = defaultAttributeSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaName getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends TargetObject>> getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanonicalContainer() {
|
||||
return isCanonicalContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, SchemaName> getElementSchemas() {
|
||||
return elementSchemas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaName getDefaultElementSchema() {
|
||||
return defaultElementSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, AttributeSchema> getAttributeSchemas() {
|
||||
return attributeSchemas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return defaultAttributeSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
toString(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected void toString(StringBuilder sb) {
|
||||
sb.append("schema ");
|
||||
sb.append(name);
|
||||
if (isCanonicalContainer) {
|
||||
sb.append("*");
|
||||
}
|
||||
sb.append(" {\n" + INDENT);
|
||||
sb.append("ifaces = [");
|
||||
for (Class<? extends TargetObject> iface : interfaces) {
|
||||
sb.append(DebuggerObjectModel.requireIfaceName(iface));
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append("]\n" + INDENT);
|
||||
sb.append("elements = ");
|
||||
sb.append(elementSchemas);
|
||||
sb.append(" default " + defaultElementSchema);
|
||||
sb.append("\n" + INDENT);
|
||||
sb.append("attributes = ");
|
||||
sb.append(attributeSchemas);
|
||||
sb.append(" default " + defaultAttributeSchema);
|
||||
sb.append("\n}");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Generally speaking, object identity is sufficient for checking equality in production;
|
||||
* however, this method is provided for testing equality between an actual and expected schema.
|
||||
* Furthermore, this tests more for "content equality" than it does schema equivalence. In
|
||||
* particular, if the two entries being compared come from different contexts, then, even though
|
||||
* they may refer to child schemas by the same name, those child schemas may not be equivalent.
|
||||
* This test will consider them "equal," even though they specify different overall schemas.
|
||||
* Testing for true equivalence has too many nuances to consider here: What if they come from
|
||||
* different contexts? What if they refer to different schemas, but those schemas are
|
||||
* equivalent? etc.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DefaultTargetObjectSchema)) {
|
||||
return false;
|
||||
}
|
||||
DefaultTargetObjectSchema that = (DefaultTargetObjectSchema) obj;
|
||||
if (!Objects.equals(this.name, that.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.type, that.type)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.interfaces, that.interfaces)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isCanonicalContainer != that.isCanonicalContainer) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.elementSchemas, that.elementSchemas)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.defaultElementSchema, that.defaultElementSchema)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.attributeSchemas, that.attributeSchemas)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.defaultAttributeSchema, that.defaultAttributeSchema)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DefaultTargetObjectSchema o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.attributes.TargetDataType;
|
||||
import ghidra.dbg.attributes.TargetObjectRefList;
|
||||
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetObject.TargetUpdateMode;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
|
||||
public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
|
||||
/**
|
||||
* The top-most type descriptor
|
||||
*
|
||||
* <p>
|
||||
* The described value can be any primitive or a {@link TargetObject}.
|
||||
*/
|
||||
ANY("ANY", Object.class) {
|
||||
@Override
|
||||
public SchemaName getDefaultElementSchema() {
|
||||
return OBJECT.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return AttributeSchema.DEFAULT_ANY;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The least restrictive, but least informative object schema.
|
||||
*
|
||||
* <p>
|
||||
* This requires nothing more than the described value to be a {@link TargetObject}.
|
||||
*/
|
||||
OBJECT("OBJECT", TargetObject.class) {
|
||||
@Override
|
||||
public SchemaName getDefaultElementSchema() {
|
||||
return OBJECT.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return AttributeSchema.DEFAULT_ANY;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A type so restrictive nothing can satisfy it.
|
||||
*
|
||||
* <p>
|
||||
* This is how a schema specifies that a particular key is not allowed. It is commonly used as
|
||||
* the default attribute when only certain enumerated attributes are allowed. It is also used as
|
||||
* the type for the children of primitives, since primitives cannot have successors.
|
||||
*/
|
||||
VOID("VOID", Void.class, void.class),
|
||||
BOOL("BOOL", Boolean.class, boolean.class),
|
||||
BYTE("BYTE", Byte.class, byte.class),
|
||||
SHORT("SHORT", Short.class, short.class),
|
||||
INT("INT", Integer.class, int.class),
|
||||
LONG("LONG", Long.class, long.class),
|
||||
STRING("STRING", String.class),
|
||||
ADDRESS("ADDRESS", Address.class),
|
||||
RANGE("RANGE", AddressRange.class),
|
||||
DATA_TYPE("DATA_TYPE", TargetDataType.class),
|
||||
LIST_OBJECT("LIST_OBJECT", TargetObjectRefList.class),
|
||||
MAP_PARAMETERS("MAP_PARAMETERS", TargetParameterMap.class),
|
||||
SET_ATTACH_KIND("SET_ATTACH_KIND", TargetAttachKindSet.class), // TODO: Limited built-in generics
|
||||
SET_BREAKPOINT_KIND("SET_BREAKPOINT_KIND", TargetBreakpointKindSet.class),
|
||||
SET_STEP_KIND("SET_STEP_KIND", TargetStepKindSet.class),
|
||||
ACCESSIBILITY("ACCESSIBILITY", TargetAccessibility.class),
|
||||
EXECUTION_STATE("EXECUTION_STATE", TargetExecutionState.class),
|
||||
UPDATE_MODE("UPDATE_MODE", TargetUpdateMode.class);
|
||||
|
||||
private static class MinimalSchemaContext extends DefaultSchemaContext {
|
||||
private static final SchemaContext INSTANCE = new MinimalSchemaContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a suitable schema for a given Java primitive class
|
||||
*
|
||||
* <p>
|
||||
* The term "primitive" here is used in terms of object schemas, not in terms of Java types.
|
||||
*
|
||||
* @param cls the class, which may or may not be the boxed form
|
||||
* @return the schema or null if no schema is suitable
|
||||
*/
|
||||
public static EnumerableTargetObjectSchema schemaForPrimitive(Class<?> cls) {
|
||||
for (EnumerableTargetObjectSchema schema : EnumerableTargetObjectSchema.values()) {
|
||||
if (schema.getTypes().contains(cls)) {
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a suitable enumerable schema for a given Java class
|
||||
*
|
||||
* @see #schemaForPrimitive(Class)
|
||||
* @param cls the class, which may or may no be the boxed form
|
||||
* @return the name or null if no schema is suitable
|
||||
*/
|
||||
public static SchemaName nameForPrimitive(Class<?> cls) {
|
||||
EnumerableTargetObjectSchema schema = schemaForPrimitive(cls);
|
||||
return schema == null ? null : schema.getName();
|
||||
}
|
||||
|
||||
private final SchemaName name;
|
||||
private final List<Class<?>> types;
|
||||
|
||||
private EnumerableTargetObjectSchema(String name, Class<?>... types) {
|
||||
this.name = new SchemaName(name);
|
||||
this.types = List.of(types);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaContext getContext() {
|
||||
return MinimalSchemaContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaName getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return types.get(0);
|
||||
}
|
||||
|
||||
public List<Class<?>> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends TargetObject>> getInterfaces() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanonicalContainer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, SchemaName> getElementSchemas() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaName getDefaultElementSchema() {
|
||||
return VOID.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, AttributeSchema> getAttributeSchemas() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return AttributeSchema.DEFAULT_VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void searchFor(Set<PathPattern> result, List<String> prefix, boolean parentIsCanonical,
|
||||
Class<? extends TargetObject> type, boolean requireCanonical) {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
|
||||
public class SchemaBuilder {
|
||||
private final DefaultSchemaContext context;
|
||||
private final SchemaName name;
|
||||
|
||||
private Class<?> type = TargetObject.class;
|
||||
private Set<Class<? extends TargetObject>> interfaces = new LinkedHashSet<>();
|
||||
private boolean isCanonicalContainer = false;
|
||||
private Map<String, SchemaName> elementSchemas = new LinkedHashMap<>();
|
||||
private SchemaName defaultElementSchema = EnumerableTargetObjectSchema.OBJECT.getName();
|
||||
private Map<String, AttributeSchema> attributeSchemas = new LinkedHashMap<>();
|
||||
private AttributeSchema defaultAttributeSchema = AttributeSchema.DEFAULT_ANY;
|
||||
|
||||
private Map<String, Object> elementOrigins = new LinkedHashMap<>();
|
||||
private Map<String, Object> attributeOrigins = new LinkedHashMap<>();
|
||||
|
||||
public SchemaBuilder(DefaultSchemaContext context, SchemaName name) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public SchemaBuilder setType(Class<?> type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public SchemaBuilder setInterfaces(Set<Class<? extends TargetObject>> interfaces) {
|
||||
this.interfaces.clear();
|
||||
this.interfaces.addAll(interfaces);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<Class<? extends TargetObject>> getInterfaces() {
|
||||
return Set.copyOf(interfaces);
|
||||
}
|
||||
|
||||
public SchemaBuilder addInterface(Class<? extends TargetObject> iface) {
|
||||
this.interfaces.add(iface);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SchemaBuilder setCanonicalContainer(boolean isCanonicalContainer) {
|
||||
this.isCanonicalContainer = isCanonicalContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isCanonicalContaineration() {
|
||||
return isCanonicalContainer;
|
||||
}
|
||||
|
||||
public SchemaBuilder setElementSchemas(Map<String, SchemaName> elementSchemas) {
|
||||
this.elementSchemas.clear();
|
||||
this.elementSchemas.putAll(elementSchemas);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the schema for a child element
|
||||
*
|
||||
* @param index the index whose schema to define, or "" for the default
|
||||
* @param schema the schema defining the element
|
||||
* @return this builder
|
||||
*/
|
||||
public SchemaBuilder addElementSchema(String index, SchemaName schema, Object origin) {
|
||||
if (index.equals("")) {
|
||||
return setDefaultElementSchema(schema);
|
||||
}
|
||||
if (elementSchemas.containsKey(index)) {
|
||||
throw new IllegalArgumentException("Duplicate element index '" + index +
|
||||
"' origin1=" + elementOrigins.get(index) +
|
||||
" origin2=" + origin);
|
||||
}
|
||||
elementSchemas.put(index, schema);
|
||||
elementOrigins.put(index, origin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, SchemaName> getElementSchemas() {
|
||||
return Map.copyOf(elementSchemas);
|
||||
}
|
||||
|
||||
public SchemaBuilder setDefaultElementSchema(SchemaName defaultElementSchema) {
|
||||
this.defaultElementSchema = defaultElementSchema;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SchemaName getDefaultElementSchema() {
|
||||
return defaultElementSchema;
|
||||
}
|
||||
|
||||
public SchemaBuilder setAttributeSchemas(Map<String, AttributeSchema> attributeSchemas) {
|
||||
this.attributeSchemas.clear();
|
||||
this.attributeSchemas.putAll(attributeSchemas);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the schema for a child attribute.
|
||||
*
|
||||
* <p>
|
||||
* If the attribute schema's name is empty, the given schema becomes the default attribute
|
||||
* schema.
|
||||
*
|
||||
* @param schema the attribute schema to add to the definition
|
||||
* @return this builder
|
||||
*/
|
||||
public SchemaBuilder addAttributeSchema(AttributeSchema schema, Object origin) {
|
||||
if (schema.getName().equals("")) {
|
||||
return setDefaultAttributeSchema(schema);
|
||||
}
|
||||
if (attributeSchemas.containsKey(schema.getName())) {
|
||||
throw new IllegalArgumentException("Duplicate attribute name '" + schema.getName() +
|
||||
"' origin1=" + attributeOrigins.get(schema.getName()) +
|
||||
" origin2=" + origin);
|
||||
}
|
||||
attributeSchemas.put(schema.getName(), schema);
|
||||
attributeOrigins.put(schema.getName(), origin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, AttributeSchema> getAttributeSchemas() {
|
||||
return Map.copyOf(attributeSchemas);
|
||||
}
|
||||
|
||||
public SchemaBuilder setDefaultAttributeSchema(AttributeSchema defaultAttributeSchema) {
|
||||
this.defaultAttributeSchema = defaultAttributeSchema;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return defaultAttributeSchema;
|
||||
}
|
||||
|
||||
public TargetObjectSchema buildAndAdd() {
|
||||
TargetObjectSchema schema = build();
|
||||
context.putSchema(schema);
|
||||
return schema;
|
||||
}
|
||||
|
||||
public TargetObjectSchema build() {
|
||||
return new DefaultTargetObjectSchema(context, name, type, interfaces, isCanonicalContainer,
|
||||
elementSchemas, defaultElementSchema, attributeSchemas, defaultAttributeSchema);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
|
||||
/**
|
||||
* A collection of related schemas
|
||||
*/
|
||||
public interface SchemaContext {
|
||||
/**
|
||||
* Resolve a schema in this context by name
|
||||
*
|
||||
* <p>
|
||||
* Note that resolving a name generated outside of this context may have undefined results. In
|
||||
* most cases, it will resolve to the schema whose name has the same string representation, but
|
||||
* it might instead throw a {@link NullPointerException}.
|
||||
*
|
||||
* @param name the schema's name
|
||||
* @return the schema
|
||||
* @throws NullPointerException if no schema by the given name exists
|
||||
*/
|
||||
TargetObjectSchema getSchema(SchemaName name);
|
||||
|
||||
/**
|
||||
* Collect all schemas in this context
|
||||
*
|
||||
* @return the set of all schemas
|
||||
*/
|
||||
Set<TargetObjectSchema> getAllSchemas();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* 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.dbg.target.schema;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
/**
|
||||
* A schema annotation to describe a model attribute.
|
||||
*
|
||||
* <p>
|
||||
* It can be used in {@link TargetObjectSchemaInfo#attributes()} or be applied to a public, possibly
|
||||
* inherited, getter method for the attribute.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TargetAttributeType {
|
||||
/**
|
||||
* The name of the attribute
|
||||
*
|
||||
* <p>
|
||||
* When used in {@link TargetObjectSchemaInfo#attributes()}, the default {@code ""} matches any
|
||||
* attribute name and should rarely, if ever, be used. When applied to a getter, the default
|
||||
* indicates the name should be derived from the method name, by removing {@code get}, and
|
||||
* converting from {@code CamelCase} to {@code lower_case_with_underscores}.
|
||||
*
|
||||
* @return the attribute name
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* The Java class best representing the attribute's type.
|
||||
*
|
||||
* <p>
|
||||
* When applied to a getter, {@code type} can be omitted. In that case, the getter's return type
|
||||
* will be used to derive the attribute's schema instead.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
Class<?> type() default TargetObject.class;
|
||||
|
||||
/**
|
||||
* True if the attribute must be set before the object exists.
|
||||
*
|
||||
* @return true if required, false if optional
|
||||
*/
|
||||
boolean required() default false;
|
||||
|
||||
/**
|
||||
* True if the attribute can only be set once.
|
||||
*
|
||||
* @return true if fixed/final/immutable, false if mutable
|
||||
*/
|
||||
boolean fixed() default false;
|
||||
|
||||
/**
|
||||
* Whether or not this attribute should be displayed by default
|
||||
*
|
||||
* <p>
|
||||
* This is purely a UI hint and has no other semantic consequences.
|
||||
*
|
||||
* @return true if hidden, false if visible
|
||||
*/
|
||||
boolean hidden() default false;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user