mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:02:42 +00:00
Merge remote-tracking branch 'origin/GP-4894_JDI_tests_RB1029--SQUASHED'
This commit is contained in:
commit
73224da27b
@ -1,5 +1,6 @@
|
|||||||
//@title java launch
|
//@title java
|
||||||
//@timeout 20000
|
////@image-opt env:OPT_TARGET_CLASS
|
||||||
|
//@timeout 2000000
|
||||||
//@desc <html><body width="300px">
|
//@desc <html><body width="300px">
|
||||||
//@desc <h3>Launch with <tt>java</tt></h3>
|
//@desc <h3>Launch with <tt>java</tt></h3>
|
||||||
//@desc <p>
|
//@desc <p>
|
||||||
@ -10,10 +11,11 @@
|
|||||||
//@menu-group local
|
//@menu-group local
|
||||||
//@icon icon.debugger
|
//@icon icon.debugger
|
||||||
//@help TraceRmiLauncherServicePlugin#java
|
//@help TraceRmiLauncherServicePlugin#java
|
||||||
|
//@env OPT_TARGET_CLASS:str="" "Image" "The Main Class to launch (defaults to current program)."
|
||||||
|
//@env OPT_TARGET_CLASSPATH:str="" "ClassPath" "The JVM classpath"
|
||||||
//@args "Arguments" "Command-line arguments to pass to the target"
|
//@args "Arguments" "Command-line arguments to pass to the target"
|
||||||
//@enum Arch:str JVM Dalvik
|
//@enum Arch:str JVM Dalvik
|
||||||
//@env OPT_ARCH:Arch="JVM" "Arch" "Either 'JVM' or 'Dalvik'"
|
//@env OPT_ARCH:Arch="JVM" "Arch" "Either 'JVM' or 'Dalvik'"
|
||||||
//@env OPT_TARGET_CLASS:str="" "Image" "The Main Class to launch (defaults to current program)."
|
|
||||||
////@env OPT_SUSPEND:bool=true "Suspend" "Suspend the VM on launch."
|
////@env OPT_SUSPEND:bool=true "Suspend" "Suspend the VM on launch."
|
||||||
//@env OPT_INCLUDE:str=n "Include virtual threads" "Include virtual threads."
|
//@env OPT_INCLUDE:str=n "Include virtual threads" "Include virtual threads."
|
||||||
//@env OPT_JSHELL_PATH:file="" "JShell cmd (if desired)" "The full path to jshell."
|
//@env OPT_JSHELL_PATH:file="" "JShell cmd (if desired)" "The full path to jshell."
|
||||||
|
@ -140,6 +140,7 @@ public class JdiEventHandler implements Runnable {
|
|||||||
//System.err.println(event + ":" + vm);
|
//System.err.println(event + ":" + vm);
|
||||||
return switch (event) {
|
return switch (event) {
|
||||||
case ExceptionEvent ev -> processException(ev);
|
case ExceptionEvent ev -> processException(ev);
|
||||||
|
case BreakpointEvent ev -> processBreakpoint(ev);
|
||||||
case AccessWatchpointEvent ev -> processAccessWatchpoint(ev);
|
case AccessWatchpointEvent ev -> processAccessWatchpoint(ev);
|
||||||
case ModificationWatchpointEvent ev -> processWatchpointModification(ev);
|
case ModificationWatchpointEvent ev -> processWatchpointModification(ev);
|
||||||
case WatchpointEvent ev -> processWatchpoint(ev);
|
case WatchpointEvent ev -> processWatchpoint(ev);
|
||||||
|
@ -17,7 +17,7 @@ package ghidra.dbg.jdi.manager.impl;
|
|||||||
|
|
||||||
import static ghidra.lifecycle.Unfinished.TODO;
|
import static ghidra.lifecycle.Unfinished.TODO;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ import com.sun.jdi.event.Event;
|
|||||||
|
|
||||||
import ghidra.dbg.jdi.manager.*;
|
import ghidra.dbg.jdi.manager.*;
|
||||||
import ghidra.dbg.jdi.manager.JdiCause.Causes;
|
import ghidra.dbg.jdi.manager.JdiCause.Causes;
|
||||||
|
import ghidra.dbg.jdi.rmi.jpda.JdiArguments;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
|
|
||||||
@ -61,23 +62,40 @@ public class JdiManagerImpl implements JdiManager {
|
|||||||
virtualMachineManager = Bootstrap.virtualMachineManager();
|
virtualMachineManager = Bootstrap.virtualMachineManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void pumpStream(InputStream in, OutputStream out) {
|
||||||
|
try {
|
||||||
|
in.transferTo(out);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
// We're done!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public VirtualMachine connectVM(Connector cx, Map<String, Connector.Argument> arguments)
|
public VirtualMachine connectVM(Connector cx, Map<String, Connector.Argument> arguments)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
if (cx instanceof LaunchingConnector) {
|
if (cx instanceof LaunchingConnector lcx) {
|
||||||
LaunchingConnector lcx = (LaunchingConnector) cx;
|
VirtualMachine vm = lcx.launch(arguments);
|
||||||
return lcx.launch(arguments);
|
new Thread(() -> pumpStream(vm.process().getErrorStream(), System.err)).start();
|
||||||
|
new Thread(() -> pumpStream(vm.process().getInputStream(), System.out)).start();
|
||||||
|
return vm;
|
||||||
}
|
}
|
||||||
if (cx instanceof AttachingConnector) {
|
if (cx instanceof AttachingConnector acx) {
|
||||||
AttachingConnector acx = (AttachingConnector) cx;
|
|
||||||
return acx.attach(arguments);
|
return acx.attach(arguments);
|
||||||
}
|
}
|
||||||
if (cx instanceof ListeningConnector) {
|
if (cx instanceof ListeningConnector lcx) {
|
||||||
ListeningConnector lcx = (ListeningConnector) cx;
|
|
||||||
return lcx.accept(arguments);
|
return lcx.accept(arguments);
|
||||||
}
|
}
|
||||||
throw new Exception("Unknown connector type");
|
throw new Exception("Unknown connector type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VirtualMachine createVM(Map<String, String> env) {
|
||||||
|
JdiArguments args = new JdiArguments(env);
|
||||||
|
Connector cx = args.getConnector(virtualMachineManager);
|
||||||
|
Map<String, Connector.Argument> defaultArguments = cx.defaultArguments();
|
||||||
|
args.putArguments(defaultArguments);
|
||||||
|
return addVM(cx, defaultArguments);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void terminate() {
|
public void terminate() {
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,7 @@ import ghidra.app.plugin.core.debug.client.tracermi.DefaultRegisterMapper;
|
|||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.program.util.DefaultLanguageService;
|
import ghidra.program.util.DefaultLanguageService;
|
||||||
|
|
||||||
public class TraceJdiArch {
|
public class JdiArch {
|
||||||
|
|
||||||
private LanguageID langID;
|
private LanguageID langID;
|
||||||
private Language language;
|
private Language language;
|
@ -0,0 +1,109 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.jdi.rmi.jpda;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.sun.jdi.VirtualMachineManager;
|
||||||
|
import com.sun.jdi.connect.AttachingConnector;
|
||||||
|
import com.sun.jdi.connect.Connector;
|
||||||
|
import com.sun.jdi.connect.Connector.Argument;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.ShellUtils;
|
||||||
|
|
||||||
|
public class JdiArguments {
|
||||||
|
enum Mode {
|
||||||
|
ATTACH_PORT, ATTACH_PID, LAUNCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, String> env;
|
||||||
|
private final Mode mode;
|
||||||
|
|
||||||
|
public JdiArguments(Map<String, String> env) {
|
||||||
|
this.env = Map.copyOf(env);
|
||||||
|
this.mode = computeMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute/detect the launch mode using the environment map.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* It'd be nice if this were selected/specified in the script body, rather than by what options
|
||||||
|
* are present in its header. The reason we can't, though, is that the JDI client thread needs
|
||||||
|
* to also work within Ghidra's JVM, i.e., without launching a jshell subprocess. By far, the
|
||||||
|
* simplest way to accomplish this is to keep all the logic here, and just pass the environment
|
||||||
|
* map in. For the jshell-subprocess case, it's the environment map proper. For the
|
||||||
|
* in-Ghidra's-VM case, it's the map we would have passed when creating the subprocess.
|
||||||
|
*
|
||||||
|
* @return the mode.
|
||||||
|
*/
|
||||||
|
protected Mode computeMode() {
|
||||||
|
if (env.containsKey("OPT_PORT")) {
|
||||||
|
return Mode.ATTACH_PORT;
|
||||||
|
}
|
||||||
|
if (env.containsKey("OPT_PID")) {
|
||||||
|
return Mode.ATTACH_PID;
|
||||||
|
}
|
||||||
|
return Mode.LAUNCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttachingConnector findConnectorByArgKey(VirtualMachineManager vmm, String key) {
|
||||||
|
return vmm.attachingConnectors()
|
||||||
|
.stream()
|
||||||
|
.filter(ac -> ac.defaultArguments().containsKey(key))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connector getConnector(VirtualMachineManager vmm) {
|
||||||
|
return switch (mode) {
|
||||||
|
case ATTACH_PORT -> findConnectorByArgKey(vmm, "port");
|
||||||
|
case ATTACH_PID -> findConnectorByArgKey(vmm, "pid");
|
||||||
|
case LAUNCH -> vmm.defaultConnector();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putArguments(Map<String, Argument> args) {
|
||||||
|
switch (mode) {
|
||||||
|
case ATTACH_PORT -> {
|
||||||
|
args.get("hostname").setValue(env.get("OPT_HOST").toString());
|
||||||
|
args.get("port").setValue(env.get("OPT_PORT").toString());
|
||||||
|
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
|
||||||
|
}
|
||||||
|
case ATTACH_PID -> {
|
||||||
|
args.get("pid").setValue(env.get("OPT_PID").toString());
|
||||||
|
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
|
||||||
|
}
|
||||||
|
case LAUNCH -> {
|
||||||
|
args.get("main").setValue(env.get("OPT_TARGET_CLASS"));
|
||||||
|
Argument argSuspend = args.get("suspend");
|
||||||
|
String optSuspend = env.get("OPT_SUSPEND");
|
||||||
|
if (argSuspend != null && optSuspend != null) {
|
||||||
|
argSuspend.setValue(optSuspend);
|
||||||
|
}
|
||||||
|
Argument argIncludeVirtualThreads = args.get("includevirtualthreads");
|
||||||
|
String optInclude = env.get("OPT_INCLUDE");
|
||||||
|
if (argIncludeVirtualThreads != null && optInclude != null) {
|
||||||
|
argIncludeVirtualThreads.setValue(optInclude);
|
||||||
|
}
|
||||||
|
String cp = env.get("OPT_TARGET_CLASSPATH");
|
||||||
|
if (!cp.isBlank()) {
|
||||||
|
args.get("options").setValue("-cp " + ShellUtils.generateArgument(cp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,6 @@ package ghidra.dbg.jdi.rmi.jpda;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.sun.jdi.connect.AttachingConnector;
|
|
||||||
import com.sun.jdi.connect.Connector;
|
import com.sun.jdi.connect.Connector;
|
||||||
import com.sun.jdi.connect.Connector.Argument;
|
import com.sun.jdi.connect.Connector.Argument;
|
||||||
|
|
||||||
@ -26,70 +25,31 @@ import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
|
|||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class JdiClientThread extends Thread {
|
public class JdiClientThread extends Thread {
|
||||||
enum Mode {
|
|
||||||
ATTACH_PORT, ATTACH_PID, LAUNCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, String> env;
|
private final Map<String, String> env;
|
||||||
private final Mode mode;
|
private final JdiArguments arguments;
|
||||||
|
|
||||||
private JdiManagerImpl manager;
|
private JdiManagerImpl manager;
|
||||||
private TraceJdiManager traceJdiManager;
|
private JdiManager jdiManager;
|
||||||
|
|
||||||
public JdiClientThread(Map<String, String> env) {
|
public JdiClientThread(Map<String, String> env) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.mode = computeMode();
|
this.arguments = new JdiArguments(env);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute/detect the launch mode using the environment map.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* It'd be nice if this were selected/specified in the script body, rather than by what options
|
|
||||||
* are present in its header. The reason we can't, though, is that the JDI client thread needs
|
|
||||||
* to also work within Ghidra's JVM, i.e., without launching a jshell subprocess. By far, the
|
|
||||||
* simplest way to accomplish this is to keep all the logic here, and just pass the environment
|
|
||||||
* map in. For the jshell-subprocess case, it's the environment map proper. For the
|
|
||||||
* in-Ghidra's-VM case, it's the map we would have passed when creating the subprocess.
|
|
||||||
*
|
|
||||||
* @return the mode.
|
|
||||||
*/
|
|
||||||
Mode computeMode() {
|
|
||||||
if (env.containsKey("OPT_PORT")) {
|
|
||||||
return Mode.ATTACH_PORT;
|
|
||||||
}
|
|
||||||
if (env.containsKey("OPT_PID")) {
|
|
||||||
return Mode.ATTACH_PID;
|
|
||||||
}
|
|
||||||
return Mode.LAUNCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
AttachingConnector findConnectorByArgKey(String key) {
|
|
||||||
return manager.getVirtualMachineManager()
|
|
||||||
.attachingConnectors()
|
|
||||||
.stream()
|
|
||||||
.filter(ac -> ac.defaultArguments().containsKey(key))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
manager = new JdiManagerImpl();
|
manager = new JdiManagerImpl();
|
||||||
traceJdiManager = new TraceJdiManager(manager, env);
|
jdiManager = new JdiManager(manager, env);
|
||||||
|
|
||||||
Connector cx = switch (mode) {
|
Connector cx = arguments.getConnector(manager.getVirtualMachineManager());
|
||||||
case ATTACH_PORT -> findConnectorByArgKey("port");
|
|
||||||
case ATTACH_PID -> findConnectorByArgKey("pid");
|
|
||||||
case LAUNCH -> manager.getVirtualMachineManager().defaultConnector();
|
|
||||||
};
|
|
||||||
|
|
||||||
Map<String, Argument> args = cx.defaultArguments();
|
Map<String, Argument> args = cx.defaultArguments();
|
||||||
putArguments(args);
|
arguments.putArguments(args);
|
||||||
if (manager.addVM(cx, args) != null) {
|
if (manager.addVM(cx, args) != null) {
|
||||||
traceJdiManager.getCommands().ghidraTraceSyncEnable();
|
jdiManager.getCommands().ghidraTraceSyncEnable();
|
||||||
traceJdiManager.getHooks().vmStarted(null, Causes.UNCLAIMED);
|
jdiManager.getHooks().vmStarted(null, Causes.UNCLAIMED);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Nothing. addVM should already have reported the error.
|
// Nothing. addVM should already have reported the error.
|
||||||
@ -100,30 +60,11 @@ public class JdiClientThread extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void putArguments(Map<String, Argument> args) {
|
public JdiManager mgr() {
|
||||||
switch (mode) {
|
return jdiManager;
|
||||||
case ATTACH_PORT -> {
|
|
||||||
args.get("hostname").setValue(env.get("OPT_HOST").toString());
|
|
||||||
args.get("port").setValue(env.get("OPT_PORT").toString());
|
|
||||||
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
|
|
||||||
}
|
|
||||||
case ATTACH_PID -> {
|
|
||||||
args.get("pid").setValue(env.get("OPT_PID").toString());
|
|
||||||
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
|
|
||||||
}
|
|
||||||
case LAUNCH -> {
|
|
||||||
args.get("main").setValue(env.get("OPT_TARGET_CLASS"));
|
|
||||||
//args.get("suspend").setValue(env.get("OPT_SUSPEND"));
|
|
||||||
args.get("includevirtualthreads").setValue(env.get("OPT_INCLUDE"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceJdiManager mgr() {
|
public JdiCommands cmds() {
|
||||||
return traceJdiManager;
|
return jdiManager.getCommands();
|
||||||
}
|
|
||||||
|
|
||||||
public TraceJdiCommands cmds() {
|
|
||||||
return traceJdiManager.getCommands();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -15,47 +15,35 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.dbg.jdi.rmi.jpda;
|
package ghidra.dbg.jdi.rmi.jpda;
|
||||||
|
|
||||||
|
import static ghidra.dbg.jdi.rmi.jpda.JdiManager.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import com.sun.jdi.*;
|
import com.sun.jdi.*;
|
||||||
import com.sun.jdi.event.*;
|
import com.sun.jdi.event.*;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiTrace;
|
import ghidra.app.plugin.core.debug.client.tracermi.*;
|
||||||
import ghidra.app.plugin.core.debug.client.tracermi.RmiTransaction;
|
|
||||||
import ghidra.dbg.jdi.manager.*;
|
import ghidra.dbg.jdi.manager.*;
|
||||||
import ghidra.dbg.jdi.manager.impl.DebugStatus;
|
import ghidra.dbg.jdi.manager.impl.DebugStatus;
|
||||||
import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
|
import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
|
||||||
|
|
||||||
class HookState {
|
class HookState {
|
||||||
|
|
||||||
private TraceJdiCommands cmds;
|
private JdiCommands cmds;
|
||||||
private Object batch;
|
|
||||||
|
|
||||||
public HookState(TraceJdiCommands cmds) {
|
public HookState(JdiCommands cmds) {
|
||||||
this.cmds = cmds;
|
this.cmds = cmds;
|
||||||
this.batch = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ensureBatch() {
|
public RmiBatch batch() {
|
||||||
if (batch == null) {
|
return cmds.state.client.startBatch();
|
||||||
batch = cmds.state.client.startBatch();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void endBatch() {
|
|
||||||
if (batch == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
batch = null;
|
|
||||||
cmds.state.client.endBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class VmState {
|
class VmState {
|
||||||
|
|
||||||
private TraceJdiManager manager;
|
private JdiManager manager;
|
||||||
private TraceJdiCommands cmds;
|
private JdiCommands cmds;
|
||||||
private boolean firstPass;
|
private boolean firstPass;
|
||||||
boolean classes;
|
boolean classes;
|
||||||
boolean modules;
|
boolean modules;
|
||||||
@ -65,7 +53,7 @@ class VmState {
|
|||||||
boolean events;
|
boolean events;
|
||||||
Set<Object> visited;
|
Set<Object> visited;
|
||||||
|
|
||||||
public VmState(TraceJdiManager manager) {
|
public VmState(JdiManager manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.cmds = manager.getCommands();
|
this.cmds = manager.getCommands();
|
||||||
this.firstPass = true;
|
this.firstPass = true;
|
||||||
@ -109,11 +97,16 @@ class VmState {
|
|||||||
}
|
}
|
||||||
StackFrame frame = manager.getJdi().getCurrentFrame();
|
StackFrame frame = manager.getJdi().getCurrentFrame();
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
|
try {
|
||||||
if (first || !visited.contains(frame)) {
|
if (first || !visited.contains(frame)) {
|
||||||
cmds.putReg(frame);
|
cmds.putReg(frame);
|
||||||
visited.add(frame);
|
visited.add(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (InvalidStackFrameException e) {
|
||||||
|
manager.getJdi().setCurrentFrame(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classes) {
|
if (classes) {
|
||||||
@ -154,7 +147,7 @@ class VmState {
|
|||||||
Process proc = vm.process();
|
Process proc = vm.process();
|
||||||
String path = cmds.getPath(proc);
|
String path = cmds.getPath(proc);
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
cmds.setValue(path, "Alive", proc.isAlive());
|
cmds.setValue(path, ATTR_ALIVE, proc.isAlive());
|
||||||
}
|
}
|
||||||
setState(vm);
|
setState(vm);
|
||||||
}
|
}
|
||||||
@ -168,8 +161,8 @@ class VmState {
|
|||||||
if (process != null) {
|
if (process != null) {
|
||||||
exitCode = process.exitValue();
|
exitCode = process.exitValue();
|
||||||
String procpath = cmds.getPath(vm.process());
|
String procpath = cmds.getPath(vm.process());
|
||||||
cmds.setValue(procpath, "ExitCode", exitCode);
|
cmds.setValue(procpath, ATTR_EXIT_CODE, exitCode);
|
||||||
cmds.setValue(procpath, TraceJdiManager.STATE_ATTRIBUTE_NAME, "TERMINATED");
|
cmds.setValue(procpath, ATTR_STATE, "TERMINATED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IllegalThreadStateException e) {
|
catch (IllegalThreadStateException e) {
|
||||||
@ -178,25 +171,25 @@ class VmState {
|
|||||||
if (description != null) {
|
if (description != null) {
|
||||||
cmds.state.trace.snapshot(description, "", null);
|
cmds.state.trace.snapshot(description, "", null);
|
||||||
}
|
}
|
||||||
cmds.setValue(path, "ExitCode", exitCode);
|
cmds.setValue(path, ATTR_EXIT_CODE, exitCode);
|
||||||
cmds.setValue(path, TraceJdiManager.STATE_ATTRIBUTE_NAME, "TERMINATED");
|
cmds.setValue(path, ATTR_STATE, "TERMINATED");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TraceJdiHooks implements JdiEventsListenerAdapter {
|
public class JdiHooks implements JdiEventsListenerAdapter {
|
||||||
|
|
||||||
private TraceJdiManager manager;
|
private JdiManager manager;
|
||||||
private TraceJdiCommands cmds;
|
private JdiCommands cmds;
|
||||||
private HookState hookState;
|
private HookState hookState;
|
||||||
private Map<VirtualMachine, VmState> vmStates = new HashMap<>();
|
private Map<VirtualMachine, VmState> vmStates = new HashMap<>();
|
||||||
|
|
||||||
public TraceJdiHooks(TraceJdiManager manager) {
|
public JdiHooks(JdiManager manager, JdiCommands cmds) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.cmds = manager.getCommands();
|
this.cmds = cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCommands(TraceJdiCommands commands) {
|
private void setCommands(JdiCommands commands) {
|
||||||
this.cmds = commands;
|
this.cmds = commands;
|
||||||
hookState = new HookState(commands);
|
hookState = new HookState(commands);
|
||||||
}
|
}
|
||||||
@ -204,17 +197,19 @@ public class TraceJdiHooks implements JdiEventsListenerAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public DebugStatus vmStarted(VMStartEvent event, JdiCause cause) {
|
public DebugStatus vmStarted(VMStartEvent event, JdiCause cause) {
|
||||||
setCommands(manager.getCommands());
|
setCommands(manager.getCommands());
|
||||||
hookState.ensureBatch();
|
|
||||||
RmiTrace trace = cmds.state.trace;
|
|
||||||
JdiManagerImpl jdi = manager.getJdi();
|
JdiManagerImpl jdi = manager.getJdi();
|
||||||
VirtualMachine vm = event == null ? jdi.getCurrentVM() : event.virtualMachine();
|
VirtualMachine vm = event == null ? jdi.getCurrentVM() : event.virtualMachine();
|
||||||
try (RmiTransaction tx = trace.openTx("New VM " + vm.description())) {
|
|
||||||
jdi.setCurrentVM(vm);
|
jdi.setCurrentVM(vm);
|
||||||
jdi.addVM(vm);
|
jdi.addVM(vm);
|
||||||
|
RmiTrace trace = cmds.state.trace;
|
||||||
|
if (trace == null) {
|
||||||
|
return DebugStatus.NO_CHANGE;
|
||||||
|
}
|
||||||
|
try (RmiBatch batch = hookState.batch();
|
||||||
|
RmiTransaction tx = trace.openTx("New VM " + vm.description())) {
|
||||||
cmds.putVMs();
|
cmds.putVMs();
|
||||||
enableCurrentVM();
|
enableCurrentVM();
|
||||||
}
|
}
|
||||||
hookState.endBatch();
|
|
||||||
return DebugStatus.NO_CHANGE;
|
return DebugStatus.NO_CHANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,12 +412,11 @@ public class TraceJdiHooks implements JdiEventsListenerAdapter {
|
|||||||
}
|
}
|
||||||
VmState state = vmStates.get(vm);
|
VmState state = vmStates.get(vm);
|
||||||
state.visited.clear();
|
state.visited.clear();
|
||||||
hookState.ensureBatch();
|
try (RmiBatch batch = hookState.batch();
|
||||||
try (RmiTransaction tx = trace.openTx("Stopped")) {
|
RmiTransaction tx = trace.openTx("Stopped")) {
|
||||||
state.recordState("Stopped");
|
state.recordState("Stopped");
|
||||||
cmds.activate(null);
|
cmds.activate(null);
|
||||||
}
|
}
|
||||||
hookState.endBatch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCurrent(Event event) {
|
private void setCurrent(Event event) {
|
||||||
@ -446,12 +440,11 @@ public class TraceJdiHooks implements JdiEventsListenerAdapter {
|
|||||||
void onContinue() {
|
void onContinue() {
|
||||||
VirtualMachine currentVM = manager.getJdi().getCurrentVM();
|
VirtualMachine currentVM = manager.getJdi().getCurrentVM();
|
||||||
VmState state = vmStates.get(currentVM);
|
VmState state = vmStates.get(currentVM);
|
||||||
hookState.ensureBatch();
|
try (RmiBatch batch = hookState.batch();
|
||||||
try (RmiTransaction tx = cmds.state.trace.openTx("Continue")) {
|
RmiTransaction tx = cmds.state.trace.openTx("Continue")) {
|
||||||
state.recordStateContinued(currentVM);
|
state.recordStateContinued(currentVM);
|
||||||
cmds.activate(null);
|
cmds.activate(null);
|
||||||
}
|
}
|
||||||
hookState.endBatch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installHooks() {
|
public void installHooks() {
|
@ -21,6 +21,7 @@ import java.util.Map;
|
|||||||
import com.sun.jdi.*;
|
import com.sun.jdi.*;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.client.tracermi.*;
|
import ghidra.app.plugin.core.debug.client.tracermi.*;
|
||||||
|
import ghidra.app.plugin.core.debug.client.tracermi.RmiMethodRegistry.TraceMethod;
|
||||||
import ghidra.dbg.jdi.manager.impl.DebugStatus;
|
import ghidra.dbg.jdi.manager.impl.DebugStatus;
|
||||||
import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
|
import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
|
||||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||||
@ -28,65 +29,87 @@ import ghidra.dbg.target.schema.TargetObjectSchema;
|
|||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class TraceJdiManager {
|
public class JdiManager {
|
||||||
|
|
||||||
private static final int STATIC_METHOD_SEPARATION = 3;
|
private static final int STATIC_METHOD_SEPARATION = 3;
|
||||||
public static final long BLOCK_SIZE = 0x1000L;
|
public static final long BLOCK_SIZE = 0x1000L;
|
||||||
public static final long DEFAULT_SECTION = 0x0000L;
|
public static final long DEFAULT_SECTION = 0x0000L;
|
||||||
|
|
||||||
public static final String PREFIX_INVISIBLE = "_";
|
public static final String ATTR_DISPLAY = "_display";
|
||||||
public static final String DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "display";
|
public static final String ATTR_STATE = "_state";
|
||||||
public static final String STATE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "state";
|
public static final String ATTR_MODULE_NAME = "_module_name";
|
||||||
public static final String MODULE_NAME_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "module_name";
|
public static final String ATTR_ARCH = "_arch";
|
||||||
public static final String ARCH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "arch";
|
public static final String ATTR_DEBUGGER = "_debugger";
|
||||||
public static final String DEBUGGER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "debugger";
|
public static final String ATTR_OS = "_os";
|
||||||
public static final String OS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "os";
|
public static final String ATTR_ENDIAN = "_endian";
|
||||||
public static final String ENDIAN_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "endian";
|
public static final String ATTR_ACCESSIBLE = "_accessible";
|
||||||
public static final String ACCESSIBLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "accessible";
|
public static final String ATTR_ADDRESS = "Address";
|
||||||
|
public static final String ATTR_ALIVE = "Alive";
|
||||||
|
public static final String ATTR_CLASS = "Class";
|
||||||
|
public static final String ATTR_COMMAND_LINE = "CommandLine";
|
||||||
|
public static final String ATTR_COUNT = "Count";
|
||||||
|
public static final String ATTR_ENABLED = "Enabled";
|
||||||
|
public static final String ATTR_EXECUTABLE = "Executable";
|
||||||
|
public static final String ATTR_EXIT_CODE = "ExitCode";
|
||||||
|
public static final String ATTR_INDEX = "Index";
|
||||||
|
public static final String ATTR_INSTANCE = "Instance";
|
||||||
|
public static final String ATTR_LENGTH = "Length";
|
||||||
|
public static final String ATTR_LINENO = "LineNo";
|
||||||
|
public static final String ATTR_LOCATION = "Location";
|
||||||
|
public static final String ATTR_NAME = "Name";
|
||||||
|
public static final String ATTR_PC = "PC";
|
||||||
|
public static final String ATTR_PLATFORM_ONLY = "PlatformOnly";
|
||||||
|
public static final String ATTR_RANGE = "Range";
|
||||||
|
public static final String ATTR_RANGE_CP = "RangeCP"; // Constant pool
|
||||||
|
public static final String ATTR_SIGNATURE = "Signature";
|
||||||
|
public static final String ATTR_THREAD = "Thread";
|
||||||
|
public static final String ATTR_TYPE = "Type";
|
||||||
|
public static final String ATTR_VALUE = "Value";
|
||||||
|
public static final String ATTR_EXCLUDE = "Exclude";
|
||||||
|
public static final String ATTR_INCLUDE = "Include";
|
||||||
|
|
||||||
private JdiManagerImpl manager;
|
private final JdiManagerImpl manager;
|
||||||
private TraceJdiArch arch;
|
private final JdiArch arch;
|
||||||
private TraceJdiHooks hooks;
|
private final JdiHooks hooks;
|
||||||
private TraceJdiMethods methods;
|
private final JdiMethods methods;
|
||||||
private TraceJdiCommands commands;
|
private final JdiCommands commands;
|
||||||
|
|
||||||
Map<String, Object> objectRegistry = new HashMap<>();
|
final Map<String, Object> objectRegistry = new HashMap<>();
|
||||||
Map<Object, String> reverseRegistry = new HashMap<>();
|
final Map<Object, String> reverseRegistry = new HashMap<>();
|
||||||
RmiMethodRegistry remoteMethodRegistry = new RmiMethodRegistry();
|
final RmiMethodRegistry remoteMethodRegistry = new RmiMethodRegistry();
|
||||||
Map<Object, Boolean> scopeRegistry = new HashMap<>();
|
final Map<Object, Boolean> scopeRegistry = new HashMap<>();
|
||||||
|
|
||||||
protected final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0);
|
protected final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0);
|
||||||
protected Long ramIndex = Long.valueOf(BLOCK_SIZE);
|
protected Long ramIndex = BLOCK_SIZE;
|
||||||
protected final AddressSpace pool =
|
protected final AddressSpace pool =
|
||||||
new GenericAddressSpace("constantPool", 64, AddressSpace.TYPE_RAM, 0);
|
new GenericAddressSpace("constantPool", 64, AddressSpace.TYPE_RAM, 0);
|
||||||
protected Long poolIndex = Long.valueOf(0x0L);
|
protected Long poolIndex = 0x0L;
|
||||||
public AddressRangeImpl defaultRange;
|
public final AddressRangeImpl defaultRange;
|
||||||
|
|
||||||
private Map<String, AddressRange> addressRangeByMethod = new HashMap<>();
|
private final Map<String, AddressRange> addressRangeByMethod = new HashMap<>();
|
||||||
private Map<String, Method> methodsByKey = new HashMap<>();
|
private final Map<String, Method> methodsByKey = new HashMap<>();
|
||||||
private Map<ReferenceType, AddressRange> addressRangeByClass = new HashMap<>();
|
private final Map<ReferenceType, AddressRange> addressRangeByClass = new HashMap<>();
|
||||||
private Map<ReferenceType, AddressRange> cpAddressRangeByClass = new HashMap<>();
|
private final Map<ReferenceType, AddressRange> cpAddressRangeByClass = new HashMap<>();
|
||||||
|
|
||||||
private Map<String, DebugStatus> returnStatusMap = new HashMap<>();
|
private final Map<String, DebugStatus> returnStatusMap = new HashMap<>();
|
||||||
TargetObjectSchema rootSchema;
|
final TargetObjectSchema rootSchema;
|
||||||
|
|
||||||
public TraceJdiManager(JdiManagerImpl manager, Map<String, String> env) {
|
public JdiManager(JdiManagerImpl manager, Map<String, String> env) {
|
||||||
this(manager);
|
this(manager);
|
||||||
commands.ghidraTraceConnect(env.get("GHIDRA_TRACE_RMI_ADDR"));
|
commands.ghidraTraceConnect(env.get("GHIDRA_TRACE_RMI_ADDR"));
|
||||||
commands.ghidraTraceStart(env.get("OPT_TARGET_CLASS"));
|
commands.ghidraTraceStart(env.get("OPT_TARGET_CLASS"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NB: Needed for testing
|
public JdiManager(JdiManagerImpl manager) {
|
||||||
public TraceJdiManager(JdiManagerImpl manager) {
|
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
Address start = ram.getAddress(DEFAULT_SECTION);
|
Address start = ram.getAddress(DEFAULT_SECTION);
|
||||||
defaultRange = new AddressRangeImpl(start, start.add(BLOCK_SIZE - 1));
|
defaultRange = new AddressRangeImpl(start, start.add(BLOCK_SIZE - 1));
|
||||||
rootSchema = RmiClient.loadSchema("jdi_schema.xml", "Debugger");
|
rootSchema = RmiClient.loadSchema("jdi_schema.xml", "Debugger");
|
||||||
|
|
||||||
arch = new TraceJdiArch();
|
arch = new JdiArch();
|
||||||
commands = new TraceJdiCommands(this); // Must precede methods/hooks
|
commands = new JdiCommands(this); // Must precede methods/hooks
|
||||||
methods = new TraceJdiMethods(this);
|
methods = new JdiMethods(this, commands);
|
||||||
hooks = new TraceJdiHooks(this);
|
hooks = new JdiHooks(this, commands);
|
||||||
hooks.installHooks();
|
hooks.installHooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,19 +117,19 @@ public class TraceJdiManager {
|
|||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceJdiArch getArch() {
|
public JdiArch getArch() {
|
||||||
return arch;
|
return arch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceJdiCommands getCommands() {
|
public JdiCommands getCommands() {
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceJdiMethods getMethods() {
|
public JdiMethods getMethods() {
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceJdiHooks getHooks() {
|
public JdiHooks getHooks() {
|
||||||
return hooks;
|
return hooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,12 +137,11 @@ public class TraceJdiManager {
|
|||||||
return commands.state.client;
|
return commands.state.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRemoteMethod(TraceJdiMethods methods, java.lang.reflect.Method m,
|
public void registerRemoteMethod(JdiMethods methods, java.lang.reflect.Method m, String name) {
|
||||||
String name) {
|
|
||||||
String action = name;
|
String action = name;
|
||||||
String display = name;
|
String display = name;
|
||||||
String description = name;
|
String description = name;
|
||||||
RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class);
|
TraceMethod annot = m.getAnnotation(TraceMethod.class);
|
||||||
if (annot == null) {
|
if (annot == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -134,6 +156,10 @@ public class TraceJdiManager {
|
|||||||
if (pcount < 1) {
|
if (pcount < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* TODO: The return type should be reflected from the method; however, none of the parameter
|
||||||
|
* collection routines currently use the return type, so just use ANY for now.
|
||||||
|
*/
|
||||||
TargetObjectSchema schema = EnumerableTargetObjectSchema.ANY;
|
TargetObjectSchema schema = EnumerableTargetObjectSchema.ANY;
|
||||||
RmiRemoteMethod method = new RmiRemoteMethod(rootSchema.getContext(), name, action, display,
|
RmiRemoteMethod method = new RmiRemoteMethod(rootSchema.getContext(), name, action, display,
|
||||||
description, schema, methods, m);
|
description, schema, methods, m);
|
||||||
@ -152,6 +178,7 @@ public class TraceJdiManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AddressRange putAddressRange(ReferenceType cls, AddressSet set) {
|
public AddressRange putAddressRange(ReferenceType cls, AddressSet set) {
|
||||||
|
synchronized (addressRangeByClass) {
|
||||||
if (set.isEmpty()) {
|
if (set.isEmpty()) {
|
||||||
addressRangeByClass.put(cls, defaultRange);
|
addressRangeByClass.put(cls, defaultRange);
|
||||||
return defaultRange;
|
return defaultRange;
|
||||||
@ -160,21 +187,26 @@ public class TraceJdiManager {
|
|||||||
addressRangeByClass.put(cls, range);
|
addressRangeByClass.put(cls, range);
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AddressRange getAddressRange(ReferenceType cls) {
|
public AddressRange getAddressRange(ReferenceType cls) {
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
return defaultRange;
|
return defaultRange;
|
||||||
}
|
}
|
||||||
|
synchronized (addressRangeByClass) {
|
||||||
return addressRangeByClass.get(cls);
|
return addressRangeByClass.get(cls);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ReferenceType getReferenceTypeForAddress(Address address) {
|
public ReferenceType getReferenceTypeForAddress(Address address) {
|
||||||
|
synchronized (addressRangeByClass) {
|
||||||
for (ReferenceType ref : addressRangeByClass.keySet()) {
|
for (ReferenceType ref : addressRangeByClass.keySet()) {
|
||||||
AddressRange range = addressRangeByClass.get(ref);
|
AddressRange range = addressRangeByClass.get(ref);
|
||||||
if (range.contains(address)) {
|
if (range.contains(address)) {
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +214,7 @@ public class TraceJdiManager {
|
|||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
return defaultRange;
|
return defaultRange;
|
||||||
}
|
}
|
||||||
|
synchronized (cpAddressRangeByClass) {
|
||||||
AddressRange range = cpAddressRangeByClass.get(cls);
|
AddressRange range = cpAddressRangeByClass.get(cls);
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
registerConstantPool(cls, sz);
|
registerConstantPool(cls, sz);
|
||||||
@ -189,12 +222,13 @@ public class TraceJdiManager {
|
|||||||
}
|
}
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void registerConstantPool(ReferenceType declaringType, int sz) {
|
public void registerConstantPool(ReferenceType declaringType, int sz) {
|
||||||
|
synchronized (cpAddressRangeByClass) {
|
||||||
if (!cpAddressRangeByClass.containsKey(declaringType)) {
|
if (!cpAddressRangeByClass.containsKey(declaringType)) {
|
||||||
if (manager.getCurrentVM().canGetConstantPool()) {
|
if (manager.getCurrentVM().canGetConstantPool()) {
|
||||||
long length = sz == 0 ? 2 : sz;
|
long length = sz == 0 ? 2 : sz;
|
||||||
synchronized (cpAddressRangeByClass) {
|
|
||||||
Address start = pool.getAddress(poolIndex);
|
Address start = pool.getAddress(poolIndex);
|
||||||
AddressRangeImpl range =
|
AddressRangeImpl range =
|
||||||
new AddressRangeImpl(start, start.add(length - 1));
|
new AddressRangeImpl(start, start.add(length - 1));
|
||||||
@ -208,12 +242,14 @@ public class TraceJdiManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ReferenceType getReferenceTypeForPoolAddress(Address address) {
|
public ReferenceType getReferenceTypeForPoolAddress(Address address) {
|
||||||
|
synchronized (cpAddressRangeByClass) {
|
||||||
for (ReferenceType ref : cpAddressRangeByClass.keySet()) {
|
for (ReferenceType ref : cpAddressRangeByClass.keySet()) {
|
||||||
AddressRange range = cpAddressRangeByClass.get(ref);
|
AddressRange range = cpAddressRangeByClass.get(ref);
|
||||||
if (range.contains(address)) {
|
if (range.contains(address)) {
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,14 +257,17 @@ public class TraceJdiManager {
|
|||||||
if (method == null) {
|
if (method == null) {
|
||||||
return defaultRange;
|
return defaultRange;
|
||||||
}
|
}
|
||||||
|
synchronized (addressRangeByMethod) {
|
||||||
AddressRange range = addressRangeByMethod.get(methodToKey(method));
|
AddressRange range = addressRangeByMethod.get(methodToKey(method));
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
return defaultRange;
|
return defaultRange;
|
||||||
}
|
}
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Method getMethodForAddress(Address address) {
|
public Method getMethodForAddress(Address address) {
|
||||||
|
synchronized (addressRangeByMethod) {
|
||||||
for (String methodName : addressRangeByMethod.keySet()) {
|
for (String methodName : addressRangeByMethod.keySet()) {
|
||||||
AddressRange range = addressRangeByMethod.get(methodName);
|
AddressRange range = addressRangeByMethod.get(methodName);
|
||||||
if (range.contains(address)) {
|
if (range.contains(address)) {
|
||||||
@ -237,6 +276,7 @@ public class TraceJdiManager {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Address getAddressFromLocation(Location location) {
|
public Address getAddressFromLocation(Location location) {
|
||||||
AddressRange addressRange = getAddressRange(location.method());
|
AddressRange addressRange = getAddressRange(location.method());
|
||||||
@ -364,5 +404,4 @@ public class TraceJdiManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -162,7 +162,7 @@
|
|||||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Events" schema="EventContainer" required="yes" fixed="yes" />
|
<attribute name="Events" schema="EventContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Classes" schema="ReferenceTypeContainer" required="yes" fixed="yes" />
|
<attribute name="Classes" schema="CanonicalReferenceTypeContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
|
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
|
||||||
<attribute name="ModuleRefs" schema="ModuleReferenceContainer" required="yes" fixed="yes" />
|
<attribute name="ModuleRefs" schema="ModuleReferenceContainer" required="yes" fixed="yes" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
@ -361,7 +361,7 @@
|
|||||||
<attribute name="_order" schema="INT" hidden="yes" />
|
<attribute name="_order" schema="INT" hidden="yes" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="Event" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="Event" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<interface name="Deletable" />
|
<interface name="Deletable" />
|
||||||
<element schema="OBJECT" />
|
<element schema="OBJECT" />
|
||||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||||
@ -483,13 +483,32 @@
|
|||||||
<attribute name="Virtual" schema="OBJECT" />
|
<attribute name="Virtual" schema="OBJECT" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="ReferenceTypeContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="CanonicalReferenceTypeContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||||
|
<element schema="CanonicalReferenceType" />
|
||||||
|
<attribute schema="ANY" />
|
||||||
|
</schema>
|
||||||
|
<schema name="ReferenceTypeContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<element schema="ReferenceType" />
|
<element schema="ReferenceType" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="ReferenceTypeProxy" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="ReferenceTypeProxy" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<element schema="ReferenceType" />
|
<element schema="ReferenceType" />
|
||||||
</schema>
|
</schema>
|
||||||
|
<schema name="CanonicalReferenceType" elementResync="NEVER" attributeResync="ALWAYS">
|
||||||
|
<interface name="Module" />
|
||||||
|
<interface name="Aggregate" />
|
||||||
|
<element schema="ANY" />
|
||||||
|
<attribute name="Fields" schema="CanonicalFieldContainer" fixed="yes" />
|
||||||
|
<attribute name="Instances" schema="ObjectReferenceContainer" fixed="yes" />
|
||||||
|
<attribute name="Locations" schema="LocationContainer" fixed="yes" />
|
||||||
|
<attribute name="Methods" schema="CanonicalMethodContainer" required="no" fixed="yes" />
|
||||||
|
<attribute name="Relations" schema="ReferenceTypeRelations" required="no" fixed="yes" />
|
||||||
|
<attribute name="_module_name" schema="STRING" hidden="yes" />
|
||||||
|
<attribute name="Range" schema="RANGE" />
|
||||||
|
<attribute name="RangeCP" schema="RANGE" />
|
||||||
|
<attribute-alias from="_range" to="Range" />
|
||||||
|
<attribute schema="ANY" />
|
||||||
|
</schema>
|
||||||
<schema name="ReferenceType" elementResync="NEVER" attributeResync="ALWAYS">
|
<schema name="ReferenceType" elementResync="NEVER" attributeResync="ALWAYS">
|
||||||
<interface name="Module" />
|
<interface name="Module" />
|
||||||
<interface name="Aggregate" />
|
<interface name="Aggregate" />
|
||||||
@ -519,7 +538,11 @@
|
|||||||
<attribute name="SuperInterfaces" schema="ReferenceTypeContainer" fixed="yes" />
|
<attribute name="SuperInterfaces" schema="ReferenceTypeContainer" fixed="yes" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="FieldContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="CanonicalFieldContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||||
|
<element schema="Field" />
|
||||||
|
<attribute schema="ANY" />
|
||||||
|
</schema>
|
||||||
|
<schema name="FieldContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<element schema="Field" />
|
<element schema="Field" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
@ -529,7 +552,12 @@
|
|||||||
<attribute name="Value" schema="Value" required="no" />
|
<attribute name="Value" schema="Value" required="no" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="MethodContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="CanonicalMethodContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||||
|
<interface name="SectionContainer" />
|
||||||
|
<element schema="Method" />
|
||||||
|
<attribute schema="ANY" />
|
||||||
|
</schema>
|
||||||
|
<schema name="MethodContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<interface name="SectionContainer" />
|
<interface name="SectionContainer" />
|
||||||
<element schema="Method" />
|
<element schema="Method" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
@ -547,7 +575,7 @@
|
|||||||
<attribute name="Locations" schema="LocationContainer" fixed="yes" />
|
<attribute name="Locations" schema="LocationContainer" fixed="yes" />
|
||||||
<attribute name="Range" schema="RANGE" />
|
<attribute name="Range" schema="RANGE" />
|
||||||
<attribute-alias from="_range" to="Range" />
|
<attribute-alias from="_range" to="Range" />
|
||||||
<attribute name="Variables" schema="VariableContainer" fixed="yes" />
|
<attribute name="Variables" schema="CanonicalVariableContainer" fixed="yes" />
|
||||||
<attribute name="_display" schema="STRING" fixed="yes" hidden="yes" />
|
<attribute name="_display" schema="STRING" fixed="yes" hidden="yes" />
|
||||||
<attribute name="_return_type" schema="TYPE" fixed="yes" hidden="yes" />
|
<attribute name="_return_type" schema="TYPE" fixed="yes" hidden="yes" />
|
||||||
<attribute name="_parameters" schema="MAP_PARAMETERS" fixed="yes" hidden="yes" />
|
<attribute name="_parameters" schema="MAP_PARAMETERS" fixed="yes" hidden="yes" />
|
||||||
@ -557,7 +585,11 @@
|
|||||||
<element schema="Type" />
|
<element schema="Type" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="VariableContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="CanonicalVariableContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||||
|
<element schema="Variable" />
|
||||||
|
<attribute schema="ANY" />
|
||||||
|
</schema>
|
||||||
|
<schema name="VariableContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<element schema="Variable" />
|
<element schema="Variable" />
|
||||||
<attribute schema="ANY" />
|
<attribute schema="ANY" />
|
||||||
</schema>
|
</schema>
|
||||||
|
@ -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>
|
<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
|
<P>This launcher uses the native Java Debug Interface (JDI) to launch the current
|
||||||
<TT>.class</TT> file.</P>
|
<TT>.class</TT> file.</P>
|
||||||
|
@ -77,4 +77,14 @@ public class ProtobufSocket<T extends AbstractMessage> {
|
|||||||
Msg.error(this, "Unable to close ProtobufSocket");
|
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;
|
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.ArrayList;
|
||||||
import java.util.Set;
|
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 final RmiClient client;
|
||||||
private Set<Object> futures = new HashSet<>();
|
private volatile int refCount = 0;
|
||||||
|
private final List<RequestResult> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
public RmiBatch(RmiClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
public void inc() {
|
public void inc() {
|
||||||
refCount++;
|
refCount++;
|
||||||
@ -32,13 +39,34 @@ public class RmiBatch {
|
|||||||
return --refCount;
|
return --refCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void append(Object f) {
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
client.endBatch(this);
|
||||||
|
}
|
||||||
|
catch (InterruptedException | ExecutionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append(RequestResult f) {
|
||||||
|
synchronized (futures) {
|
||||||
futures.add(f);
|
futures.add(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object results() {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Parameter;
|
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import org.jdom.JDOMException;
|
import org.jdom.JDOMException;
|
||||||
|
|
||||||
@ -37,18 +37,51 @@ import ghidra.rmi.trace.TraceRmi.Language;
|
|||||||
import ghidra.rmi.trace.TraceRmi.Value.Builder;
|
import ghidra.rmi.trace.TraceRmi.Value.Builder;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
public class RmiClient {
|
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 ProtobufSocket<RootMessage> socket;
|
||||||
private final String description;
|
private final String description;
|
||||||
private int nextTraceId = 0;
|
private int nextTraceId = 0;
|
||||||
private RmiBatch currentBatch = null;
|
private volatile RmiBatch currentBatch = null;
|
||||||
|
|
||||||
Map<Integer, RmiTrace> traces = new HashMap<>();
|
Map<Integer, RmiTrace> traces = new HashMap<>();
|
||||||
private SchemaContext schemaContext;
|
private SchemaContext schemaContext;
|
||||||
private RmiMethodHandlerThread handler;
|
private RmiReplyHandlerThread handler;
|
||||||
private static RmiMethodRegistry methodRegistry;
|
private static RmiMethodRegistry methodRegistry;
|
||||||
private Deque<RootMessage> requests = new LinkedList<>();
|
private Deque<RequestResult> requests = new LinkedList<>();
|
||||||
|
|
||||||
public static TargetObjectSchema loadSchema(String resourceName, String rootName) {
|
public static TargetObjectSchema loadSchema(String resourceName, String rootName) {
|
||||||
XmlSchemaContext schemaContext;
|
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 {
|
public static enum TraceRmiResolution {
|
||||||
RES_ADJUST("adjust", Resolution.CR_ADJUST), //
|
RES_ADJUST("adjust", Resolution.CR_ADJUST),
|
||||||
RES_DENY("deny", Resolution.CR_DENY), //
|
RES_DENY("deny", Resolution.CR_DENY),
|
||||||
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE), //
|
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE),
|
||||||
;
|
;
|
||||||
|
|
||||||
TraceRmiResolution(String val, TraceRmi.Resolution description) {
|
TraceRmiResolution(String val, TraceRmi.Resolution description) {
|
||||||
@ -89,9 +113,9 @@ public class RmiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static enum TraceRmiValueKinds {
|
public static enum TraceRmiValueKinds {
|
||||||
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES), //
|
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES),
|
||||||
ELEMENTS("elements", ValueKinds.VK_ELEMENTS), //
|
ELEMENTS("elements", ValueKinds.VK_ELEMENTS),
|
||||||
BOTH("both", ValueKinds.VK_BOTH), //
|
BOTH("both", ValueKinds.VK_BOTH),
|
||||||
;
|
;
|
||||||
|
|
||||||
TraceRmiValueKinds(String val, TraceRmi.ValueKinds description) {
|
TraceRmiValueKinds(String val, TraceRmi.ValueKinds description) {
|
||||||
@ -106,16 +130,12 @@ public class RmiClient {
|
|||||||
public RmiClient(SocketChannel channel, String description) {
|
public RmiClient(SocketChannel channel, String description) {
|
||||||
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
|
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.handler = new RmiMethodHandlerThread(this, socket);
|
this.handler = new RmiReplyHandlerThread(this, socket);
|
||||||
handler.start();
|
handler.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtobufSocket<RootMessage> getSocket() {
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
return description + " at " + socket.getRemoteAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
@ -123,10 +143,18 @@ public class RmiClient {
|
|||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send(RootMessage msg) {
|
private RequestResult send(RootMessage msg) {
|
||||||
try {
|
try {
|
||||||
requests.push(msg);
|
RequestResult result = new RequestResult(msg);
|
||||||
|
synchronized (requests) {
|
||||||
socket.send(msg);
|
socket.send(msg);
|
||||||
|
requests.push(result);
|
||||||
|
}
|
||||||
|
RmiBatch cb = currentBatch;
|
||||||
|
if (cb != null) {
|
||||||
|
cb.append(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@ -137,12 +165,11 @@ public class RmiClient {
|
|||||||
if (compiler == null) {
|
if (compiler == null) {
|
||||||
compiler = new CompilerSpecID("default");
|
compiler = new CompilerSpecID("default");
|
||||||
}
|
}
|
||||||
RmiTrace trace = new RmiTrace(this, nextTraceId);
|
int traceId = nextTraceId++;
|
||||||
traces.put(nextTraceId, trace);
|
RequestResult result = send(RootMessage.newBuilder()
|
||||||
send(RootMessage.newBuilder()
|
|
||||||
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
|
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(nextTraceId++))
|
.setId(traceId))
|
||||||
.setLanguage(Language.newBuilder()
|
.setLanguage(Language.newBuilder()
|
||||||
.setId(language.getIdAsString()))
|
.setId(language.getIdAsString()))
|
||||||
.setCompiler(Compiler.newBuilder()
|
.setCompiler(Compiler.newBuilder()
|
||||||
@ -150,6 +177,8 @@ public class RmiClient {
|
|||||||
.setPath(FilePath.newBuilder()
|
.setPath(FilePath.newBuilder()
|
||||||
.setPath(path)))
|
.setPath(path)))
|
||||||
.build());
|
.build());
|
||||||
|
RmiTrace trace = new RmiTrace(this, traceId, result);
|
||||||
|
traces.put(traceId, trace);
|
||||||
return trace;
|
return trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +188,7 @@ public class RmiClient {
|
|||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(id)))
|
.setId(id)))
|
||||||
.build());
|
.build());
|
||||||
|
traces.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveTrace(int id) {
|
public void saveTrace(int id) {
|
||||||
@ -248,7 +278,7 @@ public class RmiClient {
|
|||||||
.setRange(AddrRange.newBuilder()
|
.setRange(AddrRange.newBuilder()
|
||||||
.setSpace(range.getAddressSpace().getName())
|
.setSpace(range.getAddressSpace().getName())
|
||||||
.setOffset(range.getMinAddress().getOffset())
|
.setOffset(range.getMinAddress().getOffset())
|
||||||
.setExtend(range.getLength())))
|
.setExtend(range.getLength() - 1)))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +309,7 @@ public class RmiClient {
|
|||||||
.setSpace(ppath);
|
.setSpace(ppath);
|
||||||
for (int i = 0; i < names.length; i++) {
|
for (int i = 0; i < names.length; i++) {
|
||||||
String name = names[i];
|
String name = names[i];
|
||||||
builder.setNames(i, name);
|
builder.addNames(name);
|
||||||
}
|
}
|
||||||
send(RootMessage.newBuilder()
|
send(RootMessage.newBuilder()
|
||||||
.setRequestDeleteRegisterValue(builder)
|
.setRequestDeleteRegisterValue(builder)
|
||||||
@ -298,9 +328,8 @@ public class RmiClient {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createObject(int traceId, String path) {
|
RequestResult createObject(int traceId, String path) {
|
||||||
//System.err.println("createObject:"+path);
|
return send(RootMessage.newBuilder()
|
||||||
send(RootMessage.newBuilder()
|
|
||||||
.setRequestCreateObject(RequestCreateObject.newBuilder()
|
.setRequestCreateObject(RequestCreateObject.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(traceId))
|
.setId(traceId))
|
||||||
@ -323,12 +352,13 @@ public class RmiClient {
|
|||||||
.build());
|
.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()
|
send(RootMessage.newBuilder()
|
||||||
.setRequestInsertObject(RequestInsertObject.newBuilder()
|
.setRequestInsertObject(RequestInsertObject.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(traceId))
|
.setId(traceId))
|
||||||
.setObject(object)
|
.setObject(ObjSpec.newBuilder()
|
||||||
|
.setId(id))
|
||||||
.setSpan(Span.newBuilder()
|
.setSpan(Span.newBuilder()
|
||||||
.setMin(span.lmin())
|
.setMin(span.lmin())
|
||||||
.setMax(span.lmax()))
|
.setMax(span.lmax()))
|
||||||
@ -336,12 +366,28 @@ public class RmiClient {
|
|||||||
.build());
|
.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()
|
send(RootMessage.newBuilder()
|
||||||
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
|
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(traceId))
|
.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()
|
.setSpan(Span.newBuilder()
|
||||||
.setMin(span.lmin())
|
.setMin(span.lmin())
|
||||||
.setMax(span.lmax()))
|
.setMax(span.lmax()))
|
||||||
@ -389,19 +435,18 @@ public class RmiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void getObject(int traceId, String path) {
|
public void getObject(int traceId, String path) {
|
||||||
RequestGetObject.Builder builder = RequestGetObject.newBuilder()
|
send(RootMessage.newBuilder()
|
||||||
|
.setRequestGetObject(RequestGetObject.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(traceId))
|
.setId(traceId))
|
||||||
.setObject(ObjSpec.newBuilder()
|
.setObject(ObjSpec.newBuilder()
|
||||||
.setPath(ObjPath.newBuilder()
|
.setPath(ObjPath.newBuilder()
|
||||||
.setPath(path)));
|
.setPath(path))))
|
||||||
send(RootMessage.newBuilder()
|
|
||||||
.setRequestGetObject(builder)
|
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getValues(int traceId, Lifespan span, String pattern) {
|
RequestResult getValues(int traceId, Lifespan span, String pattern) {
|
||||||
send(RootMessage.newBuilder()
|
return send(RootMessage.newBuilder()
|
||||||
.setRequestGetValues(RequestGetValues.newBuilder()
|
.setRequestGetValues(RequestGetValues.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(traceId))
|
.setId(traceId))
|
||||||
@ -412,9 +457,9 @@ public class RmiClient {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
|
RequestResult getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
|
||||||
String key) {
|
String key) {
|
||||||
send(RootMessage.newBuilder()
|
return send(RootMessage.newBuilder()
|
||||||
.setRequestGetValuesIntersecting(RequestGetValuesIntersecting.newBuilder()
|
.setRequestGetValuesIntersecting(RequestGetValuesIntersecting.newBuilder()
|
||||||
.setOid(DomObjId.newBuilder()
|
.setOid(DomObjId.newBuilder()
|
||||||
.setId(traceId))
|
.setId(traceId))
|
||||||
@ -445,6 +490,9 @@ public class RmiClient {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Builder buildValue(Object value) {
|
private Builder buildValue(Object value) {
|
||||||
Builder builder = Value.newBuilder();
|
Builder builder = Value.newBuilder();
|
||||||
|
if (value == null) {
|
||||||
|
return builder.setNullValue(Null.newBuilder());
|
||||||
|
}
|
||||||
if (value instanceof String str) {
|
if (value instanceof String str) {
|
||||||
return builder.setStringValue(str);
|
return builder.setStringValue(str);
|
||||||
}
|
}
|
||||||
@ -497,7 +545,11 @@ public class RmiClient {
|
|||||||
return builder.setBoolArrValue(b.build());
|
return builder.setBoolArrValue(b.build());
|
||||||
}
|
}
|
||||||
if (list.get(0) instanceof Short) {
|
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());
|
return builder.setShortArrValue(b.build());
|
||||||
}
|
}
|
||||||
if (list.get(0) instanceof Integer) {
|
if (list.get(0) instanceof Integer) {
|
||||||
@ -570,12 +622,12 @@ public class RmiClient {
|
|||||||
.setDisplay(param.getDisplay())
|
.setDisplay(param.getDisplay())
|
||||||
.setDescription(param.getDescription())
|
.setDescription(param.getDescription())
|
||||||
.setType(param.getType())
|
.setType(param.getType())
|
||||||
.setDefaultValue(param.getDefaultValue())
|
.setDefaultValue(buildValue(param.getDefaultValue()))
|
||||||
.setRequired(param.isRequired())
|
.setRequired(param.isRequired())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
|
public XReplyInvokeMethod handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
|
||||||
RmiRemoteMethod rm = getMethod(req.getName());
|
RmiRemoteMethod rm = getMethod(req.getName());
|
||||||
Object[] arglist = new Object[req.getArgumentsCount()];
|
Object[] arglist = new Object[req.getArgumentsCount()];
|
||||||
java.lang.reflect.Method m = rm.getMethod();
|
java.lang.reflect.Method m = rm.getMethod();
|
||||||
@ -585,57 +637,50 @@ public class RmiClient {
|
|||||||
argmap.put(arg.getName(), arg);
|
argmap.put(arg.getName(), arg);
|
||||||
}
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Parameter p : m.getParameters()) {
|
for (RmiRemoteMethodParameter p : rm.getParameters()) {
|
||||||
MethodArgument arg = argmap.get(p.getName());
|
MethodArgument arg = argmap.get(p.getName());
|
||||||
if (arg != null) {
|
if (arg != null) {
|
||||||
Object obj = argToObject(traceId, arg);
|
Object obj = argToObject(traceId, arg.getValue());
|
||||||
arglist[i++] = obj;
|
arglist[i++] = obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Object ret = m.invoke(rm.getContainer(), arglist);
|
Object ret = m.invoke(rm.getContainer(), arglist);
|
||||||
if (ret != null) {
|
if (ret != null) {
|
||||||
socket.send(RootMessage.newBuilder()
|
return XReplyInvokeMethod.newBuilder()
|
||||||
.setXreplyInvokeMethod(XReplyInvokeMethod.newBuilder()
|
.setReturnValue(buildValue(ret))
|
||||||
.setReturnValue(buildValue(ret)))
|
.build();
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
|
return XReplyInvokeMethod.newBuilder()
|
||||||
|
.setReturnValue(buildValue(true))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
| IOException e) {
|
|
||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
Msg.error(this, message);
|
Msg.error(this, "Error handling method invocation:" + message);
|
||||||
try {
|
return XReplyInvokeMethod.newBuilder()
|
||||||
socket.send(RootMessage.newBuilder()
|
.setError(message)
|
||||||
.setXreplyInvokeMethod(
|
.build();
|
||||||
XReplyInvokeMethod.newBuilder().setError(message))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
catch (IOException e1) {
|
|
||||||
Msg.error(this, e1.getMessage());
|
|
||||||
}
|
}
|
||||||
|
return XReplyInvokeMethod.newBuilder()
|
||||||
|
.setError(e.toString())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Object argToObject(int traceId, Value value) {
|
||||||
|
|
||||||
private Object argToObject(int traceId, MethodArgument arg) {
|
|
||||||
if (arg == null) {
|
|
||||||
throw new RuntimeException("Null argument passed to argToObject");
|
|
||||||
}
|
|
||||||
Value value = arg.getValue();
|
|
||||||
if (value.hasStringValue()) {
|
if (value.hasStringValue()) {
|
||||||
return value.getStringValue();
|
return value.getStringValue();
|
||||||
}
|
}
|
||||||
if (value.hasStringArrValue()) {
|
if (value.hasStringArrValue()) {
|
||||||
return value.getStringArrValue();
|
return value.getStringArrValue().getArrList();
|
||||||
}
|
}
|
||||||
if (value.hasBoolValue()) {
|
if (value.hasBoolValue()) {
|
||||||
return value.getBoolValue();
|
return value.getBoolValue();
|
||||||
}
|
}
|
||||||
if (value.hasBoolArrValue()) {
|
if (value.hasBoolArrValue()) {
|
||||||
return value.getBoolArrValue();
|
return value.getBoolArrValue().getArrList();
|
||||||
}
|
}
|
||||||
if (value.hasCharValue()) {
|
if (value.hasCharValue()) {
|
||||||
return value.getCharValue();
|
return value.getCharValue();
|
||||||
@ -647,19 +692,19 @@ public class RmiClient {
|
|||||||
return value.getShortValue();
|
return value.getShortValue();
|
||||||
}
|
}
|
||||||
if (value.hasShortArrValue()) {
|
if (value.hasShortArrValue()) {
|
||||||
return value.getShortArrValue();
|
return value.getShortArrValue().getArrList();
|
||||||
}
|
}
|
||||||
if (value.hasIntValue()) {
|
if (value.hasIntValue()) {
|
||||||
return value.getIntValue();
|
return value.getIntValue();
|
||||||
}
|
}
|
||||||
if (value.hasIntArrValue()) {
|
if (value.hasIntArrValue()) {
|
||||||
return value.getIntArrValue();
|
return value.getIntArrValue().getArrList();
|
||||||
}
|
}
|
||||||
if (value.hasLongValue()) {
|
if (value.hasLongValue()) {
|
||||||
return value.getLongValue();
|
return value.getLongValue();
|
||||||
}
|
}
|
||||||
if (value.hasLongArrValue()) {
|
if (value.hasLongArrValue()) {
|
||||||
return value.getLongArrValue();
|
return value.getLongArrValue().getArrList();
|
||||||
}
|
}
|
||||||
if (value.hasAddressValue()) {
|
if (value.hasAddressValue()) {
|
||||||
return decodeAddr(traceId, value.getAddressValue());
|
return decodeAddr(traceId, value.getAddressValue());
|
||||||
@ -681,6 +726,61 @@ public class RmiClient {
|
|||||||
return proxyObjectPath(traceId, path);
|
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) {
|
private Address decodeAddr(int id, Addr addr) {
|
||||||
RmiTrace trace = traces.get(id);
|
RmiTrace trace = traces.get(id);
|
||||||
return trace.memoryMapper.genAddr(addr.getSpace(), addr.getOffset());
|
return trace.memoryMapper.genAddr(addr.getSpace(), addr.getOffset());
|
||||||
@ -700,32 +800,33 @@ public class RmiClient {
|
|||||||
return methodRegistry.getMap().get(name);
|
return methodRegistry.getMap().get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object startBatch() {
|
public RmiBatch startBatch() {
|
||||||
if (currentBatch == null) {
|
if (currentBatch == null) {
|
||||||
currentBatch = new RmiBatch();
|
currentBatch = new RmiBatch(this);
|
||||||
}
|
}
|
||||||
currentBatch.inc();
|
currentBatch.inc();
|
||||||
return currentBatch;
|
return currentBatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object endBatch() {
|
boolean hasBatch() {
|
||||||
RmiBatch cb = null;
|
return currentBatch != null;
|
||||||
if (0 == currentBatch.dec()) {
|
}
|
||||||
cb = currentBatch;
|
|
||||||
|
void endBatch(RmiBatch batch) throws InterruptedException, ExecutionException {
|
||||||
|
if (currentBatch.dec() == 0) {
|
||||||
|
RmiBatch cb = currentBatch;
|
||||||
currentBatch = null;
|
currentBatch = null;
|
||||||
|
cb.results();
|
||||||
}
|
}
|
||||||
if (cb != null) {
|
|
||||||
return cb.results();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TargetObjectSchema getSchema(String schema) {
|
public TargetObjectSchema getSchema(String schema) {
|
||||||
return schemaContext.getSchema(new SchemaName(schema));
|
return schemaContext.getSchema(new SchemaName(schema));
|
||||||
}
|
}
|
||||||
|
|
||||||
public RootMessage getRequestsPoll() {
|
public RequestResult pollRequest() {
|
||||||
|
synchronized (requests) {
|
||||||
return requests.poll();
|
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,10 +27,11 @@ public class RmiMethodRegistry {
|
|||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public static @interface TraceMethod {
|
public static @interface TraceMethod {
|
||||||
String action();
|
String action() default "";
|
||||||
|
|
||||||
String display() default "";
|
String display() default "";
|
||||||
|
|
||||||
String description() default "";
|
String description() default "";
|
||||||
String schema() default "ANY";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, RmiRemoteMethod> map = new HashMap<>();
|
Map<String, RmiRemoteMethod> map = new HashMap<>();
|
||||||
|
@ -19,11 +19,9 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetMethod;
|
import ghidra.dbg.target.TargetMethod;
|
||||||
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
import ghidra.dbg.target.schema.*;
|
import ghidra.dbg.target.schema.*;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
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 {
|
public class RmiRemoteMethod {
|
||||||
|
|
||||||
@ -37,7 +35,8 @@ public class RmiRemoteMethod {
|
|||||||
private RmiMethods instance;
|
private RmiMethods instance;
|
||||||
private Method m;
|
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.schemaContext = schemaContext;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
@ -50,48 +49,17 @@ public class RmiRemoteMethod {
|
|||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Parameter p : m.getParameters()) {
|
for (Parameter p : m.getParameters()) {
|
||||||
TargetObjectSchema pschema = getSchemaFromParameter(p);
|
ParameterDescription<?> desc = TargetMethod.ParameterDescription.annotated(p);
|
||||||
String pname = p.getName(); // NB: don't change this unless yuou resolve the ordering issues
|
TargetObjectSchema pschema;
|
||||||
String pdesc = pname;
|
if (desc.type != RmiTraceObject.class) {
|
||||||
String pdisp = pname;
|
pschema = EnumerableTargetObjectSchema.schemaForPrimitive(desc.type);
|
||||||
if (i == 0) {
|
|
||||||
RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class);
|
|
||||||
if (annot != null) {
|
|
||||||
pschema = schemaContext.getSchema(new SchemaName(annot.schema()));
|
|
||||||
}
|
}
|
||||||
pdisp = "Object";
|
else {
|
||||||
|
pschema = schemaContext.getSchema(new SchemaName(desc.schema));
|
||||||
}
|
}
|
||||||
Value pdef = null;
|
params[i++] = new RmiRemoteMethodParameter(desc.name, pschema, desc.required,
|
||||||
TargetMethod.Param pannot = p.getAnnotation(TargetMethod.Param.class);
|
desc.defaultValue, desc.display, desc.description);
|
||||||
if (pannot != null) {
|
|
||||||
pdesc = pannot.description();
|
|
||||||
pdisp = pannot.display();
|
|
||||||
}
|
}
|
||||||
boolean required = i != 0;
|
|
||||||
params[i++] = new RmiRemoteMethodParameter(pname, pschema, required, pdef, pdisp, pdesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
public String getName() {
|
||||||
@ -125,5 +93,4 @@ public class RmiRemoteMethod {
|
|||||||
public RmiMethods getContainer() {
|
public RmiMethods getContainer() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ public class RmiRemoteMethodParameter {
|
|||||||
private final String name;
|
private final String name;
|
||||||
private final TargetObjectSchema schema;
|
private final TargetObjectSchema schema;
|
||||||
private final boolean required;
|
private final boolean required;
|
||||||
private final Value defaultValue;
|
private final Object defaultValue;
|
||||||
private final String display;
|
private final String display;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
|
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
|
||||||
Value defaultValue, String display, String description) {
|
Object defaultValue, String display, String description) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.schema = schema;
|
this.schema = schema;
|
||||||
this.required = required;
|
this.required = required;
|
||||||
@ -57,15 +57,11 @@ public class RmiRemoteMethodParameter {
|
|||||||
return ValueType.newBuilder().setName(schemaName).build();
|
return ValueType.newBuilder().setName(schemaName).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value getDefaultValue() {
|
public Object getDefaultValue() {
|
||||||
if (defaultValue != null) {
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return Value.newBuilder().setNullValue(Null.newBuilder()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRequired() {
|
public boolean isRequired() {
|
||||||
return required;
|
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;
|
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.*;
|
||||||
import java.util.Set;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.lang.RegisterValue;
|
import ghidra.program.model.lang.RegisterValue;
|
||||||
import ghidra.rmi.trace.TraceRmi.*;
|
import ghidra.rmi.trace.TraceRmi.*;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
@ -33,6 +34,7 @@ public class RmiTrace {
|
|||||||
|
|
||||||
final RmiClient client;
|
final RmiClient client;
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private RequestResult createResult;
|
||||||
|
|
||||||
private int nextTx = 0;
|
private int nextTx = 0;
|
||||||
private Object txLock = new Object();
|
private Object txLock = new Object();
|
||||||
@ -45,9 +47,15 @@ public class RmiTrace {
|
|||||||
public MemoryMapper memoryMapper;
|
public MemoryMapper memoryMapper;
|
||||||
public RegisterMapper registerMapper;
|
public RegisterMapper registerMapper;
|
||||||
|
|
||||||
public RmiTrace(RmiClient client, int id) {
|
public RmiTrace(RmiClient client, int id, RequestResult createResult) {
|
||||||
this.client = client;
|
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() {
|
public void close() {
|
||||||
@ -63,7 +71,7 @@ public class RmiTrace {
|
|||||||
|
|
||||||
public RmiTransaction startTx(String description, boolean undoable) {
|
public RmiTransaction startTx(String description, boolean undoable) {
|
||||||
int txid;
|
int txid;
|
||||||
synchronized(txLock) {
|
synchronized (txLock) {
|
||||||
txid = nextTx++;
|
txid = nextTx++;
|
||||||
}
|
}
|
||||||
client.startTx(id, description, undoable, txid);
|
client.startTx(id, description, undoable, txid);
|
||||||
@ -85,6 +93,9 @@ public class RmiTrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long snapshot(String description, String datatime, Long snap) {
|
public long snapshot(String description, String datatime, Long snap) {
|
||||||
|
if (datatime == null) {
|
||||||
|
datatime = "";
|
||||||
|
}
|
||||||
if (snap == null) {
|
if (snap == null) {
|
||||||
snap = nextSnap();
|
snap = nextSnap();
|
||||||
}
|
}
|
||||||
@ -145,18 +156,40 @@ public class RmiTrace {
|
|||||||
client.createRootObject(id, schemaContext, schema);
|
client.createRootObject(id, schemaContext, schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createObject(String path) {
|
public RmiTraceObject createObject(String path) {
|
||||||
client.createObject(id, path);
|
RequestResult result = client.createObject(id, path);
|
||||||
|
return new RmiTraceObject(this, path, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleCreateObject(ReplyCreateObject reply) {
|
public RmiTraceObject createAndInsertObject(String path) {
|
||||||
RmiTraceObject obj = new RmiTraceObject(this, reply.getObject());
|
RmiTraceObject object = createObject(path);
|
||||||
try (RmiTransaction tx = startTx("CreateObject", false); LockHold hold = LockHold.lock(snLock.readLock())) {
|
object.insert(currentSnap, null);
|
||||||
obj.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) {
|
public void insertObject(String path) {
|
||||||
@ -180,9 +213,47 @@ public class RmiTrace {
|
|||||||
client.retainValues(id, ppath, span, kinds, keys);
|
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) {
|
public void activate(String path) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
Msg.error(this, "Attempt to activate null");
|
Msg.error(this, "Attempt to activate null");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
client.activate(id, path);
|
client.activate(id, path);
|
||||||
}
|
}
|
||||||
@ -191,12 +262,16 @@ public class RmiTrace {
|
|||||||
client.disassemble(id, snapOrCurrent(snap), start);
|
client.disassemble(id, snapOrCurrent(snap), start);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleInvokeMethod(XRequestInvokeMethod req) {
|
public XReplyInvokeMethod handleInvokeMethod(XRequestInvokeMethod req) {
|
||||||
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
|
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
|
||||||
client.handleInvokeMethod(id, req);
|
return client.handleInvokeMethod(id, req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RmiTraceObject proxyObject(ObjDesc desc) {
|
||||||
|
return client.proxyObjectPath(id, desc.getId(), desc.getPath().getPath());
|
||||||
|
}
|
||||||
|
|
||||||
public RmiTraceObject proxyObjectId(Long objectId) {
|
public RmiTraceObject proxyObjectId(Long objectId) {
|
||||||
return client.proxyObjectId(id, objectId);
|
return client.proxyObjectId(id, objectId);
|
||||||
}
|
}
|
||||||
|
@ -17,23 +17,31 @@ package ghidra.app.plugin.core.debug.client.tracermi;
|
|||||||
|
|
||||||
import java.util.Set;
|
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;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
|
||||||
public class RmiTraceObject {
|
public class RmiTraceObject {
|
||||||
|
|
||||||
private RmiTrace trace;
|
private final RmiTrace trace;
|
||||||
private ObjSpec spec;
|
private final String path;
|
||||||
private String path;
|
private volatile Long id;
|
||||||
|
|
||||||
public RmiTraceObject(RmiTrace trace, ObjSpec spec) {
|
public RmiTraceObject(RmiTrace trace, String path) {
|
||||||
this.trace = trace;
|
this.trace = trace;
|
||||||
this.spec = spec;
|
this.path = path;
|
||||||
this.path = spec.getPath().getPath();
|
}
|
||||||
|
|
||||||
|
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) {
|
public RmiTraceObject(RmiTrace trace, Long id, String path) {
|
||||||
this.trace = trace;
|
this.trace = trace;
|
||||||
|
this.id = id;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,17 +53,29 @@ public class RmiTraceObject {
|
|||||||
return new RmiTraceObject(trace, null, path);
|
return new RmiTraceObject(trace, null, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(long snap, Resolution resolution) {
|
public Lifespan insert(long snap, Resolution resolution) {
|
||||||
if (resolution == null) {
|
if (resolution == null) {
|
||||||
resolution = Resolution.CR_ADJUST;
|
resolution = Resolution.CR_ADJUST;
|
||||||
}
|
}
|
||||||
Lifespan span = Lifespan.nowOn(snap);
|
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);
|
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) {
|
public void setValue(String key, Object value, long snap, String resolution) {
|
||||||
|
@ -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();
|
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));
|
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
|
@Override
|
||||||
protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr<?> val) {
|
protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr<?> val) {
|
||||||
ValStr<?> v = switch (val.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());
|
case TraceObject obj -> new ValStr<>(obj, obj.getCanonicalPath().toString());
|
||||||
default -> val;
|
default -> val;
|
||||||
};
|
};
|
||||||
|
@ -42,8 +42,13 @@ import ghidra.framework.model.DomainFile;
|
|||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.program.model.listing.ProgramUserData;
|
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.Msg;
|
||||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
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) {
|
public static File tryProgramPath(String path) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -154,6 +255,12 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
|||||||
if (program == null) {
|
if (program == null) {
|
||||||
return 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());
|
File exec = tryProgramPath(program.getExecutablePath());
|
||||||
if (exec != null) {
|
if (exec != null) {
|
||||||
return exec.getAbsolutePath();
|
return exec.getAbsolutePath();
|
||||||
@ -371,8 +478,7 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
|||||||
toolLaunchConfigs.putSaveState(name, state);
|
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,
|
protected ConfigLast checkSavedConfig(Program program, ProgramUserData userData,
|
||||||
String propName) {
|
String propName) {
|
||||||
|
@ -448,12 +448,13 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.error(this, "Cannot send reply", e);
|
Msg.error(this, "Cannot send reply: " + e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveLoop() {
|
public void receiveLoop() {
|
||||||
|
boolean canSend = true;
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
RootMessage req = receive();
|
RootMessage req = receive();
|
||||||
@ -468,8 +469,15 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||||||
continue;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
|||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||||
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.debug.api.tracermi.*;
|
import ghidra.debug.api.tracermi.*;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
@ -71,8 +72,8 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
// @AutoServiceConsumed // via method
|
||||||
private DebuggerTargetService targetService;
|
private volatile DebuggerTargetService targetService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private ProgressService progressService;
|
private ProgressService progressService;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@ -94,6 +95,25 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
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() {
|
protected CloseableTaskMonitor createMonitor() {
|
||||||
if (progressService == null) {
|
if (progressService == null) {
|
||||||
return fallbackMonitor;
|
return fallbackMonitor;
|
||||||
@ -201,6 +221,15 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||||||
|
|
||||||
void publishTarget(TraceRmiHandler handler, TraceRmiTarget target) {
|
void publishTarget(TraceRmiHandler handler, TraceRmiTarget target) {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
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);
|
targetService.publishTarget(target);
|
||||||
listeners.invoke().targetPublished(handler, target);
|
listeners.invoke().targetPublished(handler, target);
|
||||||
});
|
});
|
||||||
@ -208,6 +237,10 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||||||
|
|
||||||
void withdrawTarget(TraceRmiTarget target) {
|
void withdrawTarget(TraceRmiTarget target) {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
|
if (targetService == null) {
|
||||||
|
// This can happen during tear down.
|
||||||
|
return;
|
||||||
|
}
|
||||||
targetService.withdrawTarget(target);
|
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"));
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui;
|
package ghidra.app.plugin.core.debug.gui;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
import java.beans.PropertyEditor;
|
import java.beans.PropertyEditor;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@ -23,6 +24,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
|
||||||
import docking.test.AbstractDockingTest;
|
import docking.test.AbstractDockingTest;
|
||||||
|
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
||||||
import ghidra.async.SwingExecutorService;
|
import ghidra.async.SwingExecutorService;
|
||||||
import ghidra.debug.api.ValStr;
|
import ghidra.debug.api.ValStr;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
@ -55,6 +57,11 @@ public class InvocationDialogHelper<P, D extends AbstractDebuggerParameterDialog
|
|||||||
runSwing(() -> editor.setValue(value));
|
runSwing(() -> editor.setValue(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Component getEditorComponent(P param) {
|
||||||
|
PropertyEditor editor = dialog.getEditor(param);
|
||||||
|
return MiscellaneousUtils.getEditorComponent(editor);
|
||||||
|
}
|
||||||
|
|
||||||
protected void runSwing(Runnable r) {
|
protected void runSwing(Runnable r) {
|
||||||
try {
|
try {
|
||||||
CompletableFuture.runAsync(r, SwingExecutorService.LATER).get();
|
CompletableFuture.runAsync(r, SwingExecutorService.LATER).get();
|
||||||
|
@ -223,11 +223,13 @@ public interface TargetMethod extends TargetObject {
|
|||||||
p -> new BytesValue.Val(p.defaultBytes()),
|
p -> new BytesValue.Val(p.defaultBytes()),
|
||||||
p -> new StringValue.Val(p.defaultString()));
|
p -> new StringValue.Val(p.defaultString()));
|
||||||
|
|
||||||
String name();
|
String name() default "";
|
||||||
|
|
||||||
String display();
|
String display() default "";
|
||||||
|
|
||||||
String description();
|
String description() default "";
|
||||||
|
|
||||||
|
String schema() default "ANY";
|
||||||
|
|
||||||
// TODO: Something that hints at changes in activation?
|
// TODO: Something that hints at changes in activation?
|
||||||
|
|
||||||
@ -259,6 +261,25 @@ public interface TargetMethod extends TargetObject {
|
|||||||
* @param <T> the type of the parameter
|
* @param <T> the type of the parameter
|
||||||
*/
|
*/
|
||||||
class ParameterDescription<T> {
|
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
|
||||||
|
* @param schema the parameter's schema
|
||||||
|
* @return the new parameter description
|
||||||
|
*/
|
||||||
|
public static <T> ParameterDescription<T> create(Class<T> type, String name,
|
||||||
|
boolean required, T defaultValue, String display, String description, String schema) {
|
||||||
|
return new ParameterDescription<>(type, name, required, defaultValue, display,
|
||||||
|
description, schema, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a parameter
|
* Create a parameter
|
||||||
*
|
*
|
||||||
@ -274,7 +295,7 @@ public interface TargetMethod extends TargetObject {
|
|||||||
public static <T> ParameterDescription<T> create(Class<T> type, String name,
|
public static <T> ParameterDescription<T> create(Class<T> type, String name,
|
||||||
boolean required, T defaultValue, String display, String description) {
|
boolean required, T defaultValue, String display, String description) {
|
||||||
return new ParameterDescription<>(type, name, required, defaultValue, display,
|
return new ParameterDescription<>(type, name, required, defaultValue, display,
|
||||||
description, List.of());
|
description, "ANY", List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -292,7 +313,7 @@ public interface TargetMethod extends TargetObject {
|
|||||||
Collection<T> choices, String display, String description) {
|
Collection<T> choices, String display, String description) {
|
||||||
T defaultValue = choices.iterator().next();
|
T defaultValue = choices.iterator().next();
|
||||||
return new ParameterDescription<>(type, name, false, defaultValue, display, description,
|
return new ParameterDescription<>(type, name, false, defaultValue, display, description,
|
||||||
choices);
|
"ANY", choices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -313,7 +334,7 @@ public interface TargetMethod extends TargetObject {
|
|||||||
throw new IllegalArgumentException("Default must be one of the choices.");
|
throw new IllegalArgumentException("Default must be one of the choices.");
|
||||||
}
|
}
|
||||||
return new ParameterDescription<>(type, name, false, defaultValue, display, description,
|
return new ParameterDescription<>(type, name, false, defaultValue, display, description,
|
||||||
choices);
|
"ANY", choices);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isRequired(Class<?> type, Param param) {
|
protected static boolean isRequired(Class<?> type, Param param) {
|
||||||
@ -369,11 +390,11 @@ public interface TargetMethod extends TargetObject {
|
|||||||
return type.cast(dv);
|
return type.cast(dv);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static <T> ParameterDescription<T> annotated(Class<T> type, Param annot) {
|
protected static <T> ParameterDescription<T> annotated(Class<T> type, Param annot, String name) {
|
||||||
boolean required = isRequired(type, annot);
|
boolean required = isRequired(type, annot);
|
||||||
T defaultValue = getDefault(type, annot);
|
T defaultValue = getDefault(type, annot);
|
||||||
return ParameterDescription.create(type, annot.name(),
|
return ParameterDescription.create(type, name,
|
||||||
required, defaultValue, annot.display(), annot.description());
|
required, defaultValue, annot.display(), annot.description(), annot.schema());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ParameterDescription<?> annotated(Parameter parameter) {
|
public static ParameterDescription<?> annotated(Parameter parameter) {
|
||||||
@ -382,15 +403,16 @@ public interface TargetMethod extends TargetObject {
|
|||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Missing @" + Param.class.getSimpleName() + " on " + parameter);
|
"Missing @" + Param.class.getSimpleName() + " on " + parameter);
|
||||||
}
|
}
|
||||||
|
String name = annot.name().equals("") ? parameter.getName() : annot.name();
|
||||||
if (annot.choicesString().specified()) {
|
if (annot.choicesString().specified()) {
|
||||||
if (parameter.getType() != String.class) {
|
if (parameter.getType() != String.class) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Can only specify choices for String parameter");
|
"Can only specify choices for String parameter");
|
||||||
}
|
}
|
||||||
return ParameterDescription.choices(String.class, annot.name(),
|
return ParameterDescription.choices(String.class, name,
|
||||||
List.of(annot.choicesString().value()), annot.display(), annot.description());
|
List.of(annot.choicesString().value()), annot.display(), annot.description(), annot.schema());
|
||||||
}
|
}
|
||||||
return annotated(MethodType.methodType(parameter.getType()).wrap().returnType(), annot);
|
return annotated(MethodType.methodType(parameter.getType()).wrap().returnType(), annot, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Class<T> type;
|
public final Class<T> type;
|
||||||
@ -399,16 +421,18 @@ public interface TargetMethod extends TargetObject {
|
|||||||
public final boolean required;
|
public final boolean required;
|
||||||
public final String display;
|
public final String display;
|
||||||
public final String description;
|
public final String description;
|
||||||
|
public final String schema;
|
||||||
public final Set<T> choices;
|
public final Set<T> choices;
|
||||||
|
|
||||||
private ParameterDescription(Class<T> type, String name, boolean required, T defaultValue,
|
private ParameterDescription(Class<T> type, String name, boolean required, T defaultValue,
|
||||||
String display, String description, Collection<T> choices) {
|
String display, String description, String schema, Collection<T> choices) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
this.required = required;
|
this.required = required;
|
||||||
this.display = display;
|
this.display = display;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.schema = schema;
|
||||||
this.choices = Set.copyOf(choices);
|
this.choices = Set.copyOf(choices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,15 +132,15 @@ public class ShellUtils {
|
|||||||
if (args.isEmpty()) {
|
if (args.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
StringBuilder line = new StringBuilder(genreateArgument(args.get(0)));
|
StringBuilder line = new StringBuilder(generateArgument(args.get(0)));
|
||||||
for (int i = 1; i < args.size(); i++) {
|
for (int i = 1; i < args.size(); i++) {
|
||||||
String a = args.get(i);
|
String a = args.get(i);
|
||||||
line.append(" " + genreateArgument(a));
|
line.append(" " + generateArgument(a));
|
||||||
}
|
}
|
||||||
return line.toString();
|
return line.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String genreateArgument(String a) {
|
public static String generateArgument(String a) {
|
||||||
if (a.contains(" ")) {
|
if (a.contains(" ")) {
|
||||||
if (a.contains("\"")) {
|
if (a.contains("\"")) {
|
||||||
if (a.contains("'")) {
|
if (a.contains("'")) {
|
||||||
|
@ -30,6 +30,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import db.DBHandle;
|
import db.DBHandle;
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
|
import generic.test.AbstractGenericTest;
|
||||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
@ -800,13 +801,19 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||||||
/**
|
/**
|
||||||
* Get an object by its path pattern
|
* Get an object by its path pattern
|
||||||
*
|
*
|
||||||
* @param path the path pattern
|
* @param path the path pattern <em>at snapshot 0 only!</em>
|
||||||
* @return the object or null
|
* @return the object or null
|
||||||
*/
|
*/
|
||||||
public TraceObject objAny(String path) {
|
public TraceObject objAny0(String path) {
|
||||||
return objAny(path, Lifespan.at(0));
|
return objAny(path, Lifespan.at(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an object by its path pattern intersecting the given lifespan
|
||||||
|
*
|
||||||
|
* @param path the path pattern
|
||||||
|
* @return the object or null
|
||||||
|
*/
|
||||||
public TraceObject objAny(String path, Lifespan span) {
|
public TraceObject objAny(String path, Lifespan span) {
|
||||||
return trace.getObjectManager()
|
return trace.getObjectManager()
|
||||||
.getObjectsByPath(span, TraceObjectKeyPath.parse(path))
|
.getObjectsByPath(span, TraceObjectKeyPath.parse(path))
|
||||||
@ -844,6 +851,7 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (trace.getConsumerList().contains(this)) {
|
if (trace.getConsumerList().contains(this)) {
|
||||||
|
AbstractGenericTest.waitFor(() -> !trace.isLocked());
|
||||||
trace.release(this);
|
trace.release(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,7 @@ public class RecoverySnapshotMgrPlugin extends Plugin
|
|||||||
public void dispose() {
|
public void dispose() {
|
||||||
ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
|
ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
|
||||||
opt.removeOptionsChangeListener(this);
|
opt.removeOptionsChangeListener(this);
|
||||||
|
stopSnapshotTimer();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,4 +22,31 @@ public class StringEditor extends PropertyEditorSupport {
|
|||||||
public StringEditor() {
|
public StringEditor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The comment in the parent "PropertyEditorSupport" reads:
|
||||||
|
*
|
||||||
|
* <blockquote>
|
||||||
|
* <p>
|
||||||
|
* Sets the property value by parsing a given String. May raise
|
||||||
|
* java.lang.IllegalArgumentException if either the String is badly formatted or if this kind of
|
||||||
|
* property can't be expressed as text.
|
||||||
|
* </p>
|
||||||
|
* </blockquote>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* which would be fine, except for the fact that Java initializes "value" to null, so every use
|
||||||
|
* of this method has to insure that setValue has been called at least once with a non-null
|
||||||
|
* value. If not, the method throws the IllegalArgumentException despite the fact that the input
|
||||||
|
* is not badly formatted and CAN be expressed as text.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAsText(String text) throws java.lang.IllegalArgumentException {
|
||||||
|
Object value = getValue();
|
||||||
|
if (value == null || value instanceof String) {
|
||||||
|
setValue(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new java.lang.IllegalArgumentException(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
TraceObject proc = tb.objAny("Processes[]");
|
TraceObject proc = tb.objAny0("Processes[]");
|
||||||
assertNotNull(proc);
|
assertNotNull(proc);
|
||||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -107,7 +107,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
|
|
||||||
// Via method, go is asynchronous
|
// Via method, go is asynchronous
|
||||||
RemoteMethod go = conn.conn.getMethod("go");
|
RemoteMethod go = conn.conn.getMethod("go");
|
||||||
TraceObject proc = tb.objAny("Processes[]");
|
TraceObject proc = tb.objAny0("Processes[]");
|
||||||
go.invoke(Map.of("process", proc));
|
go.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
waitForPass(
|
waitForPass(
|
||||||
@ -273,7 +273,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
""");
|
""");
|
||||||
waitRunning("Missed running after go");
|
waitRunning("Missed running after go");
|
||||||
|
|
||||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
TraceObject proc = waitForValue(() -> tb.objAny0("Processes[]"));
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -285,7 +285,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
TraceObject proc = waitForValue(() -> tb.objAny0("Processes[]"));
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -307,7 +307,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
assertNotNull(snapshot);
|
assertNotNull(snapshot);
|
||||||
assertEquals("Exited with code 0", snapshot.getDescription());
|
assertEquals("Exited with code 0", snapshot.getDescription());
|
||||||
|
|
||||||
TraceObject proc = tb.objAny("Processes[]");
|
TraceObject proc = tb.objAny0("Processes[]");
|
||||||
assertNotNull(proc);
|
assertNotNull(proc);
|
||||||
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
||||||
assertThat(val, instanceOf(Number.class));
|
assertThat(val, instanceOf(Number.class));
|
||||||
|
@ -82,7 +82,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject available = Objects.requireNonNull(tb.objAny("Available"));
|
TraceObject available = Objects.requireNonNull(tb.objAny0("Available"));
|
||||||
|
|
||||||
refreshAvailable.invoke(Map.of("node", available));
|
refreshAvailable.invoke(Map.of("node", available));
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
conn.execute("util.dbg.ba(expr=pc+4)");
|
conn.execute("util.dbg.ba(expr=pc+4)");
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject breakpoints =
|
TraceObject breakpoints =
|
||||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
Objects.requireNonNull(tb.objAny0("Processes[].Breakpoints"));
|
||||||
refreshBreakpoints.invoke(Map.of("node", breakpoints));
|
refreshBreakpoints.invoke(Map.of("node", breakpoints));
|
||||||
|
|
||||||
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
|
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
|
||||||
@ -150,7 +150,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
conn.execute("util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
|
conn.execute("util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
|
||||||
conn.execute("util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
|
conn.execute("util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
|
||||||
TraceObject locations =
|
TraceObject locations =
|
||||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
Objects.requireNonNull(tb.objAny0("Processes[].Breakpoints"));
|
||||||
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
||||||
|
|
||||||
List<TraceObjectValue> procBreakVals = tb.trace.getObjectManager()
|
List<TraceObjectValue> procBreakVals = tb.trace.getObjectManager()
|
||||||
@ -193,7 +193,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
|
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject processes = Objects.requireNonNull(tb.objAny("Processes"));
|
TraceObject processes = Objects.requireNonNull(tb.objAny0("Processes"));
|
||||||
|
|
||||||
refreshProcesses.invoke(Map.of("node", processes));
|
refreshProcesses.invoke(Map.of("node", processes));
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject env = Objects.requireNonNull(tb.objAny(path));
|
TraceObject env = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshEnvironment.invoke(Map.of("node", env));
|
refreshEnvironment.invoke(Map.of("node", env));
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
|
TraceObject threads = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshThreads.invoke(Map.of("node", threads));
|
refreshThreads.invoke(Map.of("node", threads));
|
||||||
|
|
||||||
@ -263,7 +263,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
txPut(conn, "frames");
|
txPut(conn, "frames");
|
||||||
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
|
TraceObject stack = Objects.requireNonNull(tb.objAny0(path));
|
||||||
refreshStack.invoke(Map.of("node", stack));
|
refreshStack.invoke(Map.of("node", stack));
|
||||||
|
|
||||||
// Would be nice to control / validate the specifics
|
// Would be nice to control / validate the specifics
|
||||||
@ -315,7 +315,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
|
TraceObject memory = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshMappings.invoke(Map.of("node", memory));
|
refreshMappings.invoke(Map.of("node", memory));
|
||||||
|
|
||||||
@ -337,7 +337,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
|
TraceObject modules = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshModules.invoke(Map.of("node", modules));
|
refreshModules.invoke(Map.of("node", modules));
|
||||||
|
|
||||||
@ -390,7 +390,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc2 = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
removeProcess.invoke(Map.of("process", proc2));
|
removeProcess.invoke(Map.of("process", proc2));
|
||||||
|
|
||||||
String out = conn.executeCapture("print(list(util.process_list()))");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
@ -451,7 +451,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
detach.invoke(Map.of("process", proc));
|
detach.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
String out = conn.executeCapture("print(list(util.process_list()))");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
@ -512,7 +512,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
kill.invoke(Map.of("process", proc));
|
kill.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
String out = conn.executeCapture("print(list(util.process_list()))");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
@ -535,7 +535,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
go.invoke(Map.of("process", proc));
|
go.invoke(Map.of("process", proc));
|
||||||
@ -561,7 +561,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
|
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
stepInto.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
@ -595,7 +595,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
|
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
stepOver.invoke(Map.of("thread", thread));
|
stepOver.invoke(Map.of("thread", thread));
|
||||||
@ -623,7 +623,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
stepInto.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
}
|
}
|
||||||
@ -656,7 +656,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
|
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
stepInto.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
@ -683,7 +683,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
|
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
@ -723,7 +723,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
|
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
@ -764,7 +764,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
AddressRange range = tb.range(address, address + 3); // length 4
|
AddressRange range = tb.range(address, address + 3); // length 4
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
@ -809,7 +809,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
AddressRange range = tb.range(address, address + 3); // length 4
|
AddressRange range = tb.range(address, address + 3); // length 4
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
@ -854,7 +854,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped("Missed initial stop");
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
AddressRange range = tb.range(address, address + 3); // length 4
|
AddressRange range = tb.range(address, address + 3); // length 4
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
@ -900,11 +900,11 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
|
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
|
TraceObject bpt = Objects.requireNonNull(tb.objAny0("Processes[].Breakpoints[]"));
|
||||||
|
|
||||||
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
||||||
|
|
||||||
@ -926,11 +926,11 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
|
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
|
TraceObject bpt = Objects.requireNonNull(tb.objAny0("Processes[].Breakpoints[]"));
|
||||||
|
|
||||||
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
||||||
|
|
||||||
|
@ -0,0 +1,581 @@
|
|||||||
|
/* ###
|
||||||
|
* 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 agent.java.rmi;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
|
import generic.jar.ResourceFile;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
|
import ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin;
|
||||||
|
import ghidra.app.services.TraceRmiService;
|
||||||
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
import ghidra.debug.api.tracermi.*;
|
||||||
|
import ghidra.framework.Application;
|
||||||
|
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||||
|
import ghidra.framework.main.FrontEndTool;
|
||||||
|
import ghidra.framework.model.DomainFile;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||||
|
import ghidra.framework.plugintool.util.*;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
|
public abstract class AbstractJavaTraceRmiTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: It would be nice if we didn't have to initialize a "Ghidra application" in order to use
|
||||||
|
* the RmiClient; however, I'm not sure that's worth it.
|
||||||
|
*/
|
||||||
|
public static final String PREAMBLE = """
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import com.sun.jdi.*;
|
||||||
|
import com.sun.jdi.request.*;
|
||||||
|
import ghidra.dbg.jdi.rmi.jpda.*;
|
||||||
|
import ghidra.dbg.jdi.manager.impl.*;
|
||||||
|
import ghidra.framework.Application;
|
||||||
|
import ghidra.framework.GhidraApplicationConfiguration;
|
||||||
|
import ghidra.GhidraApplicationLayout;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.app.plugin.core.debug.client.tracermi.*;
|
||||||
|
import ghidra.rmi.trace.TraceRmi.MemoryState;
|
||||||
|
|
||||||
|
GhidraApplicationLayout layout = new GhidraApplicationLayout();
|
||||||
|
GhidraApplicationConfiguration config = new GhidraApplicationConfiguration();
|
||||||
|
config.setShowSplashScreen(false);
|
||||||
|
Application.initializeApplication(layout, config);
|
||||||
|
|
||||||
|
JdiManagerImpl manager = new JdiManagerImpl();
|
||||||
|
JdiManager jdiManager = new JdiManager(manager);
|
||||||
|
JdiCommands cmds = jdiManager.getCommands();
|
||||||
|
JdiMethods meths = jdiManager.getMethods();
|
||||||
|
JdiHooks hooks = jdiManager.getHooks();
|
||||||
|
hooks.installHooks();
|
||||||
|
""";
|
||||||
|
// Connecting should be the first thing the script does, so use a tight timeout.
|
||||||
|
protected static final int CONNECT_TIMEOUT_MS = 3000;
|
||||||
|
protected static final int TIMEOUT_SECONDS = 300;
|
||||||
|
protected static final int QUIT_TIMEOUT_MS = 1000;
|
||||||
|
protected static final long EXT_TIMEOUT_MS = 5000;
|
||||||
|
protected static final long EXT_RETRY_MS = 500;
|
||||||
|
|
||||||
|
protected TraceRmiService traceRmi;
|
||||||
|
private Path jshellPath;
|
||||||
|
private Path outFile;
|
||||||
|
private Path errFile;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupTraceRmi() throws Throwable {
|
||||||
|
traceRmi = addPlugin(tool, TraceRmiPlugin.class);
|
||||||
|
|
||||||
|
traceManager.setSaveTracesByDefault(false);
|
||||||
|
|
||||||
|
jshellPath = Paths.get(System.getProperty("java.home")).resolve("bin/jshell");
|
||||||
|
outFile = Files.createTempFile("jshout", null);
|
||||||
|
errFile = Files.createTempFile("jsherr", null);
|
||||||
|
|
||||||
|
FrontEndTool frontEndTool = env.getFrontEndTool();
|
||||||
|
Plugin recoveryPlugin = frontEndTool.getManagedPlugins()
|
||||||
|
.stream()
|
||||||
|
.filter(p -> p instanceof RecoverySnapshotMgrPlugin)
|
||||||
|
.findAny()
|
||||||
|
.get();
|
||||||
|
frontEndTool.removePlugins(List.of(recoveryPlugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addAllDebuggerPlugins() throws PluginException {
|
||||||
|
PluginsConfiguration plugConf = new PluginsConfiguration() {
|
||||||
|
@Override
|
||||||
|
protected boolean accepts(Class<? extends Plugin> pluginClass) {
|
||||||
|
return !ApplicationLevelOnlyPlugin.class.isAssignableFrom(pluginClass);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (PluginDescription pd : plugConf
|
||||||
|
.getPluginDescriptions(PluginPackage.getPluginPackage("Debugger"))) {
|
||||||
|
addPlugin(tool, pd.getPluginClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String addrToStringForJshell(InetAddress address) {
|
||||||
|
if (address.isAnyLocalAddress()) {
|
||||||
|
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
|
||||||
|
}
|
||||||
|
return address.getHostAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String sockToStringForJshell(SocketAddress address) {
|
||||||
|
if (address instanceof InetSocketAddress tcp) {
|
||||||
|
return addrToStringForJshell(tcp.getAddress()) + ":" + tcp.getPort();
|
||||||
|
}
|
||||||
|
throw new AssertionError("Unhandled address type " + address);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected record JshellResult(boolean timedOut, int exitCode, String stdout, String stderr) {
|
||||||
|
protected String handle() {
|
||||||
|
if (stderr.contains("Error:") || (0 != exitCode)) {
|
||||||
|
throw new JshellError(exitCode, stdout, stderr);
|
||||||
|
}
|
||||||
|
System.out.println("--stdout--");
|
||||||
|
System.out.println(stdout);
|
||||||
|
System.out.println("--stderr--");
|
||||||
|
System.out.println(stderr);
|
||||||
|
return stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected record ExecInJshell(Process jshell, AbstractJavaTraceRmiTest test,
|
||||||
|
CompletableFuture<JshellResult> future) {}
|
||||||
|
|
||||||
|
private String parseClassPath() {
|
||||||
|
String classPath = System.getProperty("java.class.path");
|
||||||
|
String[] split = classPath.split(":");
|
||||||
|
String newClassPath = "";
|
||||||
|
for (String p : split) {
|
||||||
|
File file = new File(p);
|
||||||
|
if (file.exists()) {
|
||||||
|
newClassPath += p + ":";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newClassPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource") // Do not close stdin
|
||||||
|
protected ExecInJshell execInJshell(String script) throws IOException {
|
||||||
|
String classPath = parseClassPath();
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(jshellPath.toString(), "--class-path=" + classPath);
|
||||||
|
|
||||||
|
// If commands come from file, jshell will quit after EOF.
|
||||||
|
Msg.info(this, "outFile: " + outFile);
|
||||||
|
Msg.info(this, "errFile: " + errFile);
|
||||||
|
|
||||||
|
ResourceFile rf = Application.getModuleDataFile("TestResources", "HelloWorld.class");
|
||||||
|
pb.environment().put("OPT_TARGET_CLASSPATH", rf.getParentFile().getAbsolutePath());
|
||||||
|
pb.environment().put("OPT_TARGET_CLASS", "HelloWorld");
|
||||||
|
pb.environment().put("OPT_SUSPEND", "true");
|
||||||
|
pb.environment().put("OPT_INCLUDE", "n");
|
||||||
|
//pb.inheritIO();
|
||||||
|
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
|
||||||
|
pb.redirectOutput(outFile.toFile());
|
||||||
|
pb.redirectError(errFile.toFile());
|
||||||
|
Process proc = pb.start();
|
||||||
|
OutputStream stdin = proc.getOutputStream();
|
||||||
|
stdin.write(script.getBytes());
|
||||||
|
stdin.flush();
|
||||||
|
//stdin.close();
|
||||||
|
return new ExecInJshell(proc, this, CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
if (!proc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||||
|
Msg.error(this, "Timed out waiting for jshell");
|
||||||
|
proc.destroyForcibly();
|
||||||
|
proc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||||
|
return new JshellResult(true, -1, Files.readString(outFile),
|
||||||
|
Files.readString(errFile));
|
||||||
|
}
|
||||||
|
Msg.info(this, "jshell exited with code " + proc.exitValue());
|
||||||
|
return new JshellResult(false, proc.exitValue(), Files.readString(outFile),
|
||||||
|
Files.readString(errFile));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return ExceptionUtils.rethrow(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
proc.destroyForcibly();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JshellError extends RuntimeException {
|
||||||
|
public final int exitCode;
|
||||||
|
public final String stdout;
|
||||||
|
public final String stderr;
|
||||||
|
|
||||||
|
public JshellError(int exitCode, String stdout, String stderr) {
|
||||||
|
super("""
|
||||||
|
exitCode=%d:
|
||||||
|
----stdout----
|
||||||
|
%s
|
||||||
|
----stderr----
|
||||||
|
%s
|
||||||
|
""".formatted(exitCode, stdout, stderr));
|
||||||
|
this.exitCode = exitCode;
|
||||||
|
this.stdout = stdout;
|
||||||
|
this.stderr = stderr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String runThrowError(String script) throws Exception {
|
||||||
|
CompletableFuture<JshellResult> result = execInJshell(script).future;
|
||||||
|
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected record JshellAndConnection(ExecInJshell exec, TraceRmiConnection connection)
|
||||||
|
implements AutoCloseable {
|
||||||
|
private static BufferedReader reader = null;
|
||||||
|
|
||||||
|
protected RemoteMethod getMethod(String name) {
|
||||||
|
return Objects.requireNonNull(connection.getMethods().get(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(String cmd) {
|
||||||
|
try {
|
||||||
|
cmd += "\n";
|
||||||
|
exec.jshell.getOutputStream().write(cmd.getBytes());
|
||||||
|
exec.jshell.getOutputStream().flush();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteAsyncResult executeAsync(String cmd) {
|
||||||
|
RemoteMethod execute = getMethod("execute");
|
||||||
|
return execute.invokeAsync(Map.of("cmd", cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> executeCapture(String cmd) {
|
||||||
|
try {
|
||||||
|
if (reader == null) {
|
||||||
|
reader = Files.newBufferedReader(exec.test.outFile);
|
||||||
|
}
|
||||||
|
if (cmd != null) {
|
||||||
|
execute(cmd);
|
||||||
|
}
|
||||||
|
List<String> collect = waitForPass(() -> {
|
||||||
|
List<String> list = reader.lines().collect(Collectors.toList());
|
||||||
|
assertFalse(list.isEmpty());
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
return collect;
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
Msg.info(this, "Cleaning up jshell");
|
||||||
|
execute("/exit");
|
||||||
|
try {
|
||||||
|
JshellResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||||
|
r.handle();
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
exec.jshell.destroyForcibly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitOnClosed() {
|
||||||
|
waitForPass(() -> assertTrue(connection.isClosed()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JshellAndConnection startAndConnectJshell(Function<String, String> scriptSupplier)
|
||||||
|
throws Exception {
|
||||||
|
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
|
||||||
|
ExecInJshell exec =
|
||||||
|
execInJshell(scriptSupplier.apply(sockToStringForJshell(acceptor.getAddress())));
|
||||||
|
acceptor.setTimeout(TIMEOUT_SECONDS * 1000);
|
||||||
|
try {
|
||||||
|
TraceRmiConnection connection = acceptor.accept();
|
||||||
|
return new JshellAndConnection(exec, connection);
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException e) {
|
||||||
|
exec.jshell.destroyForcibly();
|
||||||
|
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JshellAndConnection startAndConnectJshell() throws Exception {
|
||||||
|
return startAndConnectJshell(addr -> """
|
||||||
|
%s
|
||||||
|
cmds.ghidraTraceConnect("%s");
|
||||||
|
""".formatted(PREAMBLE, addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
protected String runThrowError(Function<String, String> scriptSupplier)
|
||||||
|
throws Exception {
|
||||||
|
JshellAndConnection conn = startAndConnectJshell(scriptSupplier);
|
||||||
|
JshellResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||||
|
String stdout = r.handle();
|
||||||
|
/**
|
||||||
|
* We know at this point the process should be terminated. Depending on how cleanly that
|
||||||
|
* happened, the socket may or may not be closed. Do not assert/wait for it to close. Just
|
||||||
|
* clean up by closing our end
|
||||||
|
*/
|
||||||
|
conn.connection.close();
|
||||||
|
assertFalse(stdout.contains("Error:"));
|
||||||
|
return stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitStopped(String message, Long snap) {
|
||||||
|
TraceObject proc = Objects.requireNonNull(tb.objAny("VMs[]", Lifespan.at(snap)));
|
||||||
|
waitForPass(() -> assertEquals(message, "STOPPED", tb.objValue(proc, 0, "_state")));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitRunning(String message, Long snap) {
|
||||||
|
TraceObject proc = Objects.requireNonNull(tb.objAny("VMs[]", Lifespan.at(snap)));
|
||||||
|
waitForPass(() -> assertEquals(message, "RUNNING", tb.objValue(proc, 0, "_state")));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitAny(String message, Long snap) {
|
||||||
|
TraceObject proc = Objects.requireNonNull(tb.objAny("VMs[]", Lifespan.at(snap)));
|
||||||
|
waitForPass(() -> assertNotNull(message, tb.objValue(proc, 0, "_state")));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String extractOutSection(String out, String head) {
|
||||||
|
String[] split = out.split("\n");
|
||||||
|
String xout = "";
|
||||||
|
for (String s : split) {
|
||||||
|
if (!s.startsWith("jshell>") && !s.equals("")) {
|
||||||
|
if (s.startsWith("INFO") || s.startsWith("ERROR") || s.startsWith("WARN")) {
|
||||||
|
if (s.indexOf("(") > 0) {
|
||||||
|
s = s.substring(0, s.lastIndexOf("(")).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xout += s + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xout.split(head)[1].split("---")[0].replace("jshell>", "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
record MemDump(long address, byte[] data) {}
|
||||||
|
|
||||||
|
protected MemDump parseHexDump(String dump) throws IOException {
|
||||||
|
// First, get the address. Assume contiguous, so only need top line.
|
||||||
|
List<String> lines = List.of(dump.split("\n"));
|
||||||
|
String bytes = lines.get(0);
|
||||||
|
bytes = bytes.substring(bytes.indexOf("{") + 1, bytes.indexOf("}"));
|
||||||
|
List<String> toks = List.of(bytes.split(",\\s+"));
|
||||||
|
String addrstr = lines.get(1);
|
||||||
|
long address = Long.parseLong(addrstr, 16);
|
||||||
|
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
byte[] lineData = new byte[toks.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (String t : toks) {
|
||||||
|
lineData[i++] = Byte.parseByte(t.trim());
|
||||||
|
}
|
||||||
|
buf.write(lineData);
|
||||||
|
return new MemDump(address, buf.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ManagedDomainObject openDomainObject(String path) throws Exception {
|
||||||
|
DomainFile dfx = waitForPass(() -> {
|
||||||
|
DomainFile df = env.getProject().getProjectData().getFile(path);
|
||||||
|
assertNotNull(df);
|
||||||
|
return df;
|
||||||
|
}, TIMEOUT_SECONDS * 1000, 500);
|
||||||
|
return new ManagedDomainObject(dfx, false, false, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ManagedDomainObject waitDomainObject(String path) throws Exception {
|
||||||
|
DomainFile df;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (true) {
|
||||||
|
df = env.getProject().getProjectData().getFile(path);
|
||||||
|
if (df != null) {
|
||||||
|
return new ManagedDomainObject(df, false, false, monitor);
|
||||||
|
}
|
||||||
|
Thread.sleep(1000);
|
||||||
|
if (System.currentTimeMillis() - start > 30000) {
|
||||||
|
throw new TimeoutException("30 seconds expired waiting for domain file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitTxDone() {
|
||||||
|
waitForCondition(() -> tb.trace.getCurrentTransactionInfo() == null,
|
||||||
|
() -> "Error waiting for transaction to finish");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T waitForPass(Supplier<T> supplier) {
|
||||||
|
var locals = new Object() {
|
||||||
|
AssertionError lastError;
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
waitForCondition(() -> {
|
||||||
|
try {
|
||||||
|
locals.value = supplier.get();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (AssertionError e) {
|
||||||
|
locals.lastError = e;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, () -> locals.lastError.getMessage());
|
||||||
|
return locals.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void waitForCondition(BooleanSupplier condition,
|
||||||
|
Supplier<String> failureMessageSupplier) throws AssertionFailedError {
|
||||||
|
|
||||||
|
int totalTime = 0;
|
||||||
|
while (totalTime <= DEFAULT_WAIT_TIMEOUT * 10) {
|
||||||
|
|
||||||
|
if (condition.getAsBoolean()) {
|
||||||
|
return; // success
|
||||||
|
}
|
||||||
|
|
||||||
|
totalTime += sleep(DEFAULT_WAIT_DELAY * 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
String failureMessage = "Timed-out waiting for condition";
|
||||||
|
if (failureMessageSupplier != null) {
|
||||||
|
failureMessage = failureMessageSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionFailedError(failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void waitForPass(Runnable runnable, long timeoutMs, long retryDelayMs) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
AssertionError lastError = null;
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (AssertionError e) {
|
||||||
|
lastError = e;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(retryDelayMs);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
// Retry sooner, I guess.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastError == null) {
|
||||||
|
throw new AssertionError("Timed out before first try?");
|
||||||
|
}
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T waitForPass(Supplier<T> supplier, long timeoutMs, long retryDelayMs) {
|
||||||
|
var locals = new Object() {
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
waitForPass(() -> {
|
||||||
|
locals.value = supplier.get();
|
||||||
|
}, timeoutMs, retryDelayMs);
|
||||||
|
return locals.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getMaxSnap() {
|
||||||
|
Long maxSnap = tb.trace.getTimeManager().getMaxSnap();
|
||||||
|
return maxSnap == null ? 0 : maxSnap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceObject waitForObject(String path) {
|
||||||
|
return waitForPass(() -> {
|
||||||
|
TraceObject obj = tb.objAny(path, Lifespan.at(getMaxSnap()));
|
||||||
|
assertNotNull("Object " + path + " never appeared.", obj);
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<TraceObjectValue> getValues(String path) {
|
||||||
|
return tb.trace.getObjectManager()
|
||||||
|
.getValuePaths(Lifespan.at(getMaxSnap()), PathPredicates.parse(path))
|
||||||
|
.map(p -> p.getLastEntry())
|
||||||
|
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<TraceObjectValue> waitForValuesPass(String path,
|
||||||
|
Consumer<List<TraceObjectValue>> asserter) {
|
||||||
|
return waitForPass(() -> {
|
||||||
|
List<TraceObjectValue> vals = getValues(path);
|
||||||
|
asserter.accept(vals);
|
||||||
|
return vals;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<TraceObjectValue> waitForValues(String path) {
|
||||||
|
return waitForValuesPass(path, vals -> assertFalse(vals.isEmpty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Address getPC() {
|
||||||
|
return (Address) Unique
|
||||||
|
.assertOne(tb.objValues(getMaxSnap(), "VMs[].Threads[main].Stack[0].PC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Address waitForPC(Consumer<Address> asserter) {
|
||||||
|
return waitForPass(() -> {
|
||||||
|
Address pc = getPC();
|
||||||
|
asserter.accept(pc);
|
||||||
|
return pc;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertMatches(String expectedPattern, TraceObject object) {
|
||||||
|
assertTrue("Expected matches " + expectedPattern + " but was " +
|
||||||
|
object.getCanonicalPath().toString(),
|
||||||
|
PathPredicates.parse(expectedPattern).matches(object.getCanonicalPath().getKeyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitForLocation(String clsName, String methodName, long codeIndex) {
|
||||||
|
try {
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[0].Location.Method", methods -> {
|
||||||
|
assertMatches("VMs[].Classes[" + clsName + "].Methods[" + methodName + "]",
|
||||||
|
Unique.assertOne(methods).getChild());
|
||||||
|
});
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[0].Location.Index", indices -> {
|
||||||
|
assertEquals(codeIndex, Unique.assertOne(indices).getValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (AssertionError e) {
|
||||||
|
TraceObjectValue locVal = Unique.assertAtMostOne(
|
||||||
|
getValues("VMs[].Threads[main].Stack[0].Location"));
|
||||||
|
if (locVal == null) {
|
||||||
|
throw new AssertionError("Wrong location. Expected %s.%s:%d but was null"
|
||||||
|
.formatted(clsName, methodName, codeIndex));
|
||||||
|
}
|
||||||
|
TraceObject loc = locVal.getChild();
|
||||||
|
long snap = getMaxSnap();
|
||||||
|
throw new AssertionError("Wrong location. Expected %s.%s:%d but was %s:%d"
|
||||||
|
.formatted(clsName, methodName, codeIndex,
|
||||||
|
loc.getAttribute(snap, "Method").getValue(),
|
||||||
|
loc.getAttribute(snap, "Index").getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,381 @@
|
|||||||
|
/* ###
|
||||||
|
* 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 agent.java.rmi;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
|
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
|
import ghidra.debug.api.tracermi.RemoteMethod;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
|
||||||
|
public class JavaHooksTest extends AbstractJavaTraceRmiTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStep() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshMethod = conn.getMethod("refresh_method");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
conn.execute("VirtualMachine vm = manager.getCurrentVM();");
|
||||||
|
conn.execute("ThreadReference thread = manager.getCurrentThread();");
|
||||||
|
conn.execute("EventRequestManager mgr = vm.eventRequestManager();");
|
||||||
|
conn.execute(
|
||||||
|
"StepRequest req = mgr.createStepRequest(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO);");
|
||||||
|
conn.execute("req.enable();");
|
||||||
|
conn.execute("vm.resume();");
|
||||||
|
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
waitForObject("VMs[].Threads[main]");
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
|
||||||
|
List<TraceObjectValue> pcs =
|
||||||
|
waitForValues("VMs[].Threads[main].Stack[].Location.Method");
|
||||||
|
assertTrue(pcs.get(0).getValue().toString().contains("<init>"));
|
||||||
|
|
||||||
|
TraceObject checkName = pcs.get(0).getChild();
|
||||||
|
refreshMethod.invoke(Map.of("method", checkName));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
AddressRange range = Unique
|
||||||
|
.assertOne(
|
||||||
|
waitForValues(checkName.getCanonicalPath().extend("Range").toString()))
|
||||||
|
.castValue();
|
||||||
|
waitForPC(start -> start.equals(range.getMinAddress()));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("Step",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnMethodEntry() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod breakOnEnter = conn.getMethod("break_enter_thread");
|
||||||
|
RemoteMethod refreshMethod = conn.getMethod("refresh_method");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
breakOnEnter.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(2, eventVals.size());
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("method entry request"));
|
||||||
|
|
||||||
|
List<TraceObjectValue> pcs =
|
||||||
|
waitForValues("VMs[].Threads[main].Stack[].Location.Method");
|
||||||
|
assertTrue(pcs.get(0).getValue().toString().contains("checkName"));
|
||||||
|
assertTrue(pcs.get(1).getValue().toString().contains("<init>"));
|
||||||
|
|
||||||
|
TraceObject checkName = pcs.get(0).getChild();
|
||||||
|
refreshMethod.invoke(Map.of("method", checkName));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
AddressRange range = Unique
|
||||||
|
.assertOne(
|
||||||
|
waitForValues(checkName.getCanonicalPath().extend("Range").toString()))
|
||||||
|
.castValue();
|
||||||
|
waitForPC(start -> start.equals(range.getMinAddress()));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("MethodEntry",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnMethodExit() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod breakOnExit = conn.getMethod("break_exit_thread");
|
||||||
|
RemoteMethod refreshMethod = conn.getMethod("refresh_method");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
breakOnExit.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(2, eventVals.size());
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("method exit request"));
|
||||||
|
|
||||||
|
List<TraceObjectValue> pcs =
|
||||||
|
waitForValues("VMs[].Threads[main].Stack[].Location.Method");
|
||||||
|
assertTrue(pcs.get(0).getValue().toString().contains("checkName"));
|
||||||
|
assertTrue(pcs.get(1).getValue().toString().contains("<init>"));
|
||||||
|
|
||||||
|
TraceObject checkName = pcs.get(0).getChild();
|
||||||
|
refreshMethod.invoke(Map.of("method", checkName));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
AddressRange range = Unique
|
||||||
|
.assertOne(
|
||||||
|
waitForValues(checkName.getCanonicalPath().extend("Range").toString()))
|
||||||
|
.castValue();
|
||||||
|
waitForPC(start -> start.equals(range.getMaxAddress()));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("MethodExit",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnClassLoad() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod breakLoad = conn.getMethod("break_load_container");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
breakLoad.invoke(Map.of("container", events));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(2, eventVals.size());
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("class prepare request"));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("ClassPrepare",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnThreadStart() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod breakStart = conn.getMethod("break_started_container");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
breakStart.invoke(Map.of("container", events));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("thread start request"));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("ThreadStart",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnThreadDeath() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod breakDeath = conn.getMethod("break_death_container");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
breakDeath.invoke(Map.of("container", events));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(2, eventVals.size());
|
||||||
|
assertTrue(eventVals.get(1).getEntryKey().contains("thread death request"));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("ThreadDeath",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnBreakpoint() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod refreshMethod = conn.getMethod("refresh_method");
|
||||||
|
RemoteMethod refreshLocations = conn.getMethod("refresh_locations");
|
||||||
|
RemoteMethod breakOnExecute = conn.getMethod("break_location");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject access =
|
||||||
|
waitForObject("VMs[].Classes[java.lang.Thread].Methods[checkAccess]");
|
||||||
|
refreshMethod.invoke(Map.of("method", access));
|
||||||
|
waitTxDone();
|
||||||
|
TraceObject locCont =
|
||||||
|
waitForObject("VMs[].Classes[java.lang.Thread].Methods[checkAccess].Locations");
|
||||||
|
refreshLocations.invoke(Map.of("container", locCont));
|
||||||
|
waitTxDone();
|
||||||
|
List<TraceObjectValue> locations = waitForValues(
|
||||||
|
"VMs[].Classes[java.lang.Thread].Methods[checkAccess].Locations[]");
|
||||||
|
for (TraceObjectValue loc : locations) {
|
||||||
|
breakOnExecute.invoke(Map.of("location", loc.getChild()));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> brkVals = waitForValues("VMs[].Breakpoints[]");
|
||||||
|
assertEquals(4, brkVals.size());
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[0]._display",
|
||||||
|
pcs -> assertContainsString("checkAccess", pcs.get(0).getValue().toString()));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("Breakpoint",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnAccessWatchpoint() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod refreshFields = conn.getMethod("refresh_canonical_fields");
|
||||||
|
RemoteMethod breakOnAccess = conn.getMethod("break_access");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject fieldCont = waitForObject("VMs[].Classes[java.lang.Thread].Fields");
|
||||||
|
refreshFields.invoke(Map.of("container", fieldCont));
|
||||||
|
waitTxDone();
|
||||||
|
List<TraceObjectValue> fields =
|
||||||
|
waitForValues("VMs[].Classes[java.lang.Thread].Fields[NEW_THREAD_BINDINGS]");
|
||||||
|
for (TraceObjectValue f : fields) {
|
||||||
|
breakOnAccess.invoke(Map.of("field", f.getChild()));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> brkVals = waitForValues("VMs[].Breakpoints[]");
|
||||||
|
assertEquals(1, brkVals.size());
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
List<TraceObjectValue> pcs = waitForValues("VMs[].Threads[main].Stack[0]._display");
|
||||||
|
assertTrue(pcs.get(0).getValue().toString().contains("<init>"));
|
||||||
|
|
||||||
|
waitForValuesPass("VMs[]._display", vms -> assertContainsString("AccessWatchpoint",
|
||||||
|
vms.get(0).getValue().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(JshellAndConnection conn, String obj) {
|
||||||
|
if (obj != null) {
|
||||||
|
conn.execute("cmds.ghidraTraceStart(\"" + obj + "\");");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.execute("mds.ghidraTraceStart();");
|
||||||
|
}
|
||||||
|
conn.execute("cmds.ghidraTraceCreate(System.getenv());");
|
||||||
|
conn.execute("cmds.ghidraTraceTxStart(\"Create snapshot\")");
|
||||||
|
conn.execute("cmds.ghidraTraceNewSnap(\"Scripted snapshot\")");
|
||||||
|
conn.execute("cmds.ghidraTraceTxCommit()");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void txPut(JshellAndConnection conn, String obj) {
|
||||||
|
conn.execute("cmds.ghidraTraceTxStart(\"Tx\");");
|
||||||
|
conn.execute("cmds.ghidraTracePut" + obj + "();");
|
||||||
|
conn.execute("cmds.ghidraTraceTxCommit();");
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,806 @@
|
|||||||
|
/* ###
|
||||||
|
* 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 agent.java.rmi;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
|
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
|
import ghidra.debug.api.tracermi.RemoteMethod;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
|
||||||
|
public class JavaMethodsTest extends AbstractJavaTraceRmiTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because we control the target artifact, we know the steps precisely. NOTE: This must be the
|
||||||
|
* same at least for step_into and step_over, otherwise, we can't know for sure step_over
|
||||||
|
* actually behaves any differently than step_into. By matching with step_into, we can assure
|
||||||
|
* ourselves that step_over actually encountered an invoke* bytecode instruction, because if it
|
||||||
|
* didn't then step_into ought to fail.
|
||||||
|
*/
|
||||||
|
public void do2Steps(JshellAndConnection conn, RemoteMethod step, TraceObject thread) {
|
||||||
|
step.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
waitForLocation("HelloWorld", "main", 3);
|
||||||
|
|
||||||
|
step.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
waitForLocation("HelloWorld", "main", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvaluate() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
conn.executeCapture(null);
|
||||||
|
List<String> res = conn.executeCapture("3+4*2;");
|
||||||
|
conn.execute("/exit");
|
||||||
|
assertTrue(res.get(0).contains("11"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
// Just confirm it's present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshEvents() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshEvents = conn.getMethod("refresh_events");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
refreshEvents.invoke(Map.of("container", events));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(1, eventVals.size());
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("step request"));
|
||||||
|
|
||||||
|
conn.execute("VirtualMachine vm = manager.getCurrentVM(); ");
|
||||||
|
conn.execute(
|
||||||
|
"MethodEntryRequest brkReq = vm.eventRequestManager().createMethodEntryRequest();");
|
||||||
|
|
||||||
|
eventVals = waitForPass(() -> {
|
||||||
|
refreshEvents.invoke(Map.of("container", events));
|
||||||
|
List<TraceObjectValue> evs = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(2, evs.size());
|
||||||
|
return evs;
|
||||||
|
});
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("method entry request"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshBreakpoints() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
conn.execute("VirtualMachine vm = manager.getCurrentVM();");
|
||||||
|
conn.execute("Location loc = manager.getCurrentLocation();");
|
||||||
|
conn.execute(
|
||||||
|
"BreakpointRequest brkReq = vm.eventRequestManager().createBreakpointRequest(loc);");
|
||||||
|
|
||||||
|
TraceObject brkSet = waitForObject("VMs[].Breakpoints");
|
||||||
|
List<TraceObjectValue> brks = waitForPass(() -> {
|
||||||
|
refreshBreakpoints.invoke(Map.of("container", brkSet));
|
||||||
|
List<TraceObjectValue> d = waitForValues("VMs[].Breakpoints[]");
|
||||||
|
assertEquals(1, d.size());
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
assertTrue(brks.get(0).getEntryKey().contains("breakpoint request"));
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"String path = \"VMs[OpenJDK 64-Bit Server VM].Classes[java.lang.Thread]\";");
|
||||||
|
conn.execute(
|
||||||
|
"ReferenceType reftype = (ReferenceType) jdiManager.objForPath(path);");
|
||||||
|
conn.execute("Field field = reftype.fieldByName(\"tid\");");
|
||||||
|
conn.execute(
|
||||||
|
"AccessWatchpointRequest brkReq = vm.eventRequestManager().createAccessWatchpointRequest(field);");
|
||||||
|
|
||||||
|
TraceObject brkSet2 = waitForObject("VMs[].Breakpoints");
|
||||||
|
brks = waitForPass(() -> {
|
||||||
|
refreshBreakpoints.invoke(Map.of("container", brkSet2));
|
||||||
|
List<TraceObjectValue> d = waitForValues("VMs[].Breakpoints[]");
|
||||||
|
assertEquals(2, d.size());
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
assertTrue(brks.get(0).getEntryKey().contains("access watchpoint request"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshVMs() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshVM = conn.getMethod("refresh_vm");
|
||||||
|
RemoteMethod refreshProcess = conn.getMethod("refresh_process");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "VMs");
|
||||||
|
|
||||||
|
TraceObject vm = waitForObject("VMs[]");
|
||||||
|
List<TraceObjectValue> children = waitForValues("VMs[].");
|
||||||
|
int sz = children.size();
|
||||||
|
refreshVM.invoke(Map.of("vm", vm));
|
||||||
|
children = waitForValues("VMs[].");
|
||||||
|
assertTrue(children.size() >= sz);
|
||||||
|
List<TraceObjectValue> eventThread = waitForValues("VMs[]._event_thread");
|
||||||
|
assertTrue(eventThread.get(0).getValue().toString().contains("main"));
|
||||||
|
|
||||||
|
TraceObject proc = waitForObject("VMs[].Processes[]");
|
||||||
|
refreshProcess.invoke(Map.of("process", proc));
|
||||||
|
children = waitForValues("VMs[].Processes[].");
|
||||||
|
assertTrue(children.get(0).getEntryKey().contains("Alive"));
|
||||||
|
children = waitForValues("VMs[].Processes[].CommandLine");
|
||||||
|
assertTrue(((String) children.get(0).getValue()).contains("HelloWorld"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshThreads() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "VMs");
|
||||||
|
|
||||||
|
TraceObject threads = waitForObject("VMs[].Threads");
|
||||||
|
refreshThreads.invoke(Map.of("container", threads));
|
||||||
|
List<TraceObjectValue> children = waitForValues("VMs[].Threads[]");
|
||||||
|
assertEquals(4, children.size());
|
||||||
|
assertTrue(children.get(children.size() - 1).getEntryKey().contains("main"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshStack() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod brkEnter = conn.getMethod("break_enter_container");
|
||||||
|
RemoteMethod setClsFilt = conn.getMethod("set_class_filter");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
brkEnter.invoke(Map.of("container", events));
|
||||||
|
TraceObject evtEnter = waitForObject("VMs[].Events[]");
|
||||||
|
setClsFilt.invoke(Map.of(
|
||||||
|
"event", evtEnter,
|
||||||
|
"filter", "HelloWorld*",
|
||||||
|
"exclude", false));
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject stack = waitForObject("VMs[].Threads[main].Stack");
|
||||||
|
refreshStack.invoke(Map.of("stack", stack));
|
||||||
|
waitTxDone();
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[]",
|
||||||
|
frames -> assertEquals(1, frames.size()));
|
||||||
|
|
||||||
|
// Because main is static, there is no preceding call to <init>
|
||||||
|
// This class has no <clinit>, so we get main first!
|
||||||
|
waitForLocation("HelloWorld", "main", 0);
|
||||||
|
Address start = getPC();
|
||||||
|
|
||||||
|
do2Steps(conn, stepInto, thread);
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
refreshStack.invoke(Map.of("stack", stack));
|
||||||
|
waitTxDone();
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[]", frames -> {
|
||||||
|
assertEquals(2, frames.size());
|
||||||
|
});
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[].PC", pcs -> {
|
||||||
|
assertEquals(2, pcs.size());
|
||||||
|
assertEquals(start.add(5), pcs.get(1).getValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshRegisters() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceObject regContainer = waitForObject("VMs[].Threads[main].Stack[0].Registers");
|
||||||
|
refreshRegisters.invoke(Map.of("container", regContainer));
|
||||||
|
List<TraceObjectValue> registers =
|
||||||
|
waitForValues("VMs[].Threads[main].Stack[0].Registers[]");
|
||||||
|
assertEquals(2, registers.size());
|
||||||
|
assertTrue(registers.get(0).getEntryKey().contains("PC"));
|
||||||
|
assertTrue(registers.get(1).getEntryKey().contains("return_address"));
|
||||||
|
|
||||||
|
waitForObject("VMs[].Threads[main].Stack[1].Registers");
|
||||||
|
refreshRegisters.invoke(Map.of("container", regContainer));
|
||||||
|
registers = waitForValues("VMs[].Threads[main].Stack[1].Registers[]");
|
||||||
|
assertEquals(1, registers.size());
|
||||||
|
assertTrue(registers.get(0).getEntryKey().contains("PC"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test Too slow for testing.
|
||||||
|
public void testRefreshMemory() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshMemory = conn.getMethod("refresh_memory");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> children = waitForValues("VMs[].Memory[]");
|
||||||
|
assertTrue(children.size() < 100);
|
||||||
|
|
||||||
|
TraceObject memory = waitForObject("VMs[].Memory");
|
||||||
|
refreshMemory.invoke(Map.of("memory", memory));
|
||||||
|
children = waitForValues("VMs[].Memory[]");
|
||||||
|
assertTrue(children.size() > 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test Too slow for testing.
|
||||||
|
public void testRefreshClasses() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshClasses = conn.getMethod("refresh_reference_types");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> children = waitForValues("VMs[].Classes[]");
|
||||||
|
assertEquals(1, children.size());
|
||||||
|
|
||||||
|
TraceObject classes = waitForObject("VMs[].Classes");
|
||||||
|
refreshClasses.invoke(Map.of("container", classes));
|
||||||
|
children = waitForValues("VMs[].Classes[]");
|
||||||
|
assertTrue(children.size() > 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefreshModules() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> children = waitForValues("VMs[].ModuleRefs[]");
|
||||||
|
assertTrue(children.size() < 100);
|
||||||
|
int sz = children.size();
|
||||||
|
|
||||||
|
TraceObject modules = waitForObject("VMs[].ModuleRefs");
|
||||||
|
refreshModules.invoke(Map.of("container", modules));
|
||||||
|
children = waitForValues("VMs[].ModuleRefs[]");
|
||||||
|
assertEquals(sz, children.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivate() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "VMs");
|
||||||
|
|
||||||
|
TraceObject threads = waitForObject("VMs[].Threads");
|
||||||
|
refreshThreads.invoke(Map.of("container", threads));
|
||||||
|
List<TraceObjectValue> children = waitForValues("VMs[].Threads[]");
|
||||||
|
assertEquals(4, children.size());
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
TraceObject obj = traceManager.getCurrentObject();
|
||||||
|
assertFalse(obj == null);
|
||||||
|
assertContainsString("[main]", obj.getCanonicalPath().toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"cmds.ghidraTraceActivate(\"VMs[OpenJDK 64-Bit Server VM].Threads[Finalizer]\")");
|
||||||
|
waitTxDone();
|
||||||
|
Thread.sleep(1000); // Why?
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
TraceObject obj = traceManager.getCurrentObject();
|
||||||
|
assertFalse(obj == null);
|
||||||
|
assertContainsString("[Finalizer]", obj.getCanonicalPath().toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Test Unclear how to test
|
||||||
|
public void testKill() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod kill = conn.getMethod("kill");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "VMs");
|
||||||
|
|
||||||
|
TraceObject vm = waitForObject("VMs[]");
|
||||||
|
kill.invoke(Map.of("vm", vm));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Still a race condition between the last call to putFrames and the final assert")
|
||||||
|
public void testStepInto() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: If using break_load_container, you'd need to follow with set_source_filter
|
||||||
|
* using "HelloWorld*". I still haven't looked far enough to figure out what the exact
|
||||||
|
* match should be. (It's not "HelloWorld", but maybe it's "HelloWorld.class". I've just
|
||||||
|
* switched over to break_enter_container, instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
RemoteMethod brkEnter = conn.getMethod("break_enter_container");
|
||||||
|
RemoteMethod setClsFilt = conn.getMethod("set_class_filter");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
brkEnter.invoke(Map.of("container", events));
|
||||||
|
TraceObject evtEnter = waitForObject("VMs[].Events[]");
|
||||||
|
setClsFilt.invoke(Map.of(
|
||||||
|
"event", evtEnter,
|
||||||
|
"filter", "HelloWorld*",
|
||||||
|
"exclude", false));
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
// Because main is static, there is no preceding call to <init>
|
||||||
|
// This class has no <clinit>, so we get main first!
|
||||||
|
waitForLocation("HelloWorld", "main", 0);
|
||||||
|
Address start = waitForPC(pc -> assertNotEquals(0, pc.getOffset()));
|
||||||
|
|
||||||
|
do2Steps(conn, stepInto, thread);
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
|
||||||
|
waitForLocation("java.io.PrintStream", "println", 0);
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[0].Registers[return_address]",
|
||||||
|
rets -> {
|
||||||
|
long ret = Long.parseLong(Unique.assertOne(rets).castValue(), 16);
|
||||||
|
assertEquals(start.getOffset() + 8, ret);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Still a race condition between the last call to putFrames and the final assert")
|
||||||
|
public void testStepOver() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod brkEnter = conn.getMethod("break_enter_container");
|
||||||
|
RemoteMethod setClsFilt = conn.getMethod("set_class_filter");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod stepOver = conn.getMethod("step_over");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
brkEnter.invoke(Map.of("container", events));
|
||||||
|
TraceObject evtEnter = waitForObject("VMs[].Events[]");
|
||||||
|
setClsFilt.invoke(Map.of(
|
||||||
|
"event", evtEnter,
|
||||||
|
"filter", "HelloWorld*",
|
||||||
|
"exclude", false));
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
waitForLocation("HelloWorld", "main", 0);
|
||||||
|
Address start = waitForPC(pc -> assertNotEquals(0, pc.getOffset()));
|
||||||
|
|
||||||
|
do2Steps(conn, stepOver, thread);
|
||||||
|
|
||||||
|
stepOver.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
|
||||||
|
waitForLocation("HelloWorld", "main", 8);
|
||||||
|
waitForPC(pc -> assertEquals(start.add(8), pc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Still a race condition between the last call to putFrames and the final assert")
|
||||||
|
public void testStepOut() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod brkEnter = conn.getMethod("break_enter_container");
|
||||||
|
RemoteMethod setClsFilt = conn.getMethod("set_class_filter");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod stepOut = conn.getMethod("step_out");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject events = waitForObject("VMs[].Events");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
brkEnter.invoke(Map.of("container", events));
|
||||||
|
TraceObject evtEnter = waitForObject("VMs[].Events[]");
|
||||||
|
setClsFilt.invoke(Map.of(
|
||||||
|
"event", evtEnter,
|
||||||
|
"filter", "HelloWorld*",
|
||||||
|
"exclude", false));
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
// Because main is static, there is no preceding call to <init>
|
||||||
|
// This class has no <clinit>, so we get main first!
|
||||||
|
waitForLocation("HelloWorld", "main", 0);
|
||||||
|
Address start = waitForPC(pc -> assertNotEquals(0, pc.getOffset()));
|
||||||
|
|
||||||
|
do2Steps(conn, stepInto, thread);
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
waitForLocation("java.io.PrintStream", "println", 0);
|
||||||
|
|
||||||
|
stepOut.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
waitForLocation("HelloWorld", "main", 8);
|
||||||
|
waitForPC(pc -> assertEquals(start.add(8), pc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBreakByEvent() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod breakOnEnter = conn.getMethod("break_enter_thread");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
breakOnEnter.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> eventVals = waitForValues("VMs[].Events[]");
|
||||||
|
assertEquals(2, eventVals.size());
|
||||||
|
assertTrue(eventVals.get(0).getEntryKey().contains("method entry request"));
|
||||||
|
|
||||||
|
List<TraceObjectValue> pcs =
|
||||||
|
waitForValues("VMs[].Threads[main].Stack[].Location.Method");
|
||||||
|
assertTrue(pcs.get(0).getValue().toString().contains("checkName"));
|
||||||
|
assertTrue(pcs.get(1).getValue().toString().contains("<init>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBreakByLocation() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod refreshMethod = conn.getMethod("refresh_method");
|
||||||
|
RemoteMethod refreshLocations = conn.getMethod("refresh_locations");
|
||||||
|
RemoteMethod breakOnExecute = conn.getMethod("break_location");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject access =
|
||||||
|
waitForObject("VMs[].Classes[java.lang.Thread].Methods[checkAccess]");
|
||||||
|
refreshMethod.invoke(Map.of("method", access));
|
||||||
|
waitTxDone();
|
||||||
|
TraceObject locCont =
|
||||||
|
waitForObject("VMs[].Classes[java.lang.Thread].Methods[checkAccess].Locations");
|
||||||
|
refreshLocations.invoke(Map.of("container", locCont));
|
||||||
|
waitTxDone();
|
||||||
|
List<TraceObjectValue> locations = waitForValues(
|
||||||
|
"VMs[].Classes[java.lang.Thread].Methods[checkAccess].Locations[]");
|
||||||
|
for (TraceObjectValue loc : locations) {
|
||||||
|
breakOnExecute.invoke(Map.of("location", loc.getChild()));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> brkVals = waitForValues("VMs[].Breakpoints[]");
|
||||||
|
assertEquals(4, brkVals.size());
|
||||||
|
//assertTrue(eventVals.get(0).getEntryKey().contains("method entry request"));
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
waitForValuesPass("VMs[].Threads[main].Stack[0]._display",
|
||||||
|
pcs -> assertContainsString("checkAccess", pcs.get(0).getValue().toString()));
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBreakOnAccess() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
RemoteMethod resume = conn.getMethod("resume_thread");
|
||||||
|
RemoteMethod refreshFields = conn.getMethod("refresh_canonical_fields");
|
||||||
|
RemoteMethod breakOnAccess = conn.getMethod("break_access");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
TraceObject fieldCont = waitForObject("VMs[].Classes[java.lang.Thread].Fields");
|
||||||
|
refreshFields.invoke(Map.of("container", fieldCont));
|
||||||
|
waitTxDone();
|
||||||
|
List<TraceObjectValue> fields =
|
||||||
|
waitForValues("VMs[].Classes[java.lang.Thread].Fields[NEW_THREAD_BINDINGS]");
|
||||||
|
for (TraceObjectValue f : fields) {
|
||||||
|
breakOnAccess.invoke(Map.of("field", f.getChild()));
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
resume.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
List<TraceObjectValue> brkVals = waitForValues("VMs[].Breakpoints[]");
|
||||||
|
assertEquals(1, brkVals.size());
|
||||||
|
//assertTrue(eventVals.get(0).getEntryKey().contains("method entry request"));
|
||||||
|
|
||||||
|
txPut(conn, "Frames");
|
||||||
|
List<TraceObjectValue> pcs = waitForValues("VMs[].Threads[main].Stack[0]._display");
|
||||||
|
assertTrue(pcs.get(0).getValue().toString().contains("<init>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToggleBreakpoint() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
|
||||||
|
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
conn.execute("VirtualMachine vm = manager.getCurrentVM();");
|
||||||
|
conn.execute("Location loc = manager.getCurrentLocation();");
|
||||||
|
conn.execute(
|
||||||
|
"BreakpointRequest brkReq = vm.eventRequestManager().createBreakpointRequest(loc);");
|
||||||
|
|
||||||
|
TraceObject brkPts = waitForObject("VMs[].Breakpoints");
|
||||||
|
List<TraceObjectValue> descs = waitForPass(() -> {
|
||||||
|
refreshBreakpoints.invoke(Map.of("container", brkPts));
|
||||||
|
List<TraceObjectValue> d = waitForValues("VMs[].Breakpoints[]._display");
|
||||||
|
assertEquals(1, d.size());
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(descs.get(0).getValue().toString().contains("breakpoint request"));
|
||||||
|
assertTrue(descs.get(0).getValue().toString().contains("disabled"));
|
||||||
|
|
||||||
|
TraceObject brk = waitForObject("VMs[].Breakpoints[]");
|
||||||
|
toggleBreakpoint.invoke(Map.of("breakpoint", brk));
|
||||||
|
|
||||||
|
refreshBreakpoints.invoke(Map.of("container", brkPts));
|
||||||
|
descs = waitForValues("VMs[].Breakpoints[]._display");
|
||||||
|
assertTrue(descs.get(0).getValue().toString().contains("enabled"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteBreakpoint() throws Exception {
|
||||||
|
try (JshellAndConnection conn = startAndConnectJshell()) {
|
||||||
|
start(conn, "HelloWorld.class");
|
||||||
|
|
||||||
|
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
|
||||||
|
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
|
||||||
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/HelloWorld.class")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
txPut(conn, "Threads");
|
||||||
|
|
||||||
|
TraceObject thread = waitForObject("VMs[].Threads[main]");
|
||||||
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
waitTxDone();
|
||||||
|
|
||||||
|
conn.execute("VirtualMachine vm = manager.getCurrentVM();");
|
||||||
|
conn.execute("Location loc = manager.getCurrentLocation();");
|
||||||
|
conn.execute(
|
||||||
|
"BreakpointRequest brkReq = vm.eventRequestManager().createBreakpointRequest(loc);");
|
||||||
|
|
||||||
|
TraceObject brkPts = waitForObject("VMs[].Breakpoints");
|
||||||
|
waitForPass(() -> {
|
||||||
|
refreshBreakpoints.invoke(Map.of("container", brkPts));
|
||||||
|
List<TraceObjectValue> d = waitForValues("VMs[].Breakpoints[]._display");
|
||||||
|
assertEquals(1, d.size());
|
||||||
|
});
|
||||||
|
|
||||||
|
TraceObject brk = waitForObject("VMs[].Breakpoints[]");
|
||||||
|
deleteBreakpoint.invoke(Map.of("breakpoint", brk));
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
TraceObject bpts = waitForObject("VMs[].Breakpoints");
|
||||||
|
refreshBreakpoints.invoke(Map.of("container", bpts));
|
||||||
|
Collection<? extends TraceObjectValue> elements =
|
||||||
|
bpts.getElements(Lifespan.at(getMaxSnap()));
|
||||||
|
assertTrue(elements.isEmpty());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(JshellAndConnection conn, String obj) {
|
||||||
|
if (obj != null) {
|
||||||
|
conn.execute("cmds.ghidraTraceStart(\"" + obj + "\");");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.execute("mds.ghidraTraceStart();");
|
||||||
|
}
|
||||||
|
conn.execute("cmds.ghidraTraceCreate(System.getenv());");
|
||||||
|
conn.execute("cmds.ghidraTraceTxStart(\"Create snapshot\")");
|
||||||
|
conn.execute("cmds.ghidraTraceNewSnap(\"Scripted snapshot\")");
|
||||||
|
conn.execute("cmds.ghidraTraceTxCommit()");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void txPut(JshellAndConnection conn, String obj) {
|
||||||
|
conn.execute("cmds.ghidraTraceTxStart(\"Tx\");");
|
||||||
|
conn.execute("cmds.ghidraTracePut" + obj + "();");
|
||||||
|
conn.execute("cmds.ghidraTraceTxCommit();");
|
||||||
|
waitTxDone();
|
||||||
|
}
|
||||||
|
}
|
@ -89,7 +89,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
|||||||
start(conn, "%s".formatted(cloneExit));
|
start(conn, "%s".formatted(cloneExit));
|
||||||
conn.execute("break set -n work");
|
conn.execute("break set -n work");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
TraceObject proc = tb.objAny("Processes[]");
|
TraceObject proc = tb.objAny0("Processes[]");
|
||||||
assertNotNull(proc);
|
assertNotNull(proc);
|
||||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -119,7 +119,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
|||||||
conn.execute("break set -n work");
|
conn.execute("break set -n work");
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
TraceObject inf = tb.objAny("Processes[]");
|
TraceObject inf = tb.objAny0("Processes[]");
|
||||||
assertNotNull(inf);
|
assertNotNull(inf);
|
||||||
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -131,7 +131,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
|||||||
conn.execute("continue");
|
conn.execute("continue");
|
||||||
waitStopped(conn.conn);
|
waitStopped(conn.conn);
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
TraceObject inf = tb.objAny("Processes[]");
|
TraceObject inf = tb.objAny0("Processes[]");
|
||||||
assertNotNull(inf);
|
assertNotNull(inf);
|
||||||
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -283,7 +283,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
|||||||
conn.execute("cont");
|
conn.execute("cont");
|
||||||
waitRunning(conn.conn);
|
waitRunning(conn.conn);
|
||||||
|
|
||||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
TraceObject proc = waitForValue(() -> tb.objAny0("Processes[]"));
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -297,7 +297,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
|||||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||||
start(conn, getSpecimenPrint());
|
start(conn, getSpecimenPrint());
|
||||||
|
|
||||||
TraceObject inf = waitForValue(() -> tb.objAny("Processes[]"));
|
TraceObject inf = waitForValue(() -> tb.objAny0("Processes[]"));
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
@ -318,7 +318,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
|||||||
assertNotNull(snapshot);
|
assertNotNull(snapshot);
|
||||||
assertEquals("Exited with code 72", snapshot.getDescription());
|
assertEquals("Exited with code 72", snapshot.getDescription());
|
||||||
|
|
||||||
TraceObject proc = tb.objAny("Processes[]");
|
TraceObject proc = tb.objAny0("Processes[]");
|
||||||
assertNotNull(proc);
|
assertNotNull(proc);
|
||||||
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
||||||
assertThat(val, instanceOf(Number.class));
|
assertThat(val, instanceOf(Number.class));
|
||||||
|
@ -79,7 +79,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject available = Objects.requireNonNull(tb.objAny("Available"));
|
TraceObject available = Objects.requireNonNull(tb.objAny0("Available"));
|
||||||
|
|
||||||
refreshAvailable.invoke(Map.of("node", available));
|
refreshAvailable.invoke(Map.of("node", available));
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
conn.execute("breakpoint set --name main");
|
conn.execute("breakpoint set --name main");
|
||||||
conn.execute("breakpoint set -H --name main");
|
conn.execute("breakpoint set -H --name main");
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject breakpoints = Objects.requireNonNull(tb.objAny("Breakpoints"));
|
TraceObject breakpoints = Objects.requireNonNull(tb.objAny0("Breakpoints"));
|
||||||
refreshBreakpoints.invoke(Map.of("node", breakpoints));
|
refreshBreakpoints.invoke(Map.of("node", breakpoints));
|
||||||
|
|
||||||
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
|
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
|
||||||
@ -145,7 +145,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject locations =
|
TraceObject locations =
|
||||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
Objects.requireNonNull(tb.objAny0("Processes[].Breakpoints"));
|
||||||
conn.execute("breakpoint set --name main");
|
conn.execute("breakpoint set --name main");
|
||||||
conn.execute("breakpoint set -H --name main");
|
conn.execute("breakpoint set -H --name main");
|
||||||
refreshProcBreakpoints.invoke(Map.of("node", locations));
|
refreshProcBreakpoints.invoke(Map.of("node", locations));
|
||||||
@ -183,7 +183,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject locations =
|
TraceObject locations =
|
||||||
Objects.requireNonNull(tb.objAny("Processes[].Watchpoints"));
|
Objects.requireNonNull(tb.objAny0("Processes[].Watchpoints"));
|
||||||
conn.execute("watchpoint set expression -s 1 -- `(void(*)())main`");
|
conn.execute("watchpoint set expression -s 1 -- `(void(*)())main`");
|
||||||
conn.execute("watchpoint set expression -s 1 -w read -- `(void(*)())main`+-0x20");
|
conn.execute("watchpoint set expression -s 1 -w read -- `(void(*)())main`+-0x20");
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@ -230,7 +230,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
|
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject processes = Objects.requireNonNull(tb.objAny("Processes"));
|
TraceObject processes = Objects.requireNonNull(tb.objAny0("Processes"));
|
||||||
|
|
||||||
refreshProcesses.invoke(Map.of("node", processes));
|
refreshProcesses.invoke(Map.of("node", processes));
|
||||||
|
|
||||||
@ -254,7 +254,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject env = Objects.requireNonNull(tb.objAny(path));
|
TraceObject env = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshEnvironment.invoke(Map.of("node", env));
|
refreshEnvironment.invoke(Map.of("node", env));
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
|
TraceObject threads = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshThreads.invoke(Map.of("node", threads));
|
refreshThreads.invoke(Map.of("node", threads));
|
||||||
|
|
||||||
@ -302,7 +302,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
waitTxDone();
|
waitTxDone();
|
||||||
|
|
||||||
txPut(conn, "frames");
|
txPut(conn, "frames");
|
||||||
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
|
TraceObject stack = Objects.requireNonNull(tb.objAny0(path));
|
||||||
refreshStack.invoke(Map.of("node", stack));
|
refreshStack.invoke(Map.of("node", stack));
|
||||||
|
|
||||||
// Would be nice to control / validate the specifics
|
// Would be nice to control / validate the specifics
|
||||||
@ -355,7 +355,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
|
TraceObject memory = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshMappings.invoke(Map.of("node", memory));
|
refreshMappings.invoke(Map.of("node", memory));
|
||||||
|
|
||||||
@ -377,7 +377,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
|
TraceObject modules = Objects.requireNonNull(tb.objAny0(path));
|
||||||
|
|
||||||
refreshModules.invoke(Map.of("node", modules));
|
refreshModules.invoke(Map.of("node", modules));
|
||||||
|
|
||||||
@ -474,7 +474,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc2 = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
removeProcess.invoke(Map.of("process", proc2));
|
removeProcess.invoke(Map.of("process", proc2));
|
||||||
|
|
||||||
String out = conn.executeCapture("target list");
|
String out = conn.executeCapture("target list");
|
||||||
@ -545,7 +545,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
detach.invoke(Map.of("process", proc));
|
detach.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
String out = conn.executeCapture("target list");
|
String out = conn.executeCapture("target list");
|
||||||
@ -565,7 +565,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
launch.invoke(Map.ofEntries(
|
launch.invoke(Map.ofEntries(
|
||||||
Map.entry("process", proc),
|
Map.entry("process", proc),
|
||||||
Map.entry("file", getSpecimenPrint())));
|
Map.entry("file", getSpecimenPrint())));
|
||||||
@ -587,7 +587,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/noname")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
launch.invoke(Map.ofEntries(
|
launch.invoke(Map.ofEntries(
|
||||||
Map.entry("process", proc),
|
Map.entry("process", proc),
|
||||||
Map.entry("file", getSpecimenRead())));
|
Map.entry("file", getSpecimenRead())));
|
||||||
@ -619,7 +619,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
kill.invoke(Map.of("process", proc));
|
kill.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
String out = conn.executeCapture("target list");
|
String out = conn.executeCapture("target list");
|
||||||
@ -677,7 +677,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
stepToCall(conn, step_into, thread);
|
stepToCall(conn, step_into, thread);
|
||||||
|
|
||||||
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
|
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
|
||||||
@ -704,7 +704,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
stepToCall(conn, step_over, thread);
|
stepToCall(conn, step_over, thread);
|
||||||
|
|
||||||
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
|
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
|
||||||
@ -733,7 +733,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
String dis3 = conn.executeCapture("disassemble -c3 -s '$pc'");
|
String dis3 = conn.executeCapture("disassemble -c3 -s '$pc'");
|
||||||
// TODO: Examine for control transfer?
|
// TODO: Examine for control transfer?
|
||||||
List<String> lines = List.of(dis3.split("\n"));
|
List<String> lines = List.of(dis3.split("\n"));
|
||||||
@ -766,7 +766,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
|
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
activate.invoke(Map.of("thread", thread));
|
activate.invoke(Map.of("thread", thread));
|
||||||
|
|
||||||
int initDepth = getDepth(conn);
|
int initDepth = getDepth(conn);
|
||||||
@ -796,7 +796,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
|
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny0("Processes[].Threads[]"));
|
||||||
activate.invoke(Map.of("thread", thread));
|
activate.invoke(Map.of("thread", thread));
|
||||||
|
|
||||||
int initDepth = getDepth(conn);
|
int initDepth = getDepth(conn);
|
||||||
@ -819,7 +819,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
|
|
||||||
@ -861,7 +861,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
|
|
||||||
@ -903,7 +903,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||||
AddressRange range = tb.range(address, address + 0); // length 1
|
AddressRange range = tb.range(address, address + 0); // length 1
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
@ -949,7 +949,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||||
AddressRange range = tb.range(address, address + 0); // length 1
|
AddressRange range = tb.range(address, address + 0); // length 1
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
@ -995,7 +995,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped(conn);
|
waitStopped(conn);
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny0("Processes[]"));
|
||||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||||
AddressRange range = tb.range(address, address + 0); // length 1
|
AddressRange range = tb.range(address, address + 0); // length 1
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
@ -1063,7 +1063,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
|
|
||||||
conn.execute("breakpoint set -n main");
|
conn.execute("breakpoint set -n main");
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
|
TraceObject bpt = Objects.requireNonNull(tb.objAny0("Breakpoints[]"));
|
||||||
|
|
||||||
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
||||||
|
|
||||||
@ -1087,7 +1087,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
conn.execute("breakpoint set -n main");
|
conn.execute("breakpoint set -n main");
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
|
|
||||||
TraceObject loc = Objects.requireNonNull(tb.objAny("Breakpoints[][]"));
|
TraceObject loc = Objects.requireNonNull(tb.objAny0("Breakpoints[][]"));
|
||||||
|
|
||||||
toggleBreakpointLocation.invoke(Map.of("location", loc, "enabled", false));
|
toggleBreakpointLocation.invoke(Map.of("location", loc, "enabled", false));
|
||||||
|
|
||||||
@ -1110,7 +1110,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
|
|
||||||
conn.execute("breakpoint set -n main");
|
conn.execute("breakpoint set -n main");
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
|
TraceObject bpt = Objects.requireNonNull(tb.objAny0("Breakpoints[]"));
|
||||||
|
|
||||||
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
||||||
|
|
||||||
@ -1140,7 +1140,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
|||||||
assertThat(out, containsString(Long.toHexString(address)));
|
assertThat(out, containsString(Long.toHexString(address)));
|
||||||
|
|
||||||
txPut(conn, "watchpoints");
|
txPut(conn, "watchpoints");
|
||||||
TraceObject wpt = Objects.requireNonNull(tb.objAny("Processes[].Watchpoints[]"));
|
TraceObject wpt = Objects.requireNonNull(tb.objAny0("Processes[].Watchpoints[]"));
|
||||||
|
|
||||||
deleteWatchpoint.invoke(Map.of("watchpoint", wpt));
|
deleteWatchpoint.invoke(Map.of("watchpoint", wpt));
|
||||||
|
|
||||||
|
@ -26,13 +26,22 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
|
import generic.jar.ResourceFile;
|
||||||
|
import ghidra.app.plugin.core.analysis.AnalysisBackgroundCommand;
|
||||||
|
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.services.TraceRmiLauncherService;
|
import ghidra.app.services.TraceRmiLauncherService;
|
||||||
|
import ghidra.app.util.importer.AutoImporter;
|
||||||
|
import ghidra.app.util.importer.MessageLog;
|
||||||
|
import ghidra.app.util.opinion.LoadResults;
|
||||||
import ghidra.debug.api.ValStr;
|
import ghidra.debug.api.ValStr;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
|
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
|
||||||
|
import ghidra.framework.Application;
|
||||||
import ghidra.framework.OperatingSystem;
|
import ghidra.framework.OperatingSystem;
|
||||||
|
import ghidra.framework.cmd.Command;
|
||||||
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.task.ConsoleTaskMonitor;
|
import ghidra.util.task.ConsoleTaskMonitor;
|
||||||
|
|
||||||
public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebuggerTest {
|
public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
@ -68,6 +77,20 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetClassName() throws Exception {
|
||||||
|
ResourceFile rf = Application.getModuleDataFile("TestResources", "HelloWorld.class");
|
||||||
|
LoadResults<Program> results = AutoImporter.importByUsingBestGuess(rf.getFile(false),
|
||||||
|
env.getProject(), "/", this, new MessageLog(), monitor);
|
||||||
|
program = results.getPrimaryDomainObject();
|
||||||
|
AutoAnalysisManager analyzer = AutoAnalysisManager.getAnalysisManager(program);
|
||||||
|
analyzer.reAnalyzeAll(null);
|
||||||
|
Command<Program> cmd = new AnalysisBackgroundCommand(analyzer, false);
|
||||||
|
tool.execute(cmd, program);
|
||||||
|
waitForBusyTool(tool);
|
||||||
|
assertEquals("HelloWorld", TraceRmiLauncherServicePlugin.tryProgramJvmClass(program));
|
||||||
|
}
|
||||||
|
|
||||||
// @Test // This is currently hanging the test machine. The gdb process is left running
|
// @Test // This is currently hanging the test machine. The gdb process is left running
|
||||||
public void testLaunchLocalGdb() throws Exception {
|
public void testLaunchLocalGdb() throws Exception {
|
||||||
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
|
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
|
||||||
|
Loading…
Reference in New Issue
Block a user