mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-4894: Improve and better test Java debug connector.
This commit is contained in:
parent
24a5928c3c
commit
cce33f772e
44 changed files with 5159 additions and 1345 deletions
|
@ -857,7 +857,7 @@ bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1
|
|||
|
||||
<P>The following launchers based on the Java Debugger are included out of the box:</P>
|
||||
|
||||
<H3><A name="java"></A>java launch</H3>
|
||||
<H3><A name="java"></A>java</H3>
|
||||
|
||||
<P>This launcher uses the native Java Debug Interface (JDI) to launch the current
|
||||
<TT>.class</TT> file.</P>
|
||||
|
|
|
@ -77,4 +77,14 @@ public class ProtobufSocket<T extends AbstractMessage> {
|
|||
Msg.error(this, "Unable to close ProtobufSocket");
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteAddress() {
|
||||
try {
|
||||
return channel.getRemoteAddress().toString();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,14 +15,21 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class RmiBatch {
|
||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
|
||||
|
||||
public class RmiBatch implements AutoCloseable {
|
||||
|
||||
private int refCount = 0;
|
||||
private Set<Object> futures = new HashSet<>();
|
||||
private final RmiClient client;
|
||||
private volatile int refCount = 0;
|
||||
private final List<RequestResult> futures = new ArrayList<>();
|
||||
|
||||
public RmiBatch(RmiClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void inc() {
|
||||
refCount++;
|
||||
|
@ -31,14 +38,35 @@ public class RmiBatch {
|
|||
public int dec() {
|
||||
return --refCount;
|
||||
}
|
||||
|
||||
public void append(Object f) {
|
||||
futures.add(f);
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
client.endBatch(this);
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Object results() {
|
||||
return null;
|
||||
public void append(RequestResult f) {
|
||||
synchronized (futures) {
|
||||
futures.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Object> results() throws InterruptedException, ExecutionException {
|
||||
List<RequestResult> futures = futures();
|
||||
List<Object> results = new ArrayList<>(futures.size());
|
||||
for (RequestResult r : futures) {
|
||||
results.add(r.get());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<RequestResult> futures() {
|
||||
synchronized (futures) {
|
||||
return List.copyOf(futures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ package ghidra.app.plugin.core.debug.client.tracermi;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
|
@ -37,18 +37,51 @@ import ghidra.rmi.trace.TraceRmi.Language;
|
|||
import ghidra.rmi.trace.TraceRmi.Value.Builder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class RmiClient {
|
||||
|
||||
static class RequestResult extends CompletableFuture<Object> {
|
||||
public final RootMessage request;
|
||||
|
||||
public RequestResult(RootMessage req) {
|
||||
this.request = req;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() throws InterruptedException, ExecutionException {
|
||||
if (Swing.isSwingThread()) {
|
||||
throw new AssertionError("Refusing indefinite wait on Swing thread");
|
||||
}
|
||||
return super.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
if (Swing.isSwingThread() && unit.toSeconds(timeout) > 1) {
|
||||
throw new AssertionError("Refusing a timeout > 1 second on Swing thread");
|
||||
}
|
||||
return super.get(timeout, unit);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RmiException extends RuntimeException {
|
||||
public RmiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private final ProtobufSocket<RootMessage> socket;
|
||||
private final String description;
|
||||
private int nextTraceId = 0;
|
||||
private RmiBatch currentBatch = null;
|
||||
private volatile RmiBatch currentBatch = null;
|
||||
|
||||
Map<Integer, RmiTrace> traces = new HashMap<>();
|
||||
private SchemaContext schemaContext;
|
||||
private RmiMethodHandlerThread handler;
|
||||
private RmiReplyHandlerThread handler;
|
||||
private static RmiMethodRegistry methodRegistry;
|
||||
private Deque<RootMessage> requests = new LinkedList<>();
|
||||
private Deque<RequestResult> requests = new LinkedList<>();
|
||||
|
||||
public static TargetObjectSchema loadSchema(String resourceName, String rootName) {
|
||||
XmlSchemaContext schemaContext;
|
||||
|
@ -64,19 +97,10 @@ public class RmiClient {
|
|||
}
|
||||
}
|
||||
|
||||
// public static TargetObjectSchema getSchema(String name) {
|
||||
// try {
|
||||
// return SCHEMA_CTX.getSchema(new SchemaName(name));
|
||||
// } catch (NullPointerException e) {
|
||||
// System.err.println("Possibly non-existent schema: "+name);
|
||||
// return SCHEMA_CTX.getSchema(new SchemaName("OBJECT"));
|
||||
// }
|
||||
// }
|
||||
|
||||
public static enum TraceRmiResolution {
|
||||
RES_ADJUST("adjust", Resolution.CR_ADJUST), //
|
||||
RES_DENY("deny", Resolution.CR_DENY), //
|
||||
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE), //
|
||||
RES_ADJUST("adjust", Resolution.CR_ADJUST),
|
||||
RES_DENY("deny", Resolution.CR_DENY),
|
||||
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE),
|
||||
;
|
||||
|
||||
TraceRmiResolution(String val, TraceRmi.Resolution description) {
|
||||
|
@ -89,9 +113,9 @@ public class RmiClient {
|
|||
}
|
||||
|
||||
public static enum TraceRmiValueKinds {
|
||||
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES), //
|
||||
ELEMENTS("elements", ValueKinds.VK_ELEMENTS), //
|
||||
BOTH("both", ValueKinds.VK_BOTH), //
|
||||
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES),
|
||||
ELEMENTS("elements", ValueKinds.VK_ELEMENTS),
|
||||
BOTH("both", ValueKinds.VK_BOTH),
|
||||
;
|
||||
|
||||
TraceRmiValueKinds(String val, TraceRmi.ValueKinds description) {
|
||||
|
@ -106,16 +130,12 @@ public class RmiClient {
|
|||
public RmiClient(SocketChannel channel, String description) {
|
||||
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
|
||||
this.description = description;
|
||||
this.handler = new RmiMethodHandlerThread(this, socket);
|
||||
this.handler = new RmiReplyHandlerThread(this, socket);
|
||||
handler.start();
|
||||
}
|
||||
|
||||
public ProtobufSocket<RootMessage> getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
return description + " at " + socket.getRemoteAddress();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
@ -123,10 +143,18 @@ public class RmiClient {
|
|||
socket.close();
|
||||
}
|
||||
|
||||
private void send(RootMessage msg) {
|
||||
private RequestResult send(RootMessage msg) {
|
||||
try {
|
||||
requests.push(msg);
|
||||
socket.send(msg);
|
||||
RequestResult result = new RequestResult(msg);
|
||||
synchronized (requests) {
|
||||
socket.send(msg);
|
||||
requests.push(result);
|
||||
}
|
||||
RmiBatch cb = currentBatch;
|
||||
if (cb != null) {
|
||||
cb.append(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -137,12 +165,11 @@ public class RmiClient {
|
|||
if (compiler == null) {
|
||||
compiler = new CompilerSpecID("default");
|
||||
}
|
||||
RmiTrace trace = new RmiTrace(this, nextTraceId);
|
||||
traces.put(nextTraceId, trace);
|
||||
send(RootMessage.newBuilder()
|
||||
int traceId = nextTraceId++;
|
||||
RequestResult result = send(RootMessage.newBuilder()
|
||||
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(nextTraceId++))
|
||||
.setId(traceId))
|
||||
.setLanguage(Language.newBuilder()
|
||||
.setId(language.getIdAsString()))
|
||||
.setCompiler(Compiler.newBuilder()
|
||||
|
@ -150,6 +177,8 @@ public class RmiClient {
|
|||
.setPath(FilePath.newBuilder()
|
||||
.setPath(path)))
|
||||
.build());
|
||||
RmiTrace trace = new RmiTrace(this, traceId, result);
|
||||
traces.put(traceId, trace);
|
||||
return trace;
|
||||
}
|
||||
|
||||
|
@ -159,6 +188,7 @@ public class RmiClient {
|
|||
.setOid(DomObjId.newBuilder()
|
||||
.setId(id)))
|
||||
.build());
|
||||
traces.remove(id);
|
||||
}
|
||||
|
||||
public void saveTrace(int id) {
|
||||
|
@ -248,7 +278,7 @@ public class RmiClient {
|
|||
.setRange(AddrRange.newBuilder()
|
||||
.setSpace(range.getAddressSpace().getName())
|
||||
.setOffset(range.getMinAddress().getOffset())
|
||||
.setExtend(range.getLength())))
|
||||
.setExtend(range.getLength() - 1)))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
@ -279,7 +309,7 @@ public class RmiClient {
|
|||
.setSpace(ppath);
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
String name = names[i];
|
||||
builder.setNames(i, name);
|
||||
builder.addNames(name);
|
||||
}
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestDeleteRegisterValue(builder)
|
||||
|
@ -298,9 +328,8 @@ public class RmiClient {
|
|||
.build());
|
||||
}
|
||||
|
||||
public void createObject(int traceId, String path) {
|
||||
//System.err.println("createObject:"+path);
|
||||
send(RootMessage.newBuilder()
|
||||
RequestResult createObject(int traceId, String path) {
|
||||
return send(RootMessage.newBuilder()
|
||||
.setRequestCreateObject(RequestCreateObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
|
@ -323,12 +352,13 @@ public class RmiClient {
|
|||
.build());
|
||||
}
|
||||
|
||||
public void insertObject(int traceId, ObjSpec object, Lifespan span, Resolution r) {
|
||||
public void insertObject(int traceId, long id, Lifespan span, Resolution r) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestInsertObject(RequestInsertObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(object)
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setId(id))
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
|
@ -336,12 +366,28 @@ public class RmiClient {
|
|||
.build());
|
||||
}
|
||||
|
||||
public void removeObject(int traceId, ObjSpec object, Lifespan span, boolean tree) {
|
||||
public void removeObject(int traceId, String path, Lifespan span, boolean tree) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(object)
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path)))
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setTree(tree))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void removeObject(int traceId, long id, Lifespan span, boolean tree) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setId(id))
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
|
@ -389,19 +435,18 @@ public class RmiClient {
|
|||
}
|
||||
|
||||
public void getObject(int traceId, String path) {
|
||||
RequestGetObject.Builder builder = RequestGetObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path)));
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestGetObject(builder)
|
||||
.setRequestGetObject(RequestGetObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path))))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void getValues(int traceId, Lifespan span, String pattern) {
|
||||
send(RootMessage.newBuilder()
|
||||
RequestResult getValues(int traceId, Lifespan span, String pattern) {
|
||||
return send(RootMessage.newBuilder()
|
||||
.setRequestGetValues(RequestGetValues.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
|
@ -412,9 +457,9 @@ public class RmiClient {
|
|||
.build());
|
||||
}
|
||||
|
||||
public void getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
|
||||
RequestResult getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
|
||||
String key) {
|
||||
send(RootMessage.newBuilder()
|
||||
return send(RootMessage.newBuilder()
|
||||
.setRequestGetValuesIntersecting(RequestGetValuesIntersecting.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
|
@ -445,6 +490,9 @@ public class RmiClient {
|
|||
@SuppressWarnings("unchecked")
|
||||
private Builder buildValue(Object value) {
|
||||
Builder builder = Value.newBuilder();
|
||||
if (value == null) {
|
||||
return builder.setNullValue(Null.newBuilder());
|
||||
}
|
||||
if (value instanceof String str) {
|
||||
return builder.setStringValue(str);
|
||||
}
|
||||
|
@ -497,7 +545,11 @@ public class RmiClient {
|
|||
return builder.setBoolArrValue(b.build());
|
||||
}
|
||||
if (list.get(0) instanceof Short) {
|
||||
ShortArr.Builder b = ShortArr.newBuilder().addAllArr((List<Integer>) list);
|
||||
List<Integer> newList = new ArrayList<>();
|
||||
for (Object object : list) {
|
||||
newList.add(((Short) object).intValue());
|
||||
}
|
||||
ShortArr.Builder b = ShortArr.newBuilder().addAllArr(newList);
|
||||
return builder.setShortArrValue(b.build());
|
||||
}
|
||||
if (list.get(0) instanceof Integer) {
|
||||
|
@ -570,12 +622,12 @@ public class RmiClient {
|
|||
.setDisplay(param.getDisplay())
|
||||
.setDescription(param.getDescription())
|
||||
.setType(param.getType())
|
||||
.setDefaultValue(param.getDefaultValue())
|
||||
.setDefaultValue(buildValue(param.getDefaultValue()))
|
||||
.setRequired(param.isRequired())
|
||||
.build();
|
||||
}
|
||||
|
||||
public void handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
|
||||
public XReplyInvokeMethod handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
|
||||
RmiRemoteMethod rm = getMethod(req.getName());
|
||||
Object[] arglist = new Object[req.getArgumentsCount()];
|
||||
java.lang.reflect.Method m = rm.getMethod();
|
||||
|
@ -585,57 +637,50 @@ public class RmiClient {
|
|||
argmap.put(arg.getName(), arg);
|
||||
}
|
||||
int i = 0;
|
||||
for (Parameter p : m.getParameters()) {
|
||||
for (RmiRemoteMethodParameter p : rm.getParameters()) {
|
||||
MethodArgument arg = argmap.get(p.getName());
|
||||
if (arg != null) {
|
||||
Object obj = argToObject(traceId, arg);
|
||||
Object obj = argToObject(traceId, arg.getValue());
|
||||
arglist[i++] = obj;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object ret = m.invoke(rm.getContainer(), arglist);
|
||||
if (ret != null) {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setXreplyInvokeMethod(XReplyInvokeMethod.newBuilder()
|
||||
.setReturnValue(buildValue(ret)))
|
||||
.build());
|
||||
return XReplyInvokeMethod.newBuilder()
|
||||
.setReturnValue(buildValue(ret))
|
||||
.build();
|
||||
}
|
||||
return XReplyInvokeMethod.newBuilder()
|
||||
.setReturnValue(buildValue(true))
|
||||
.build();
|
||||
}
|
||||
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
||||
| IOException e) {
|
||||
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
String message = e.getMessage();
|
||||
if (message != null) {
|
||||
Msg.error(this, message);
|
||||
try {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setXreplyInvokeMethod(
|
||||
XReplyInvokeMethod.newBuilder().setError(message))
|
||||
.build());
|
||||
}
|
||||
catch (IOException e1) {
|
||||
Msg.error(this, e1.getMessage());
|
||||
}
|
||||
Msg.error(this, "Error handling method invocation:" + message);
|
||||
return XReplyInvokeMethod.newBuilder()
|
||||
.setError(message)
|
||||
.build();
|
||||
}
|
||||
return XReplyInvokeMethod.newBuilder()
|
||||
.setError(e.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Object argToObject(int traceId, MethodArgument arg) {
|
||||
if (arg == null) {
|
||||
throw new RuntimeException("Null argument passed to argToObject");
|
||||
}
|
||||
Value value = arg.getValue();
|
||||
Object argToObject(int traceId, Value value) {
|
||||
if (value.hasStringValue()) {
|
||||
return value.getStringValue();
|
||||
}
|
||||
if (value.hasStringArrValue()) {
|
||||
return value.getStringArrValue();
|
||||
return value.getStringArrValue().getArrList();
|
||||
}
|
||||
if (value.hasBoolValue()) {
|
||||
return value.getBoolValue();
|
||||
}
|
||||
if (value.hasBoolArrValue()) {
|
||||
return value.getBoolArrValue();
|
||||
return value.getBoolArrValue().getArrList();
|
||||
}
|
||||
if (value.hasCharValue()) {
|
||||
return value.getCharValue();
|
||||
|
@ -647,19 +692,19 @@ public class RmiClient {
|
|||
return value.getShortValue();
|
||||
}
|
||||
if (value.hasShortArrValue()) {
|
||||
return value.getShortArrValue();
|
||||
return value.getShortArrValue().getArrList();
|
||||
}
|
||||
if (value.hasIntValue()) {
|
||||
return value.getIntValue();
|
||||
}
|
||||
if (value.hasIntArrValue()) {
|
||||
return value.getIntArrValue();
|
||||
return value.getIntArrValue().getArrList();
|
||||
}
|
||||
if (value.hasLongValue()) {
|
||||
return value.getLongValue();
|
||||
}
|
||||
if (value.hasLongArrValue()) {
|
||||
return value.getLongArrValue();
|
||||
return value.getLongArrValue().getArrList();
|
||||
}
|
||||
if (value.hasAddressValue()) {
|
||||
return decodeAddr(traceId, value.getAddressValue());
|
||||
|
@ -681,6 +726,61 @@ public class RmiClient {
|
|||
return proxyObjectPath(traceId, path);
|
||||
}
|
||||
|
||||
String argToType(Value value) {
|
||||
if (value.hasStringValue()) {
|
||||
return "STRING";
|
||||
}
|
||||
if (value.hasStringArrValue()) {
|
||||
return "STRING_ARR";
|
||||
}
|
||||
if (value.hasBoolValue()) {
|
||||
return "BOOL";
|
||||
}
|
||||
if (value.hasBoolArrValue()) {
|
||||
return "BOOL_ARR";
|
||||
}
|
||||
if (value.hasCharValue()) {
|
||||
return "CHAR";
|
||||
}
|
||||
if (value.hasCharArrValue()) {
|
||||
return "CHAR_ARR";
|
||||
}
|
||||
if (value.hasShortValue()) {
|
||||
return "SHORT";
|
||||
}
|
||||
if (value.hasShortArrValue()) {
|
||||
return "SHORT_ARR";
|
||||
}
|
||||
if (value.hasIntValue()) {
|
||||
return "INT";
|
||||
}
|
||||
if (value.hasIntArrValue()) {
|
||||
return "INT_ARR";
|
||||
}
|
||||
if (value.hasLongValue()) {
|
||||
return "LONG";
|
||||
}
|
||||
if (value.hasLongArrValue()) {
|
||||
return "LONG_ARR";
|
||||
}
|
||||
if (value.hasAddressValue()) {
|
||||
return "ADDRESS";
|
||||
}
|
||||
if (value.hasRangeValue()) {
|
||||
return "RANGE";
|
||||
}
|
||||
if (value.hasByteValue()) {
|
||||
return "BYTE";
|
||||
}
|
||||
if (value.hasBytesValue()) {
|
||||
return "BYTE_ARR";
|
||||
}
|
||||
if (value.hasNullValue()) {
|
||||
return "NULL";
|
||||
}
|
||||
return "OBJECT";
|
||||
}
|
||||
|
||||
private Address decodeAddr(int id, Addr addr) {
|
||||
RmiTrace trace = traces.get(id);
|
||||
return trace.memoryMapper.genAddr(addr.getSpace(), addr.getOffset());
|
||||
|
@ -700,32 +800,33 @@ public class RmiClient {
|
|||
return methodRegistry.getMap().get(name);
|
||||
}
|
||||
|
||||
public Object startBatch() {
|
||||
public RmiBatch startBatch() {
|
||||
if (currentBatch == null) {
|
||||
currentBatch = new RmiBatch();
|
||||
currentBatch = new RmiBatch(this);
|
||||
}
|
||||
currentBatch.inc();
|
||||
return currentBatch;
|
||||
}
|
||||
|
||||
public Object endBatch() {
|
||||
RmiBatch cb = null;
|
||||
if (0 == currentBatch.dec()) {
|
||||
cb = currentBatch;
|
||||
boolean hasBatch() {
|
||||
return currentBatch != null;
|
||||
}
|
||||
|
||||
void endBatch(RmiBatch batch) throws InterruptedException, ExecutionException {
|
||||
if (currentBatch.dec() == 0) {
|
||||
RmiBatch cb = currentBatch;
|
||||
currentBatch = null;
|
||||
cb.results();
|
||||
}
|
||||
if (cb != null) {
|
||||
return cb.results();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TargetObjectSchema getSchema(String schema) {
|
||||
return schemaContext.getSchema(new SchemaName(schema));
|
||||
}
|
||||
|
||||
public RootMessage getRequestsPoll() {
|
||||
return requests.poll();
|
||||
public RequestResult pollRequest() {
|
||||
synchronized (requests) {
|
||||
return requests.poll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class RmiMethodHandlerThread extends Thread {
|
||||
|
||||
private RmiClient client;
|
||||
private ProtobufSocket<RootMessage> socket;
|
||||
private boolean terminated = false;
|
||||
|
||||
public RmiMethodHandlerThread(RmiClient client, ProtobufSocket<RootMessage> socket) {
|
||||
this.client = client;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!terminated) {
|
||||
try {
|
||||
RootMessage msg = socket.recv();
|
||||
if (msg.hasXrequestInvokeMethod()) {
|
||||
try {
|
||||
XRequestInvokeMethod req = msg.getXrequestInvokeMethod();
|
||||
int id = req.getOid().getId();
|
||||
RmiTrace trace = client.traces.get(id);
|
||||
trace.handleInvokeMethod(req);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
RootMessage request = client.getRequestsPoll();
|
||||
if (msg.hasError()) {
|
||||
Msg.error(this, msg);
|
||||
}
|
||||
else if (msg.hasReplyCreateObject()) {
|
||||
ReplyCreateObject reply = msg.getReplyCreateObject();
|
||||
RmiTrace trace = client.traces.get(request.getRequestCreateObject().getOid().getId());
|
||||
trace.handleCreateObject(reply);
|
||||
}
|
||||
else if (msg.hasReplyCreateTrace()) {
|
||||
ReplyCreateTrace reply = msg.getReplyCreateTrace();
|
||||
RmiTrace trace = client.traces.get(request.getRequestCreateTrace().getOid().getId());
|
||||
trace.handleCreateTrace(reply);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, e.getMessage());
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Handler exiting");
|
||||
}
|
||||
|
||||
public void close() {
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,12 +27,13 @@ public class RmiMethodRegistry {
|
|||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface TraceMethod {
|
||||
String action();
|
||||
String display() default "";
|
||||
String description() default "";
|
||||
String schema() default "ANY";
|
||||
String action() default "";
|
||||
|
||||
String display() default "";
|
||||
|
||||
String description() default "";
|
||||
}
|
||||
|
||||
|
||||
Map<String, RmiRemoteMethod> map = new HashMap<>();
|
||||
|
||||
public RmiRemoteMethod getMethod(String key) {
|
||||
|
|
|
@ -19,14 +19,12 @@ import java.lang.reflect.Method;
|
|||
import java.lang.reflect.Parameter;
|
||||
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.rmi.trace.TraceRmi.Value;
|
||||
|
||||
public class RmiRemoteMethod {
|
||||
|
||||
|
||||
private final SchemaContext schemaContext;
|
||||
private String name;
|
||||
private String action;
|
||||
|
@ -37,7 +35,8 @@ public class RmiRemoteMethod {
|
|||
private RmiMethods instance;
|
||||
private Method m;
|
||||
|
||||
public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display, String description, TargetObjectSchema schema, RmiMethods instance, Method m) {
|
||||
public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display,
|
||||
String description, TargetObjectSchema schema, RmiMethods instance, Method m) {
|
||||
this.schemaContext = schemaContext;
|
||||
this.name = name;
|
||||
this.action = action;
|
||||
|
@ -46,54 +45,23 @@ public class RmiRemoteMethod {
|
|||
this.params = new RmiRemoteMethodParameter[m.getParameterCount()];
|
||||
this.schema = schema;
|
||||
this.instance = instance;
|
||||
this.m = m;
|
||||
|
||||
this.m = m;
|
||||
|
||||
int i = 0;
|
||||
for (Parameter p : m.getParameters()) {
|
||||
TargetObjectSchema pschema = getSchemaFromParameter(p);
|
||||
String pname = p.getName(); // NB: don't change this unless yuou resolve the ordering issues
|
||||
String pdesc = pname;
|
||||
String pdisp = pname;
|
||||
if (i == 0) {
|
||||
RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class);
|
||||
if (annot != null) {
|
||||
pschema = schemaContext.getSchema(new SchemaName(annot.schema()));
|
||||
}
|
||||
pdisp = "Object";
|
||||
ParameterDescription<?> desc = TargetMethod.ParameterDescription.annotated(p);
|
||||
TargetObjectSchema pschema;
|
||||
if (desc.type != RmiTraceObject.class) {
|
||||
pschema = EnumerableTargetObjectSchema.schemaForPrimitive(desc.type);
|
||||
}
|
||||
Value pdef = null;
|
||||
TargetMethod.Param pannot = p.getAnnotation(TargetMethod.Param.class);
|
||||
if (pannot != null) {
|
||||
pdesc = pannot.description();
|
||||
pdisp = pannot.display();
|
||||
else {
|
||||
pschema = schemaContext.getSchema(new SchemaName(desc.schema));
|
||||
}
|
||||
boolean required = i != 0;
|
||||
params[i++] = new RmiRemoteMethodParameter(pname, pschema, required, pdef, pdisp, pdesc);
|
||||
params[i++] = new RmiRemoteMethodParameter(desc.name, pschema, desc.required,
|
||||
desc.defaultValue, desc.display, desc.description);
|
||||
}
|
||||
}
|
||||
|
||||
private TargetObjectSchema getSchemaFromParameter(Parameter p) {
|
||||
if (p.getAnnotatedType().getType().equals(String.class)) {
|
||||
return EnumerableTargetObjectSchema.STRING;
|
||||
}
|
||||
if (p.getAnnotatedType().getType().equals(Boolean.class)) {
|
||||
return EnumerableTargetObjectSchema.BOOL;
|
||||
}
|
||||
if (p.getAnnotatedType().getType().equals(Integer.class)) {
|
||||
return EnumerableTargetObjectSchema.INT;
|
||||
}
|
||||
if (p.getAnnotatedType().getType().equals(Long.class)) {
|
||||
return EnumerableTargetObjectSchema.LONG;
|
||||
}
|
||||
if (p.getAnnotatedType().getType().equals(Address.class)) {
|
||||
return EnumerableTargetObjectSchema.ADDRESS;
|
||||
}
|
||||
if (p.getAnnotatedType().getType().equals(AddressRange.class)) {
|
||||
return EnumerableTargetObjectSchema.RANGE;
|
||||
}
|
||||
return EnumerableTargetObjectSchema.ANY;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -125,5 +93,4 @@ public class RmiRemoteMethod {
|
|||
public RmiMethods getContainer() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,24 +19,24 @@ import ghidra.dbg.target.schema.TargetObjectSchema;
|
|||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
|
||||
public class RmiRemoteMethodParameter {
|
||||
|
||||
|
||||
private final String name;
|
||||
private final TargetObjectSchema schema;
|
||||
private final boolean required;
|
||||
private final Value defaultValue;
|
||||
private final Object defaultValue;
|
||||
private final String display;
|
||||
private final String description;
|
||||
|
||||
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
|
||||
Value defaultValue, String display, String description) {
|
||||
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
|
||||
Object defaultValue, String display, String description) {
|
||||
this.name = name;
|
||||
this.schema = schema;
|
||||
this.required = required;
|
||||
this.defaultValue = defaultValue;
|
||||
this.display = display;
|
||||
this.description = description;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -57,15 +57,11 @@ public class RmiRemoteMethodParameter {
|
|||
return ValueType.newBuilder().setName(schemaName).build();
|
||||
}
|
||||
|
||||
public Value getDefaultValue() {
|
||||
if (defaultValue != null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Value.newBuilder().setNullValue(Null.newBuilder()).build();
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
|
||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RmiException;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class RmiReplyHandlerThread extends Thread {
|
||||
|
||||
private RmiClient client;
|
||||
private ProtobufSocket<RootMessage> socket;
|
||||
private boolean terminated = false;
|
||||
|
||||
public RmiReplyHandlerThread(RmiClient client, ProtobufSocket<RootMessage> socket) {
|
||||
this.client = client;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!terminated) {
|
||||
try {
|
||||
RootMessage msg = socket.recv();
|
||||
if (msg.hasXrequestInvokeMethod()) {
|
||||
try {
|
||||
XRequestInvokeMethod req = msg.getXrequestInvokeMethod();
|
||||
int id = req.getOid().getId();
|
||||
RmiTrace trace = client.traces.get(id);
|
||||
XReplyInvokeMethod reply = trace.handleInvokeMethod(req);
|
||||
socket.send(RootMessage.newBuilder().setXreplyInvokeMethod(reply).build());
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Error handling method invocation", e);
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setXreplyInvokeMethod(
|
||||
XReplyInvokeMethod.newBuilder().setError(e.toString()))
|
||||
.build());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
RequestResult result = client.pollRequest();
|
||||
if (result == null) {
|
||||
System.err.println("REPLY without request: " + msg);
|
||||
continue;
|
||||
}
|
||||
RootMessage request = result.request;
|
||||
|
||||
switch (msg.getMsgCase()) {
|
||||
case ERROR -> {
|
||||
Msg.error(this, msg.getError().getMessage());
|
||||
result.completeExceptionally(new RmiException(msg.getError().getMessage()));
|
||||
}
|
||||
case REPLY_CREATE_OBJECT -> {
|
||||
ReplyCreateObject reply = msg.getReplyCreateObject();
|
||||
RmiTrace trace =
|
||||
client.traces.get(request.getRequestCreateObject().getOid().getId());
|
||||
result.complete(trace.handleCreateObject(reply));
|
||||
}
|
||||
case REPLY_CREATE_TRACE -> {
|
||||
ReplyCreateTrace reply = msg.getReplyCreateTrace();
|
||||
RmiTrace trace =
|
||||
client.traces.get(request.getRequestCreateTrace().getOid().getId());
|
||||
result.complete(trace.handleCreateTrace(reply));
|
||||
}
|
||||
case REPLY_GET_VALUES -> {
|
||||
ReplyGetValues reply = msg.getReplyGetValues();
|
||||
RmiTrace trace =
|
||||
client.traces.get(request.getRequestGetValues().getOid().getId());
|
||||
result.complete(trace.handleGetValues(reply));
|
||||
}
|
||||
case REPLY_DISASSEMBLE -> {
|
||||
ReplyDisassemble reply = msg.getReplyDisassemble();
|
||||
RmiTrace trace =
|
||||
client.traces.get(request.getRequestDisassemble().getOid().getId());
|
||||
result.complete(trace.handleDisassemble(reply));
|
||||
}
|
||||
default -> result.complete(null);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e.getMessage() == null) {
|
||||
Msg.error(this, "Error processing reply", e);
|
||||
}
|
||||
else {
|
||||
Msg.error(this, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Handler exiting");
|
||||
}
|
||||
|
||||
public void close() {
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,14 +15,15 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
|
@ -30,14 +31,15 @@ import ghidra.util.LockHold;
|
|||
import ghidra.util.Msg;
|
||||
|
||||
public class RmiTrace {
|
||||
|
||||
|
||||
final RmiClient client;
|
||||
private final int id;
|
||||
|
||||
private RequestResult createResult;
|
||||
|
||||
private int nextTx = 0;
|
||||
private Object txLock = new Object();
|
||||
private ReadWriteLock snLock = new ReentrantReadWriteLock();
|
||||
|
||||
|
||||
private Set<String> overlays = new HashSet<>();
|
||||
private long currentSnap = -1;
|
||||
private boolean closed = false;
|
||||
|
@ -45,11 +47,17 @@ public class RmiTrace {
|
|||
public MemoryMapper memoryMapper;
|
||||
public RegisterMapper registerMapper;
|
||||
|
||||
public RmiTrace(RmiClient client, int id) {
|
||||
public RmiTrace(RmiClient client, int id, RequestResult createResult) {
|
||||
this.client = client;
|
||||
this.id = id;
|
||||
this.id = id;
|
||||
this.createResult = createResult;
|
||||
}
|
||||
|
||||
|
||||
public void checkResult(long timeoutMs)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
createResult.get(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (closed) {
|
||||
return;
|
||||
|
@ -63,7 +71,7 @@ public class RmiTrace {
|
|||
|
||||
public RmiTransaction startTx(String description, boolean undoable) {
|
||||
int txid;
|
||||
synchronized(txLock) {
|
||||
synchronized (txLock) {
|
||||
txid = nextTx++;
|
||||
}
|
||||
client.startTx(id, description, undoable, txid);
|
||||
|
@ -74,17 +82,20 @@ public class RmiTrace {
|
|||
return startTx(description, false);
|
||||
}
|
||||
|
||||
public void endTx(int txid, boolean abort) {
|
||||
public void endTx(int txid, boolean abort) {
|
||||
client.endTx(id, txid, abort);
|
||||
}
|
||||
|
||||
|
||||
public long nextSnap() {
|
||||
try (LockHold hold = LockHold.lock(snLock.writeLock())) {
|
||||
return ++currentSnap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public long snapshot(String description, String datatime, Long snap) {
|
||||
if (datatime == null) {
|
||||
datatime = "";
|
||||
}
|
||||
if (snap == null) {
|
||||
snap = nextSnap();
|
||||
}
|
||||
|
@ -97,68 +108,90 @@ public class RmiTrace {
|
|||
return currentSnap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setSnap(long snap) {
|
||||
try (LockHold hold = LockHold.lock(snLock.writeLock())) {
|
||||
this.currentSnap = snap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public long snapOrCurrent(Long snap) {
|
||||
try (LockHold hold = LockHold.lock(snLock.readLock())) {
|
||||
return snap == null ? this.currentSnap : snap.longValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void createOverlaySpace(String base, String name) {
|
||||
if (overlays.contains(name)) {
|
||||
return;
|
||||
}
|
||||
client.createOverlaySpace(id, base, name);
|
||||
}
|
||||
|
||||
|
||||
public void createOverlaySpace(Address repl, Address orig) {
|
||||
createOverlaySpace(repl.getAddressSpace().getName(), orig.getAddressSpace().getName());
|
||||
}
|
||||
|
||||
public void putBytes(Address addr, byte[] data, Long snap) {
|
||||
public void putBytes(Address addr, byte[] data, Long snap) {
|
||||
client.putBytes(id, snapOrCurrent(snap), addr, data);
|
||||
}
|
||||
|
||||
|
||||
public void setMemoryState(AddressRange range, MemoryState state, Long snap) {
|
||||
client.setMemoryState(id, snapOrCurrent(snap), range, state);
|
||||
}
|
||||
|
||||
|
||||
public void deleteBytes(AddressRange range, Long snap) {
|
||||
client.deleteBytes(id, snapOrCurrent(snap), range);
|
||||
}
|
||||
|
||||
|
||||
public void putRegisters(String ppath, RegisterValue[] values, Long snap) {
|
||||
client.putRegisters(id, snapOrCurrent(snap), ppath, values);
|
||||
}
|
||||
|
||||
|
||||
public void deleteRegisters(String ppath, String[] names, Long snap) {
|
||||
client.deleteRegisters(id, snapOrCurrent(snap), ppath, names);
|
||||
}
|
||||
|
||||
|
||||
public void createRootObject(SchemaContext schemaContext, String schema) {
|
||||
client.createRootObject(id, schemaContext, schema);
|
||||
}
|
||||
|
||||
public void createObject(String path) {
|
||||
client.createObject(id, path);
|
||||
|
||||
public RmiTraceObject createObject(String path) {
|
||||
RequestResult result = client.createObject(id, path);
|
||||
return new RmiTraceObject(this, path, result);
|
||||
}
|
||||
|
||||
public void handleCreateObject(ReplyCreateObject reply) {
|
||||
RmiTraceObject obj = new RmiTraceObject(this, reply.getObject());
|
||||
try (RmiTransaction tx = startTx("CreateObject", false); LockHold hold = LockHold.lock(snLock.readLock())) {
|
||||
obj.insert(currentSnap, null);
|
||||
}
|
||||
public RmiTraceObject createAndInsertObject(String path) {
|
||||
RmiTraceObject object = createObject(path);
|
||||
object.insert(currentSnap, null);
|
||||
return object;
|
||||
}
|
||||
|
||||
public void handleCreateTrace(ReplyCreateTrace reply) {
|
||||
|
||||
long handleCreateObject(ReplyCreateObject reply) {
|
||||
return reply.getObject().getId();
|
||||
}
|
||||
|
||||
|
||||
public Void handleCreateTrace(ReplyCreateTrace reply) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<RmiTraceObjectValue> handleGetValues(ReplyGetValues reply) {
|
||||
List<RmiTraceObjectValue> result = new ArrayList<>();
|
||||
for (ValDesc d : reply.getValuesList()) {
|
||||
RmiTraceObject parent = proxyObject(d.getParent());
|
||||
Lifespan span = Lifespan.span(d.getSpan().getMin(), d.getSpan().getMax());
|
||||
Object value = client.argToObject(id, d.getValue());
|
||||
TargetObjectSchema schema = client.getSchema(client.argToType(d.getValue()));
|
||||
result.add(new RmiTraceObjectValue(parent, span, d.getKey(), value, schema));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long handleDisassemble(ReplyDisassemble reply) {
|
||||
Msg.info(this, "Disassembled " + reply.getLength() + " bytes");
|
||||
return reply.getLength();
|
||||
}
|
||||
|
||||
public void insertObject(String path) {
|
||||
Lifespan span = getLifespan();
|
||||
client.insertObject(id, path, span, Resolution.CR_ADJUST);
|
||||
|
@ -174,29 +207,71 @@ public class RmiTrace {
|
|||
Lifespan span = getLifespan();
|
||||
client.setValue(id, ppath, span, key, value, null);
|
||||
}
|
||||
|
||||
|
||||
public void retainValues(String ppath, Set<String> keys, ValueKinds kinds) {
|
||||
Lifespan span = getLifespan();
|
||||
client.retainValues(id, ppath, span, kinds, keys);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T doSync(RequestResult r) {
|
||||
if (client.hasBatch()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (T) r.get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RequestResult getValuesAsync(String pattern) {
|
||||
Lifespan span = getLifespan();
|
||||
return client.getValues(id, span, pattern);
|
||||
}
|
||||
|
||||
public List<RmiTraceObjectValue> getValues(String pattern) {
|
||||
return doSync(getValuesAsync(pattern));
|
||||
}
|
||||
|
||||
public RequestResult getValuesRngAsync(Address start, long length) {
|
||||
Lifespan span = getLifespan();
|
||||
try {
|
||||
AddressRange range = new AddressRangeImpl(start, length);
|
||||
return client.getValuesIntersecting(id, span, range, "");
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public List<RmiTraceObjectValue> getValuesRng(Address start, long length) {
|
||||
return doSync(getValuesRngAsync(start, length));
|
||||
}
|
||||
|
||||
public void activate(String path) {
|
||||
if (path == null) {
|
||||
Msg.error(this, "Attempt to activate null");
|
||||
return;
|
||||
}
|
||||
client.activate(id, path);
|
||||
}
|
||||
|
||||
public void disassemble(Address start, Long snap) {
|
||||
|
||||
public void disassemble(Address start, Long snap) {
|
||||
client.disassemble(id, snapOrCurrent(snap), start);
|
||||
}
|
||||
|
||||
public void handleInvokeMethod(XRequestInvokeMethod req) {
|
||||
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
|
||||
client.handleInvokeMethod(id, req);
|
||||
}
|
||||
|
||||
public XReplyInvokeMethod handleInvokeMethod(XRequestInvokeMethod req) {
|
||||
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
|
||||
return client.handleInvokeMethod(id, req);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private RmiTraceObject proxyObject(ObjDesc desc) {
|
||||
return client.proxyObjectPath(id, desc.getId(), desc.getPath().getPath());
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectId(Long objectId) {
|
||||
return client.proxyObjectId(id, objectId);
|
||||
}
|
||||
|
|
|
@ -17,45 +17,65 @@ package ghidra.app.plugin.core.debug.client.tracermi;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
|
||||
import ghidra.rmi.trace.TraceRmi.Resolution;
|
||||
import ghidra.rmi.trace.TraceRmi.ValueKinds;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
|
||||
public class RmiTraceObject {
|
||||
|
||||
private RmiTrace trace;
|
||||
private ObjSpec spec;
|
||||
private String path;
|
||||
|
||||
public RmiTraceObject(RmiTrace trace, ObjSpec spec) {
|
||||
this.trace = trace;
|
||||
this.spec = spec;
|
||||
this.path = spec.getPath().getPath();
|
||||
}
|
||||
|
||||
public RmiTraceObject(RmiTrace trace, Long id, String path) {
|
||||
private final RmiTrace trace;
|
||||
private final String path;
|
||||
private volatile Long id;
|
||||
|
||||
public RmiTraceObject(RmiTrace trace, String path) {
|
||||
this.trace = trace;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
|
||||
RmiTraceObject(RmiTrace trace, String path, RequestResult result) {
|
||||
this.trace = trace;
|
||||
this.path = path;
|
||||
result.thenAccept(id -> this.id = (Long) id);
|
||||
}
|
||||
|
||||
public RmiTraceObject(RmiTrace trace, Long id, String path) {
|
||||
this.trace = trace;
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static RmiTraceObject fromId(RmiTrace trace, long id) {
|
||||
return new RmiTraceObject(trace, id, null);
|
||||
}
|
||||
|
||||
|
||||
public static RmiTraceObject fromPath(RmiTrace trace, String path) {
|
||||
return new RmiTraceObject(trace, null, path);
|
||||
}
|
||||
|
||||
public void insert(long snap, Resolution resolution) {
|
||||
|
||||
public Lifespan insert(long snap, Resolution resolution) {
|
||||
if (resolution == null) {
|
||||
resolution = Resolution.CR_ADJUST;
|
||||
}
|
||||
Lifespan span = Lifespan.nowOn(snap);
|
||||
trace.client.insertObject(trace.getId(), spec, span, resolution);
|
||||
if (id != null) {
|
||||
trace.client.insertObject(trace.getId(), id, span, resolution);
|
||||
}
|
||||
else {
|
||||
trace.client.insertObject(trace.getId(), path, span, resolution);
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
public void remove(long snap, boolean tree) {
|
||||
public Lifespan remove(long snap, boolean tree) {
|
||||
Lifespan span = Lifespan.nowOn(snap);
|
||||
trace.client.removeObject(trace.getId(), spec, span, tree);
|
||||
if (id != null) {
|
||||
trace.client.removeObject(trace.getId(), id, span, tree);
|
||||
}
|
||||
else {
|
||||
trace.client.removeObject(trace.getId(), path, span, tree);
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
public void setValue(String key, Object value, long snap, String resolution) {
|
||||
|
@ -71,7 +91,7 @@ public class RmiTraceObject {
|
|||
public void activate() {
|
||||
trace.client.activate(trace.getId(), path);
|
||||
}
|
||||
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
|
||||
public record RmiTraceObjectValue(RmiTraceObject parent, Lifespan span, String key, Object value,
|
||||
TargetObjectSchema schema) {}
|
|
@ -57,8 +57,4 @@ public class RmiTransaction implements AutoCloseable {
|
|||
commit();
|
||||
}
|
||||
|
||||
public RmiTransaction startTx(String description, boolean b) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,10 +130,19 @@ public class RemoteMethodInvocationDialog extends AbstractDebuggerParameterDialo
|
|||
ConfigStateField.getState(state, parameterType(parameter), key));
|
||||
}
|
||||
|
||||
protected ValStr<?> forMissingDefault(RemoteParameter param) {
|
||||
Class<?> type = parameterType(param);
|
||||
if (type == Boolean.class || type == boolean.class) {
|
||||
return ValStr.from(false);
|
||||
}
|
||||
return new ValStr<>(null, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr<?> val) {
|
||||
ValStr<?> v = switch (val.val()) {
|
||||
case Missing __ -> new ValStr<>(null, "");
|
||||
case null -> forMissingDefault(param);
|
||||
case Missing __ -> forMissingDefault(param);
|
||||
case TraceObject obj -> new ValStr<>(obj, obj.getCanonicalPath().toString());
|
||||
default -> val;
|
||||
};
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -42,8 +42,13 @@ import ghidra.framework.model.DomainFile;
|
|||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.listing.ProgramUserData;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.data.Composite;
|
||||
import ghidra.program.model.data.DataTypeComponent;
|
||||
import ghidra.program.model.lang.Processor;
|
||||
import ghidra.program.model.lang.ProcessorNotFoundException;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
@ -124,6 +129,102 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
|||
}
|
||||
}
|
||||
|
||||
public static class FieldIndex {
|
||||
public static FieldIndex fromData(Data data) {
|
||||
if (!(data.getDataType() instanceof Composite dt)) {
|
||||
return null;
|
||||
}
|
||||
return new FieldIndex(dt);
|
||||
}
|
||||
|
||||
private final Map<String, DataTypeComponent> byName;
|
||||
|
||||
public FieldIndex(Composite dt) {
|
||||
byName = Stream.of(dt.getComponents())
|
||||
.collect(Collectors.toMap(c -> c.getFieldName(), c -> c));
|
||||
}
|
||||
|
||||
public Data getField(Data data, String name) {
|
||||
DataTypeComponent dtComp = byName.get(name);
|
||||
if (dtComp == null) {
|
||||
return null;
|
||||
}
|
||||
return data.getComponent(dtComp.getOrdinal());
|
||||
}
|
||||
}
|
||||
|
||||
public static String tryProgramJvmClass(Program program) {
|
||||
Processor procJvm;
|
||||
try {
|
||||
procJvm = Processor.toProcessor("JVM");
|
||||
}
|
||||
catch (ProcessorNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
if (program.getLanguage().getProcessor() != procJvm) {
|
||||
return null;
|
||||
}
|
||||
AddressSpace cpool =
|
||||
program.getLanguage().getAddressFactory().getAddressSpace("constantPool");
|
||||
|
||||
Data dClassFile = program.getListing().getDataAt(cpool.getAddress(0));
|
||||
if (dClassFile == null) {
|
||||
return null;
|
||||
}
|
||||
FieldIndex fiClassFile = FieldIndex.fromData(dClassFile);
|
||||
if (fiClassFile == null) {
|
||||
return null;
|
||||
}
|
||||
Data dThisClass = fiClassFile.getField(dClassFile, "this_class");
|
||||
if (dThisClass == null || !(dThisClass.getValue() instanceof Scalar sThisClass)) {
|
||||
return null;
|
||||
}
|
||||
long thisClassCpi = sThisClass.getValue();
|
||||
|
||||
Data dConstantPool = fiClassFile.getField(dClassFile, "constant_pool");
|
||||
if (dConstantPool == null) {
|
||||
return null;
|
||||
}
|
||||
FieldIndex fiConstantPool = FieldIndex.fromData(dConstantPool);
|
||||
if (fiConstantPool == null) {
|
||||
return null;
|
||||
}
|
||||
Data dThisClassConst =
|
||||
fiConstantPool.getField(dConstantPool, "constant_pool_0x%x".formatted(thisClassCpi));
|
||||
if (dThisClassConst == null ||
|
||||
!"CONSTANT_Class_info".equals(dThisClassConst.getDataType().getName())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FieldIndex fiConstantClassInfo = FieldIndex.fromData(dThisClassConst);
|
||||
if (fiConstantClassInfo == null) {
|
||||
return null;
|
||||
}
|
||||
Data dThisClassNameIndex = fiConstantClassInfo.getField(dThisClassConst, "name_index");
|
||||
if (dThisClassNameIndex == null ||
|
||||
!(dThisClassNameIndex.getValue() instanceof Scalar sThisClassNameIndex)) {
|
||||
return null;
|
||||
}
|
||||
long thisClassNameIndexCpi = sThisClassNameIndex.getValue();
|
||||
|
||||
Data dThisClassNameConst = fiConstantPool.getField(dConstantPool,
|
||||
"constant_pool_0x%x".formatted(thisClassNameIndexCpi));
|
||||
if (dThisClassNameConst == null ||
|
||||
!dThisClassNameConst.getDataType().getName().startsWith("CONSTANT_Utf8_info")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FieldIndex fiUtf8InfoN = FieldIndex.fromData(dThisClassNameConst);
|
||||
if (fiUtf8InfoN == null) {
|
||||
return null;
|
||||
}
|
||||
Data dThisClassNameData = fiUtf8InfoN.getField(dThisClassNameConst, "data");
|
||||
if (!(dThisClassNameData.getValue() instanceof String thisClassName)) {
|
||||
return null;
|
||||
}
|
||||
return thisClassName;
|
||||
}
|
||||
|
||||
public static File tryProgramPath(String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
|
@ -154,6 +255,12 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
|||
if (program == null) {
|
||||
return null;
|
||||
}
|
||||
// TODO: All these tryers should be extension points...?
|
||||
// Probably applicable by language/file type.
|
||||
String jvmClass = tryProgramJvmClass(program);
|
||||
if (jvmClass != null) {
|
||||
return jvmClass;
|
||||
}
|
||||
File exec = tryProgramPath(program.getExecutablePath());
|
||||
if (exec != null) {
|
||||
return exec.getAbsolutePath();
|
||||
|
@ -371,8 +478,7 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
|||
toolLaunchConfigs.putSaveState(name, state);
|
||||
}
|
||||
|
||||
protected record ConfigLast(String configName, long last, Program program) {
|
||||
}
|
||||
protected record ConfigLast(String configName, long last, Program program) {}
|
||||
|
||||
protected ConfigLast checkSavedConfig(Program program, ProgramUserData userData,
|
||||
String propName) {
|
||||
|
|
|
@ -448,12 +448,13 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Cannot send reply", e);
|
||||
Msg.error(this, "Cannot send reply: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveLoop() {
|
||||
boolean canSend = true;
|
||||
try {
|
||||
while (true) {
|
||||
RootMessage req = receive();
|
||||
|
@ -468,8 +469,15 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!send(rep)) {
|
||||
return;
|
||||
/**
|
||||
* The likely cause of this failing is that the remote end has closed the socket.
|
||||
* However, we don't return, because there may be commands still in the queue, and
|
||||
* we should process them until we reach the end of input. This will ensure clients
|
||||
* that brazenly send a bunch of commands and then disconnect before receiving the
|
||||
* replies will have their commands processed, even if unsuccessfully.
|
||||
*/
|
||||
if (canSend) {
|
||||
canSend = send(rep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -25,6 +25,7 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
|||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.framework.plugintool.*;
|
||||
|
@ -71,8 +72,8 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTargetService targetService;
|
||||
// @AutoServiceConsumed // via method
|
||||
private volatile DebuggerTargetService targetService;
|
||||
@AutoServiceConsumed
|
||||
private ProgressService progressService;
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -94,6 +95,25 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
public void setTargetService(DebuggerTargetService targetService) {
|
||||
this.targetService = targetService;
|
||||
record ConnAndTarget(TraceRmiConnection conn, Target target) {}
|
||||
List<ConnAndTarget> targets = new ArrayList<>();
|
||||
synchronized (handlers) {
|
||||
for (TraceRmiConnection conn : getAllConnections()) {
|
||||
for (Target target : conn.getTargets()) {
|
||||
targets.add(new ConnAndTarget(conn, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ConnAndTarget cat : targets) {
|
||||
targetService.publishTarget(cat.target);
|
||||
listeners.invoke().targetPublished(cat.conn, cat.target);
|
||||
}
|
||||
}
|
||||
|
||||
protected CloseableTaskMonitor createMonitor() {
|
||||
if (progressService == null) {
|
||||
return fallbackMonitor;
|
||||
|
@ -201,6 +221,15 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
|
||||
void publishTarget(TraceRmiHandler handler, TraceRmiTarget target) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
if (targetService == null) {
|
||||
/**
|
||||
* I have no idea how this is happening, given targetService is a required service,
|
||||
* and I don't see any way the rmi server can be started before the services are
|
||||
* wired in. Whatever. I'll have to publish all the targets when the service
|
||||
* appears, I guess.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
targetService.publishTarget(target);
|
||||
listeners.invoke().targetPublished(handler, target);
|
||||
});
|
||||
|
@ -208,6 +237,10 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
|
||||
void withdrawTarget(TraceRmiTarget target) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
if (targetService == null) {
|
||||
// This can happen during tear down.
|
||||
return;
|
||||
}
|
||||
targetService.withdrawTarget(target);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteParameter;
|
||||
import ghidra.async.SwingExecutorService;
|
||||
import ghidra.dbg.target.TargetMethod.Param;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema.MinimalSchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.tracermi.RemoteMethod;
|
||||
import ghidra.debug.api.tracermi.RemoteParameter;
|
||||
import ghidra.framework.options.PropertyBoolean;
|
||||
|
||||
public class RemoteMethodInvocationDialogTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
|
||||
private static final SchemaContext CTX = MinimalSchemaContext.INSTANCE;
|
||||
|
||||
public static TestRemoteMethod createTestMethod(Method m) {
|
||||
Map<String, RemoteParameter> params = new LinkedHashMap<>();
|
||||
for (Parameter p : m.getParameters()) {
|
||||
TestRemoteParameter parameter = createParameter(p);
|
||||
params.put(parameter.name(), parameter);
|
||||
}
|
||||
return new TestRemoteMethod(m.getName(), null, "Test", "A test method", params,
|
||||
EnumerableTargetObjectSchema.schemaForPrimitive(m.getReturnType()));
|
||||
}
|
||||
|
||||
public static TestRemoteParameter createParameter(Parameter p) {
|
||||
ParameterDescription<?> desc = ParameterDescription.annotated(p);
|
||||
TargetObjectSchema schema = EnumerableTargetObjectSchema.schemaForPrimitive(desc.type);
|
||||
if (schema == EnumerableTargetObjectSchema.OBJECT ||
|
||||
schema == EnumerableTargetObjectSchema.ANY) {
|
||||
schema = CTX.getSchema(new SchemaName(desc.schema));
|
||||
}
|
||||
return new TestRemoteParameter(desc.name, schema, desc.required, desc.defaultValue,
|
||||
desc.display, desc.description);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getDefaults(RemoteMethod method) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
for (Map.Entry<String, RemoteParameter> ent : method.parameters().entrySet()) {
|
||||
result.put(ent.getKey(), ent.getValue().getDefaultValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
record TestBits(TestRemoteMethod method, CompletableFuture<Map<String, ValStr<?>>> future,
|
||||
InvocationDialogHelper<RemoteParameter, ?> helper) {
|
||||
Component getComponent(String name) {
|
||||
return helper.getEditorComponent(method.parameters().get(name));
|
||||
}
|
||||
|
||||
void setArg(String name, Object value) {
|
||||
helper.setArg(method.parameters().get(name), value);
|
||||
}
|
||||
|
||||
Map<String, Object> invoke() throws Exception {
|
||||
helper.invoke();
|
||||
Map<String, ValStr<?>> args = future.get(1, TimeUnit.SECONDS);
|
||||
return args == null ? null : ValStr.toPlainMap(args);
|
||||
}
|
||||
}
|
||||
|
||||
protected TestBits startTest(Method m) throws Exception {
|
||||
TestRemoteMethod method = createTestMethod(m);
|
||||
Map<String, Object> defaults = getDefaults(method);
|
||||
|
||||
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
|
||||
RemoteMethodInvocationDialog dialog =
|
||||
new RemoteMethodInvocationDialog(tool, CTX, method.display(), method.display(), null);
|
||||
CompletableFuture<Map<String, ValStr<?>>> future = CompletableFuture.supplyAsync(
|
||||
() -> dialog.promptArguments(method.parameters(), defs, defs),
|
||||
SwingExecutorService.LATER);
|
||||
// Yes, I have it in hand, but I still must wait for it to appear on screen.
|
||||
InvocationDialogHelper<RemoteParameter, ?> helper =
|
||||
InvocationDialogHelper.waitFor(RemoteMethodInvocationDialog.class);
|
||||
|
||||
return new TestBits(method, future, helper);
|
||||
}
|
||||
|
||||
public static class MethodTakesBooleanPrimitive {
|
||||
public void theMethod(@Param(name = "b") boolean b) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanPrimitiveField() throws Exception {
|
||||
TestBits bits =
|
||||
startTest(MethodTakesBooleanPrimitive.class.getMethod("theMethod", boolean.class));
|
||||
assertTrue(bits.getComponent("b") instanceof PropertyBoolean);
|
||||
bits.setArg("b", true);
|
||||
Map<String, Object> values = bits.invoke();
|
||||
assertEquals(true, values.get("b"));
|
||||
}
|
||||
|
||||
public static class MethodTakesBooleanBoxed {
|
||||
public void theMethod(@Param(name = "b") Boolean b) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanBoxedField() throws Exception {
|
||||
TestBits bits =
|
||||
startTest(MethodTakesBooleanBoxed.class.getMethod("theMethod", Boolean.class));
|
||||
assertTrue(bits.getComponent("b") instanceof PropertyBoolean);
|
||||
bits.setArg("b", true);
|
||||
Map<String, Object> values = bits.invoke();
|
||||
assertEquals(true, values.get("b"));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue