GP-77: Model schema specification, and GDB's implementation.

This commit is contained in:
Dan 2020-12-28 09:55:03 -05:00
parent c90e12a78b
commit eb66a90f6c
110 changed files with 4207 additions and 354 deletions

View File

@ -202,4 +202,6 @@ public interface GdbGadpServer extends AutoCloseable {
default void close() throws IOException {
terminate();
}
void setExitOnClosed(boolean exitOnClosed);
}

View File

@ -59,4 +59,9 @@ public class GdbGadpServerImpl implements GdbGadpServer {
model.terminate();
server.terminate();
}
@Override
public void setExitOnClosed(boolean exitOnClosed) {
server.setExitOnClosed(exitOnClosed);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -128,7 +128,7 @@ public abstract class SctlTargetNamedDataType<T extends SctlTargetNamedDataType<
}
@Override
public NamedDataTypeKind getKind() {
public NamedDataTypeKind getTypeKind() {
return kind;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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