mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-4760: Implement TraceRmi for JPDA (Java/Dalvik targets)
This commit is contained in:
parent
62819ff301
commit
6d39e7231c
50 changed files with 8324 additions and 440 deletions
|
@ -733,94 +733,93 @@ python3 -m pip install --no-index -f Debugger-rmi-trace\pypkg\dist -f Debugger-a
|
|||
Alternatively, if you are trying to quit, but typed "<TT>.exit</TT>", just type
|
||||
"<TT>quit()</TT>" to terminate the session.</P>
|
||||
|
||||
<H3><A name="dbgeng_ext"></A>dbgeng-ext</H3>
|
||||
<H3><A name="dbgeng_ext"></A>dbgeng-ext</H3>
|
||||
|
||||
<P>This launcher extends the base dbgeng launcher adding extra options (a la IDebugClient's CreateProcess2).</P>
|
||||
<P>This launcher extends the base dbgeng launcher adding extra options (a la IDebugClient's
|
||||
CreateProcess2).</P>
|
||||
|
||||
<H4>Options</H4>
|
||||
|
||||
<H4>Options</H4>
|
||||
<UL>
|
||||
<LI><B>Dir</B>: This is the starting directory for the process.</LI>
|
||||
|
||||
<UL>
|
||||
<LI><B>Dir</B>: This is the starting directory for the process.</LI>
|
||||
<LI><B>Env</B>: This is a composite string containg Environment Variable entries delineated
|
||||
by '/0' separators. For example, you could redefine USERNAME and USERPROFILE with the entry
|
||||
'USERNAME=SomeUser/0USERPROFILE=C:\Users\SomeUser'.</LI>
|
||||
|
||||
<LI><B>Env</B>: This is a composite string containg Environment Variable entries
|
||||
delineated by '/0' separators. For example, you could redefine USERNAME and USERPROFILE
|
||||
with the entry 'USERNAME=SomeUser/0USERPROFILE=C:\Users\SomeUser'.</LI>
|
||||
<LI><B>CreateFlags</B>: Flags used when creating the process, typically either
|
||||
DEBUG_PROCESS(1) or DEBUG_ONLY_THIS_PROCESS(2) if you do not wish to follow spawned
|
||||
processes. Other possible values are defined by processes.h's
|
||||
CreateProcessCreationFlags.</LI>
|
||||
|
||||
<LI><B>CreateFlags</B>: Flags used when creating the process, typically either DEBUG_PROCESS(1) or
|
||||
DEBUG_ONLY_THIS_PROCESS(2) if you do not wish to follow spawned processes. Other possible values
|
||||
are defined by processes.h's CreateProcessCreationFlags.</LI>
|
||||
<LI><B>CreateFlags (Engine)</B>: Engine-specific flags used when creating the process
|
||||
(defined in dbgeng.h). Typically, these are set to 0.</LI>
|
||||
|
||||
<LI><B>CreateFlags (Engine)</B>: Engine-specific flags used when creating the process (defined in dbgeng.h).
|
||||
Typically, these are set to 0.</LI>
|
||||
<LI><B>VerifierFlags (Engine)</B>: Flags used by the Application Verifier. Typically unused,
|
||||
but, if desired, CreateEngineFlags must include
|
||||
DEBUG_ECREATE_PROCESS_USE_VERIFIER_FLAGS(2).</LI>
|
||||
</UL>
|
||||
|
||||
<LI><B>VerifierFlags (Engine)</B>: Flags used by the Application Verifier. Typically unused, but, if desired,
|
||||
CreateEngineFlags must include DEBUG_ECREATE_PROCESS_USE_VERIFIER_FLAGS(2).</LI>
|
||||
</UL>
|
||||
<H3><A name="dbgeng_attach"></A>dbgeng-attach</H3>
|
||||
|
||||
|
||||
<H3><A name="dbgeng_attach"></A>dbgeng-attach</H3>
|
||||
<P>This launcher allows the user to attach to a local running process. Options are the same as
|
||||
those for the base dbgeng, except for ProcessId and AttachFlags</P>
|
||||
|
||||
<P>This launcher allows the user to attach to a local running process. Options are the same as those for the base dbgeng, except for ProcessId and AttachFlags</P>
|
||||
<H4>Options</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>ProcessId</B>: The pid of the process you wish to attach to.</LI>
|
||||
|
||||
<H4>Options</H4>
|
||||
<LI><B>AttachFlags</B>: Flags used when attaching to the target process, typically
|
||||
DEBUG_ATTACH_PROCESS(0). Other possible values are defined in dbgeng.h and determine whether
|
||||
the attach should be invasive or not and the status of the process after attaching.</LI>
|
||||
</UL>
|
||||
|
||||
<UL>
|
||||
<LI><B>ProcessId</B>: The pid of the process you wish to attach to.</LI>
|
||||
<H3><A name="dbgeng_remote"></A>dbgeng-remote</H3>
|
||||
|
||||
<LI><B>AttachFlags</B>: Flags used when attaching to the target process, typically DEBUG_ATTACH_PROCESS(0). Other possible values
|
||||
are defined in dbgeng.h and determine whether the attach should be invasive or not
|
||||
and the status of the process after attaching.</LI>
|
||||
<P>This launcher extends the base dbgeng launcher adding an option for connecting through a
|
||||
remote process server.</P>
|
||||
|
||||
</UL>
|
||||
<H4>Options</H4>
|
||||
|
||||
|
||||
<H3><A name="dbgeng_remote"></A>dbgeng-remote</H3>
|
||||
|
||||
<P>This launcher extends the base dbgeng launcher adding an option for connecting through a remote process server.
|
||||
</P>
|
||||
|
||||
|
||||
<H4>Options</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>Connection</B>: This is the connection string specifying the transport options for
|
||||
communicating with the remote server. A typical example might be 'tcp:port=12345,server=192.168.0.2''
|
||||
for a process server launched on the machine at 192.168.0.2 using:
|
||||
<UL>
|
||||
<LI>
|
||||
<B>Connection</B>: This is the connection string specifying the transport options for
|
||||
communicating with the remote server. A typical example might be
|
||||
'tcp:port=12345,server=192.168.0.2'' for a process server launched on the machine at
|
||||
192.168.0.2 using:
|
||||
<PRE>
|
||||
dbgsrv -t tcp:port=12345
|
||||
</PRE>
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
|
||||
<H3><A name="dbgeng_kernel"></A>dbgeng-kernel</H3>
|
||||
<H3><A name="dbgeng_kernel"></A>dbgeng-kernel</H3>
|
||||
|
||||
<P>This version of the dbgeng should be used for kernel-debugging of a remote machine. Options are the
|
||||
same as the base dbgeng, except for the connection-string arguments. For remote
|
||||
debugging, the target machine should be booted with the appropriate options, set using BCDEDIT or the
|
||||
equivalent, such as:
|
||||
</P>
|
||||
<UL style='list-style-type: none'><LI>
|
||||
<P>This version of the dbgeng should be used for kernel-debugging of a remote machine. Options
|
||||
are the same as the base dbgeng, except for the connection-string arguments. For remote
|
||||
debugging, the target machine should be booted with the appropriate options, set using BCDEDIT
|
||||
or the equivalent, such as:</P>
|
||||
|
||||
<UL style='list-style-type: none'>
|
||||
<LI>
|
||||
<PRE>
|
||||
bcdedit /debug ON
|
||||
bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1
|
||||
</PRE>
|
||||
</LI></UL>
|
||||
<P>
|
||||
where IP= the address of the machine runing Ghidra.
|
||||
</P>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
<H4>Options</H4>
|
||||
<P>where IP= the address of the machine runing Ghidra.</P>
|
||||
|
||||
<UL>
|
||||
<LI><B>Arguments</B>: This is the connection string specifying the transport options for
|
||||
communicating with the remote target. A typical example might be 'net:port=54321,key=1.1.1.1'.'
|
||||
</LI>
|
||||
</UL>
|
||||
<H4>Options</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>Arguments</B>: This is the connection string specifying the transport options for
|
||||
communicating with the remote target. A typical example might be
|
||||
'net:port=54321,key=1.1.1.1'.'</LI>
|
||||
</UL>
|
||||
|
||||
|
||||
<H3><A name="dbgeng_ttd"></A>TTD (Time-Travel Debugging)</H3>
|
||||
|
||||
<P>This is a nascent extension to our launcher for the Windows Debugger. The launcher itself
|
||||
|
@ -854,6 +853,88 @@ bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1
|
|||
contain <TT>dbgmodel.dll</TT> and the scripts that implement TTD. These are most easily
|
||||
obtained by installing WinDbg Preview or later.</P>
|
||||
|
||||
<H2>Stock Java Launchers</H2>
|
||||
|
||||
<P>The following launchers based on the Java Debugger are included out of the box:</P>
|
||||
|
||||
<H3><A name="java"></A>java launch</H3>
|
||||
|
||||
<P>This launcher uses the native Java Debug Interface (JDI) to launch the current
|
||||
<TT>.class</TT> file.</P>
|
||||
|
||||
<H4><A name="java_setup"></A>Setup</H4>
|
||||
|
||||
<P>You must have Java installed on the local system. No additional setup is required.</P>
|
||||
|
||||
<H4>Options</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>Arguments</B>: These are the command-line arguments to pass into the target.</LI>
|
||||
|
||||
<LI><B>Arch</B>: The architecture (currently, either "JVM" or "Dalvik").</LI>
|
||||
|
||||
<LI><B>Suspend</B>: Should the target be suspended on launch.</LI>
|
||||
|
||||
<LI><B>Include virtual threads</B>: As described.</LI>
|
||||
|
||||
<LI><B>JShell cmd</B>: If desired, the path to the jshell binary that will host
|
||||
execution.</LI>
|
||||
</UL>
|
||||
|
||||
<H3><A name="java_attach"></A>java attach port</H3>
|
||||
|
||||
<P>This launcher uses the native Java Debug Interface (JDI) to attach to a running java program
|
||||
launched with an open Java Debug Wire Port (JDWP), e.g.:</P>
|
||||
|
||||
<UL style="list-style-type: none">
|
||||
<LI>
|
||||
<PRE>
|
||||
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:54321 Target.class
|
||||
</PRE>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
<H4><A name="java_attach_setup"></A>Setup</H4>
|
||||
|
||||
<P>Identical to that for the java launcher.</P>
|
||||
|
||||
<H4>Options</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>Arch</B>: The architecture (currently, either "JVM" or "Dalvik").</LI>
|
||||
|
||||
<LI><B>Host</B>: The host IP where the target is running.</LI>
|
||||
|
||||
<LI><B>Port</B>: The open JDWP port used by the target.</LI>
|
||||
|
||||
<LI><B>Timeout</B>: How long to wait for a connection attempt.</LI>
|
||||
|
||||
<LI><B>JShell cmd</B>: If desired, the path to the jshell binary that will host
|
||||
execution.</LI>
|
||||
</UL>
|
||||
|
||||
<H3><A name="java_by_pid"></A>java attach PID</H3>
|
||||
|
||||
<P>This launcher uses the native Java Debug Interface (JDI) to attach to a running java program
|
||||
launched with a Java Debug Wire Port (JDWP) identified by process id.</P>
|
||||
|
||||
<H4><A name="java_bypid_setup"></A>Setup</H4>
|
||||
|
||||
<P>Identical to that for the java launcher.</P>
|
||||
|
||||
<H4>Options</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>Arch</B>: The architecture (currently, either "JVM" or "Dalvik").</LI>
|
||||
|
||||
<LI><B>Pid</B>: The target process's ID.</LI>
|
||||
|
||||
<LI><B>Timeout</B>: How long to wait for a connection attempt.</LI>
|
||||
|
||||
<LI><B>JShell cmd</B>: If desired, the path to the jshell binary that will host
|
||||
execution.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Development and Diagnostic Launchers</H2>
|
||||
|
||||
<P>We currently provide one launcher for Trace RMI API exploration and development:</P>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/* ###
|
||||
* 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.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
|
||||
public class DefaultMemoryMapper implements MemoryMapper {
|
||||
|
||||
private final AddressFactory factory;
|
||||
|
||||
public DefaultMemoryMapper(LanguageID id) {
|
||||
LanguageService langServ = DefaultLanguageService.getLanguageService();
|
||||
try {
|
||||
Language lang = langServ.getLanguage(id);
|
||||
this.factory = lang.getAddressFactory();
|
||||
}
|
||||
catch (LanguageNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address map(Address address) {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address mapBack(Address address) {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address genAddr(String space, long offset) {
|
||||
return factory.getAddressSpace(space).getAddress(offset);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* ###
|
||||
* 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.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
|
||||
public class DefaultRegisterMapper implements RegisterMapper {
|
||||
|
||||
public DefaultRegisterMapper(LanguageID id) {
|
||||
// Nothing so far
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapName(String name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapNameBack(String name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisterValue mapValue(String name, RegisterValue rv) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisterValue mapValueBack(String name, RegisterValue rv) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* ###
|
||||
* 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 com.sun.jdi.VirtualMachine;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
public interface MemoryMapper {
|
||||
|
||||
public Address map(Address address);
|
||||
|
||||
public Address mapBack(Address address);
|
||||
|
||||
public Address genAddr(String space, long offset);
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/* ###
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.protobuf.AbstractMessage;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ProtobufSocket<T extends AbstractMessage> {
|
||||
public interface Decoder<T> {
|
||||
T decode(ByteBuffer buf) throws InvalidProtocolBufferException;
|
||||
}
|
||||
|
||||
private final ByteBuffer lenSend = ByteBuffer.allocate(4);
|
||||
private final ByteBuffer lenRecv = ByteBuffer.allocate(4);
|
||||
private final SocketChannel channel;
|
||||
private final Decoder<T> decoder;
|
||||
|
||||
public ProtobufSocket(SocketChannel channel, Decoder<T> decoder) {
|
||||
this.channel = channel;
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
public void send(T msg) throws IOException {
|
||||
synchronized (lenSend) {
|
||||
lenSend.clear();
|
||||
lenSend.putInt(msg.getSerializedSize());
|
||||
lenSend.flip();
|
||||
channel.write(lenSend);
|
||||
for (ByteBuffer buf : msg.toByteString().asReadOnlyByteBufferList()) {
|
||||
channel.write(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T recv() throws IOException {
|
||||
synchronized (lenRecv) {
|
||||
lenRecv.clear();
|
||||
while (lenRecv.hasRemaining()) {
|
||||
channel.read(lenRecv);
|
||||
}
|
||||
lenRecv.flip();
|
||||
int len = lenRecv.getInt();
|
||||
// This is just for testing, so littering on the heap is okay.
|
||||
ByteBuffer buf = ByteBuffer.allocate(len);
|
||||
while (buf.hasRemaining()) {
|
||||
channel.read(buf);
|
||||
}
|
||||
buf.flip();
|
||||
return decoder.decode(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Unable to close ProtobufSocket");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* ###
|
||||
* 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.program.model.lang.RegisterValue;
|
||||
|
||||
public interface RegisterMapper {
|
||||
|
||||
public String mapName(String name);
|
||||
|
||||
public String mapNameBack(String name);
|
||||
|
||||
public RegisterValue mapValue(String name, RegisterValue rv);
|
||||
|
||||
public RegisterValue mapValueBack(String name, RegisterValue rv);
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RmiBatch {
|
||||
|
||||
|
||||
private int refCount = 0;
|
||||
private Set<Object> futures = new HashSet<>();
|
||||
|
||||
public void inc() {
|
||||
refCount++;
|
||||
}
|
||||
|
||||
public int dec() {
|
||||
return --refCount;
|
||||
}
|
||||
|
||||
public void append(Object f) {
|
||||
futures.add(f);
|
||||
}
|
||||
|
||||
public Object results() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,731 @@
|
|||
/* ###
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.*;
|
||||
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.rmi.trace.TraceRmi;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.rmi.trace.TraceRmi.Language;
|
||||
import ghidra.rmi.trace.TraceRmi.Value.Builder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class RmiClient {
|
||||
private final ProtobufSocket<RootMessage> socket;
|
||||
private final String description;
|
||||
private int nextTraceId = 0;
|
||||
private RmiBatch currentBatch = null;
|
||||
|
||||
Map<Integer, RmiTrace> traces = new HashMap<>();
|
||||
private SchemaContext schemaContext;
|
||||
private RmiMethodHandlerThread handler;
|
||||
private static RmiMethodRegistry methodRegistry;
|
||||
private Deque<RootMessage> requests = new LinkedList<>();
|
||||
|
||||
public static TargetObjectSchema loadSchema(String resourceName, String rootName) {
|
||||
XmlSchemaContext schemaContext;
|
||||
|
||||
try {
|
||||
InputStream resourceAsStream = RmiClient.class.getResourceAsStream(resourceName);
|
||||
schemaContext = XmlSchemaContext
|
||||
.deserialize(resourceAsStream);
|
||||
return schemaContext.getSchema(schemaContext.name(rootName));
|
||||
}
|
||||
catch (JDOMException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// public static TargetObjectSchema getSchema(String name) {
|
||||
// try {
|
||||
// return SCHEMA_CTX.getSchema(new SchemaName(name));
|
||||
// } catch (NullPointerException e) {
|
||||
// System.err.println("Possibly non-existent schema: "+name);
|
||||
// return SCHEMA_CTX.getSchema(new SchemaName("OBJECT"));
|
||||
// }
|
||||
// }
|
||||
|
||||
public static enum TraceRmiResolution {
|
||||
RES_ADJUST("adjust", Resolution.CR_ADJUST), //
|
||||
RES_DENY("deny", Resolution.CR_DENY), //
|
||||
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE), //
|
||||
;
|
||||
|
||||
TraceRmiResolution(String val, TraceRmi.Resolution description) {
|
||||
this.val = val;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public final String val;
|
||||
public final Resolution description;
|
||||
}
|
||||
|
||||
public static enum TraceRmiValueKinds {
|
||||
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES), //
|
||||
ELEMENTS("elements", ValueKinds.VK_ELEMENTS), //
|
||||
BOTH("both", ValueKinds.VK_BOTH), //
|
||||
;
|
||||
|
||||
TraceRmiValueKinds(String val, TraceRmi.ValueKinds description) {
|
||||
this.val = val;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public final String val;
|
||||
public final ValueKinds description;
|
||||
}
|
||||
|
||||
public RmiClient(SocketChannel channel, String description) {
|
||||
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
|
||||
this.description = description;
|
||||
this.handler = new RmiMethodHandlerThread(this, socket);
|
||||
handler.start();
|
||||
}
|
||||
|
||||
public ProtobufSocket<RootMessage> getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
handler.close();
|
||||
socket.close();
|
||||
}
|
||||
|
||||
private void send(RootMessage msg) {
|
||||
try {
|
||||
requests.push(msg);
|
||||
socket.send(msg);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RmiTrace createTrace(String path, LanguageID language, CompilerSpecID compiler) {
|
||||
if (compiler == null) {
|
||||
compiler = new CompilerSpecID("default");
|
||||
}
|
||||
RmiTrace trace = new RmiTrace(this, nextTraceId);
|
||||
traces.put(nextTraceId, trace);
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(nextTraceId++))
|
||||
.setLanguage(Language.newBuilder()
|
||||
.setId(language.getIdAsString()))
|
||||
.setCompiler(Compiler.newBuilder()
|
||||
.setId(compiler.getIdAsString()))
|
||||
.setPath(FilePath.newBuilder()
|
||||
.setPath(path)))
|
||||
.build());
|
||||
return trace;
|
||||
}
|
||||
|
||||
public void closeTrace(int id) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestCloseTrace(RequestCloseTrace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(id)))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void saveTrace(int id) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestSaveTrace(RequestSaveTrace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(id)))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void startTx(int traceId, String desc, boolean undoable, int txId) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestStartTx(RequestStartTx.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setTxid(TxId.newBuilder().setId(txId))
|
||||
.setDescription(desc)
|
||||
.setUndoable(undoable))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void endTx(int traceId, int txId, boolean abort) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestEndTx(RequestEndTx.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setTxid(TxId.newBuilder().setId(txId))
|
||||
.setAbort(abort))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void snapshot(int traceId, String desc, String datetime, long snap) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestSnapshot(RequestSnapshot.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder()
|
||||
.setSnap(snap))
|
||||
.setDatetime(datetime)
|
||||
.setDescription(desc))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void createOverlaySpace(int traceId, String base, String name) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestCreateOverlay(RequestCreateOverlaySpace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setBaseSpace(base)
|
||||
.setName(name))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void putBytes(int traceId, long snap, Address start, byte[] data) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestPutBytes(RequestPutBytes.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder().setSnap(snap))
|
||||
.setStart(Addr.newBuilder()
|
||||
.setSpace(start.getAddressSpace().getName())
|
||||
.setOffset(start.getOffset()))
|
||||
.setData(ByteString.copyFrom(data)))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void setMemoryState(int traceId, long snap, AddressRange range, MemoryState state) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestSetMemoryState(RequestSetMemoryState.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder().setSnap(snap))
|
||||
.setRange(AddrRange.newBuilder()
|
||||
.setSpace(range.getAddressSpace().getName())
|
||||
.setOffset(range.getMinAddress().getOffset())
|
||||
.setExtend(range.getLength() - 1))
|
||||
.setState(state))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void deleteBytes(int traceId, long snap, AddressRange range) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestDeleteBytes(RequestDeleteBytes.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder().setSnap(snap))
|
||||
.setRange(AddrRange.newBuilder()
|
||||
.setSpace(range.getAddressSpace().getName())
|
||||
.setOffset(range.getMinAddress().getOffset())
|
||||
.setExtend(range.getLength())))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void putRegisters(int traceId, long snap, String ppath, RegisterValue[] values) {
|
||||
RequestPutRegisterValue.Builder builder = RequestPutRegisterValue.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder().setSnap(snap))
|
||||
.setSpace(ppath);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
RegisterValue rv = values[i];
|
||||
ByteString val = ByteString.copyFrom(rv.toBytes());
|
||||
rv.getUnsignedValue();
|
||||
builder.addValues(i, RegVal.newBuilder()
|
||||
.setName(rv.getRegister().getName())
|
||||
.setValue(val));
|
||||
}
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestPutRegisterValue(builder)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void deleteRegisters(int traceId, long snap, String ppath, String[] names) {
|
||||
RequestDeleteRegisterValue.Builder builder = RequestDeleteRegisterValue.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder().setSnap(snap))
|
||||
.setSpace(ppath);
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
String name = names[i];
|
||||
builder.setNames(i, name);
|
||||
}
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestDeleteRegisterValue(builder)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void createRootObject(int traceId, SchemaContext schemContext, String schema) {
|
||||
this.schemaContext = schemContext;
|
||||
String xmlCtx = XmlSchemaContext.serialize(schemContext);
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestCreateRootObject(RequestCreateRootObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSchemaContext(xmlCtx)
|
||||
.setRootSchema(schema))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void createObject(int traceId, String path) {
|
||||
//System.err.println("createObject:"+path);
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestCreateObject(RequestCreateObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setPath(ObjPath.newBuilder().setPath(path)))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void insertObject(int traceId, String path, Lifespan span, Resolution r) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestInsertObject(RequestInsertObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path)))
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setResolution(r))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void insertObject(int traceId, ObjSpec object, Lifespan span, Resolution r) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestInsertObject(RequestInsertObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(object)
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setResolution(r))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void removeObject(int traceId, ObjSpec object, Lifespan span, boolean tree) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(object)
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setTree(tree))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void setValue(int traceId, String ppath, Lifespan span, String key, Object value,
|
||||
String resolution) {
|
||||
Resolution r = resolution == null ? Resolution.CR_ADJUST
|
||||
: TraceRmiResolution.valueOf(resolution).description;
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestSetValue(RequestSetValue.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setValue(ValSpec.newBuilder()
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setParent(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(ppath)))
|
||||
.setKey(key)
|
||||
.setValue(buildValue(value)))
|
||||
.setResolution(r))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void retainValues(int traceId, String ppath, Lifespan span, ValueKinds kinds,
|
||||
Set<String> keys) {
|
||||
RequestRetainValues.Builder builder = RequestRetainValues.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(ppath)))
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setKinds(kinds)
|
||||
.addAllKeys(keys);
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestRetainValues(builder)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void getObject(int traceId, String path) {
|
||||
RequestGetObject.Builder builder = RequestGetObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path)));
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestGetObject(builder)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void getValues(int traceId, Lifespan span, String pattern) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestGetValues(RequestGetValues.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setPattern(ObjPath.newBuilder().setPath(pattern)))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
|
||||
String key) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestGetValuesIntersecting(RequestGetValuesIntersecting.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setBox(Box.newBuilder()
|
||||
.setSpan(Span.newBuilder()
|
||||
.setMin(span.lmin())
|
||||
.setMax(span.lmax()))
|
||||
.setRange(AddrRange.newBuilder()
|
||||
.setSpace(range.getAddressSpace().getName())
|
||||
.setOffset(range.getMinAddress().getOffset())
|
||||
.setExtend(range.getLength())))
|
||||
.setKey(key))
|
||||
.build());
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectId(int traceId, Long id) {
|
||||
return RmiTraceObject.fromId(traces.get(traceId), id);
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectPath(int traceId, String path) {
|
||||
return RmiTraceObject.fromPath(traces.get(traceId), path);
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectPath(int traceId, Long id, String path) {
|
||||
return new RmiTraceObject(traces.get(traceId), id, path);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Builder buildValue(Object value) {
|
||||
Builder builder = Value.newBuilder();
|
||||
if (value instanceof String str) {
|
||||
return builder.setStringValue(str);
|
||||
}
|
||||
if (value instanceof Boolean bval) {
|
||||
return builder.setBoolValue(bval);
|
||||
}
|
||||
if (value instanceof Short sval) {
|
||||
return builder.setShortValue(sval);
|
||||
}
|
||||
if (value instanceof Integer ival) {
|
||||
return builder.setIntValue(ival);
|
||||
}
|
||||
if (value instanceof Long lval) {
|
||||
return builder.setLongValue(lval);
|
||||
}
|
||||
if (value instanceof ByteString bstr) {
|
||||
return builder.setBytesValue(bstr);
|
||||
}
|
||||
if (value instanceof Byte b) {
|
||||
return builder.setByteValue(b);
|
||||
}
|
||||
if (value instanceof Character c) {
|
||||
return builder.setCharValue(c);
|
||||
}
|
||||
if (value instanceof Address address) {
|
||||
Addr.Builder addr = Addr.newBuilder()
|
||||
.setSpace(address.getAddressSpace().getName())
|
||||
.setOffset(address.getOffset());
|
||||
return builder.setAddressValue(addr);
|
||||
}
|
||||
if (value instanceof AddressRange range) {
|
||||
AddrRange.Builder rng = AddrRange.newBuilder()
|
||||
.setSpace(range.getAddressSpace().getName())
|
||||
.setOffset(range.getMinAddress().getOffset())
|
||||
.setExtend(range.getLength() - 1);
|
||||
return builder.setRangeValue(rng);
|
||||
}
|
||||
if (value instanceof RmiTraceObject obj) {
|
||||
return builder.setChildSpec(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(obj.getPath())));
|
||||
}
|
||||
if (value instanceof List<?> list) {
|
||||
if (list.get(0) instanceof String) {
|
||||
StringArr.Builder b = StringArr.newBuilder().addAllArr((List<String>) list);
|
||||
return builder.setStringArrValue(b.build());
|
||||
}
|
||||
if (list.get(0) instanceof Boolean) {
|
||||
BoolArr.Builder b = BoolArr.newBuilder().addAllArr((List<Boolean>) list);
|
||||
return builder.setBoolArrValue(b.build());
|
||||
}
|
||||
if (list.get(0) instanceof Short) {
|
||||
ShortArr.Builder b = ShortArr.newBuilder().addAllArr((List<Integer>) list);
|
||||
return builder.setShortArrValue(b.build());
|
||||
}
|
||||
if (list.get(0) instanceof Integer) {
|
||||
IntArr.Builder b = IntArr.newBuilder().addAllArr((List<Integer>) list);
|
||||
return builder.setIntArrValue(b.build());
|
||||
}
|
||||
if (list.get(0) instanceof Long) {
|
||||
LongArr.Builder b = LongArr.newBuilder().addAllArr((List<Long>) list);
|
||||
return builder.setLongArrValue(b.build());
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unhandled type for buildValue: " + value);
|
||||
}
|
||||
|
||||
public void activate(int traceId, String path) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestActivate(RequestActivate.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path))))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void disassemble(int traceId, long snap, Address start) {
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestDisassemble(RequestDisassemble.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder().setSnap(snap))
|
||||
.setStart(Addr.newBuilder()
|
||||
.setSpace(start.getAddressSpace().getName())
|
||||
.setOffset(start.getOffset())))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void negotiate(String desc) {
|
||||
RequestNegotiate.Builder builder = RequestNegotiate.newBuilder()
|
||||
.setVersion(TraceRmiHandler.VERSION)
|
||||
.setDescription(desc);
|
||||
int i = 0;
|
||||
for (RmiRemoteMethod m : methodRegistry.getMap().values()) {
|
||||
Method method = buildMethod(m);
|
||||
builder.addMethods(i++, method);
|
||||
}
|
||||
send(RootMessage.newBuilder()
|
||||
.setRequestNegotiate(builder)
|
||||
.build());
|
||||
}
|
||||
|
||||
private Method buildMethod(RmiRemoteMethod method) {
|
||||
Method.Builder builder = Method.newBuilder()
|
||||
.setName(method.getName())
|
||||
.setDescription(method.getDescription())
|
||||
.setAction(method.getAction())
|
||||
.setDisplay(method.getDisplay());
|
||||
int i = 0;
|
||||
for (RmiRemoteMethodParameter p : method.getParameters()) {
|
||||
MethodParameter param = buildParameter(p);
|
||||
builder.addParameters(i++, param);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private MethodParameter buildParameter(RmiRemoteMethodParameter param) {
|
||||
return MethodParameter.newBuilder()
|
||||
.setName(param.getName())
|
||||
.setDisplay(param.getDisplay())
|
||||
.setDescription(param.getDescription())
|
||||
.setType(param.getType())
|
||||
.setDefaultValue(param.getDefaultValue())
|
||||
.setRequired(param.isRequired())
|
||||
.build();
|
||||
}
|
||||
|
||||
public void handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
|
||||
RmiRemoteMethod rm = getMethod(req.getName());
|
||||
Object[] arglist = new Object[req.getArgumentsCount()];
|
||||
java.lang.reflect.Method m = rm.getMethod();
|
||||
Map<String, MethodArgument> argmap = new HashMap<>();
|
||||
for (int i = 0; i < req.getArgumentsCount(); i++) {
|
||||
MethodArgument arg = req.getArguments(i);
|
||||
argmap.put(arg.getName(), arg);
|
||||
}
|
||||
int i = 0;
|
||||
for (Parameter p : m.getParameters()) {
|
||||
MethodArgument arg = argmap.get(p.getName());
|
||||
if (arg != null) {
|
||||
Object obj = argToObject(traceId, arg);
|
||||
arglist[i++] = obj;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object ret = m.invoke(rm.getContainer(), arglist);
|
||||
if (ret != null) {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setXreplyInvokeMethod(XReplyInvokeMethod.newBuilder()
|
||||
.setReturnValue(buildValue(ret)))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
||||
| IOException e) {
|
||||
String message = e.getMessage();
|
||||
if (message != null) {
|
||||
Msg.error(this, message);
|
||||
try {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setXreplyInvokeMethod(
|
||||
XReplyInvokeMethod.newBuilder().setError(message))
|
||||
.build());
|
||||
}
|
||||
catch (IOException e1) {
|
||||
Msg.error(this, e1.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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()) {
|
||||
return value.getStringValue();
|
||||
}
|
||||
if (value.hasStringArrValue()) {
|
||||
return value.getStringArrValue();
|
||||
}
|
||||
if (value.hasBoolValue()) {
|
||||
return value.getBoolValue();
|
||||
}
|
||||
if (value.hasBoolArrValue()) {
|
||||
return value.getBoolArrValue();
|
||||
}
|
||||
if (value.hasCharValue()) {
|
||||
return value.getCharValue();
|
||||
}
|
||||
if (value.hasCharArrValue()) {
|
||||
return value.getCharArrValue();
|
||||
}
|
||||
if (value.hasShortValue()) {
|
||||
return value.getShortValue();
|
||||
}
|
||||
if (value.hasShortArrValue()) {
|
||||
return value.getShortArrValue();
|
||||
}
|
||||
if (value.hasIntValue()) {
|
||||
return value.getIntValue();
|
||||
}
|
||||
if (value.hasIntArrValue()) {
|
||||
return value.getIntArrValue();
|
||||
}
|
||||
if (value.hasLongValue()) {
|
||||
return value.getLongValue();
|
||||
}
|
||||
if (value.hasLongArrValue()) {
|
||||
return value.getLongArrValue();
|
||||
}
|
||||
if (value.hasAddressValue()) {
|
||||
return decodeAddr(traceId, value.getAddressValue());
|
||||
}
|
||||
if (value.hasRangeValue()) {
|
||||
return decodeRange(traceId, value.getRangeValue());
|
||||
}
|
||||
if (value.hasByteValue()) {
|
||||
return value.getByteValue();
|
||||
}
|
||||
if (value.hasBytesValue()) {
|
||||
return value.getBytesValue();
|
||||
}
|
||||
if (value.hasNullValue()) {
|
||||
return value.getNullValue();
|
||||
}
|
||||
ObjDesc desc = value.getChildDesc();
|
||||
String path = desc.getPath().getPath();
|
||||
return proxyObjectPath(traceId, path);
|
||||
}
|
||||
|
||||
private Address decodeAddr(int id, Addr addr) {
|
||||
RmiTrace trace = traces.get(id);
|
||||
return trace.memoryMapper.genAddr(addr.getSpace(), addr.getOffset());
|
||||
}
|
||||
|
||||
private AddressRange decodeRange(int id, AddrRange rng) {
|
||||
RmiTrace trace = traces.get(id);
|
||||
Address start = trace.memoryMapper.genAddr(rng.getSpace(), rng.getOffset());
|
||||
return new AddressRangeImpl(start, start.add(rng.getExtend()));
|
||||
}
|
||||
|
||||
public void setRegistry(RmiMethodRegistry methodRegistry) {
|
||||
RmiClient.methodRegistry = methodRegistry;
|
||||
}
|
||||
|
||||
public RmiRemoteMethod getMethod(String name) {
|
||||
return methodRegistry.getMap().get(name);
|
||||
}
|
||||
|
||||
public Object startBatch() {
|
||||
if (currentBatch == null) {
|
||||
currentBatch = new RmiBatch();
|
||||
}
|
||||
currentBatch.inc();
|
||||
return currentBatch;
|
||||
}
|
||||
|
||||
public Object endBatch() {
|
||||
RmiBatch cb = null;
|
||||
if (0 == currentBatch.dec()) {
|
||||
cb = currentBatch;
|
||||
currentBatch = null;
|
||||
}
|
||||
if (cb != null) {
|
||||
return cb.results();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TargetObjectSchema getSchema(String schema) {
|
||||
return schemaContext.getSchema(new SchemaName(schema));
|
||||
}
|
||||
|
||||
public RootMessage getRequestsPoll() {
|
||||
return requests.poll();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/* ###
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* ###
|
||||
* 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 java.lang.annotation.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RmiMethodRegistry {
|
||||
|
||||
/**
|
||||
* An annotation for marking remote methods.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface TraceMethod {
|
||||
String action();
|
||||
String display() default "";
|
||||
String description() default "";
|
||||
String schema() default "ANY";
|
||||
}
|
||||
|
||||
Map<String, RmiRemoteMethod> map = new HashMap<>();
|
||||
|
||||
public RmiRemoteMethod getMethod(String key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
public void putMethod(String key, RmiRemoteMethod value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
public Map<String, RmiRemoteMethod> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
public interface RmiMethods {
|
||||
|
||||
// This is a marker class - we need an instance of the methods container to invoke its methods
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/* ###
|
||||
* 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 java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.rmi.trace.TraceRmi.Value;
|
||||
|
||||
public class RmiRemoteMethod {
|
||||
|
||||
private final SchemaContext schemaContext;
|
||||
private String name;
|
||||
private String action;
|
||||
private String display;
|
||||
private String description;
|
||||
private RmiRemoteMethodParameter[] params;
|
||||
private TargetObjectSchema schema;
|
||||
private RmiMethods instance;
|
||||
private Method m;
|
||||
|
||||
public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display, String description, TargetObjectSchema schema, RmiMethods instance, Method m) {
|
||||
this.schemaContext = schemaContext;
|
||||
this.name = name;
|
||||
this.action = action;
|
||||
this.display = display;
|
||||
this.description = description;
|
||||
this.params = new RmiRemoteMethodParameter[m.getParameterCount()];
|
||||
this.schema = schema;
|
||||
this.instance = instance;
|
||||
this.m = m;
|
||||
|
||||
int i = 0;
|
||||
for (Parameter p : m.getParameters()) {
|
||||
TargetObjectSchema pschema = getSchemaFromParameter(p);
|
||||
String pname = p.getName(); // NB: don't change this unless yuou resolve the ordering issues
|
||||
String pdesc = pname;
|
||||
String pdisp = pname;
|
||||
if (i == 0) {
|
||||
RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class);
|
||||
if (annot != null) {
|
||||
pschema = schemaContext.getSchema(new SchemaName(annot.schema()));
|
||||
}
|
||||
pdisp = "Object";
|
||||
}
|
||||
Value pdef = null;
|
||||
TargetMethod.Param pannot = p.getAnnotation(TargetMethod.Param.class);
|
||||
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() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public String getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
public RmiRemoteMethodParameter[] getParameters() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return m;
|
||||
}
|
||||
|
||||
public TargetObjectSchema getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
public RmiMethods getContainer() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* ###
|
||||
* 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.rmi.trace.TraceRmi.*;
|
||||
|
||||
public class RmiRemoteMethodParameter {
|
||||
|
||||
private final String name;
|
||||
private final TargetObjectSchema schema;
|
||||
private final boolean required;
|
||||
private final Value defaultValue;
|
||||
private final String display;
|
||||
private final String description;
|
||||
|
||||
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
|
||||
Value defaultValue, String display, String description) {
|
||||
this.name = name;
|
||||
this.schema = schema;
|
||||
this.required = required;
|
||||
this.defaultValue = defaultValue;
|
||||
this.display = display;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
public ValueType getType() {
|
||||
String schemaName = schema.getName().toString();
|
||||
// if (schemaName.equals("ANY")) {
|
||||
// return ValueType.newBuilder().setName("OBJECT").build();
|
||||
// }
|
||||
return ValueType.newBuilder().setName(schemaName).build();
|
||||
}
|
||||
|
||||
public Value getDefaultValue() {
|
||||
if (defaultValue != null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Value.newBuilder().setNullValue(Null.newBuilder()).build();
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/* ###
|
||||
* 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 java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class RmiTrace {
|
||||
|
||||
final RmiClient client;
|
||||
private final int id;
|
||||
|
||||
private int nextTx = 0;
|
||||
private Object txLock = new Object();
|
||||
private ReadWriteLock snLock = new ReentrantReadWriteLock();
|
||||
|
||||
private Set<String> overlays = new HashSet<>();
|
||||
private long currentSnap = -1;
|
||||
private boolean closed = false;
|
||||
|
||||
public MemoryMapper memoryMapper;
|
||||
public RegisterMapper registerMapper;
|
||||
|
||||
public RmiTrace(RmiClient client, int id) {
|
||||
this.client = client;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
client.closeTrace(id);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
client.saveTrace(id);
|
||||
}
|
||||
|
||||
public RmiTransaction startTx(String description, boolean undoable) {
|
||||
int txid;
|
||||
synchronized(txLock) {
|
||||
txid = nextTx++;
|
||||
}
|
||||
client.startTx(id, description, undoable, txid);
|
||||
return new RmiTransaction(this, txid);
|
||||
}
|
||||
|
||||
public RmiTransaction openTx(String description) {
|
||||
return startTx(description, false);
|
||||
}
|
||||
|
||||
public void endTx(int txid, boolean abort) {
|
||||
client.endTx(id, txid, abort);
|
||||
}
|
||||
|
||||
public long nextSnap() {
|
||||
try (LockHold hold = LockHold.lock(snLock.writeLock())) {
|
||||
return ++currentSnap;
|
||||
}
|
||||
}
|
||||
|
||||
public long snapshot(String description, String datatime, Long snap) {
|
||||
if (snap == null) {
|
||||
snap = nextSnap();
|
||||
}
|
||||
client.snapshot(id, description, datatime, snap.intValue());
|
||||
return snap.longValue();
|
||||
}
|
||||
|
||||
public long getSnap() {
|
||||
try (LockHold hold = LockHold.lock(snLock.readLock())) {
|
||||
return currentSnap;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSnap(long snap) {
|
||||
try (LockHold hold = LockHold.lock(snLock.writeLock())) {
|
||||
this.currentSnap = snap;
|
||||
}
|
||||
}
|
||||
|
||||
public long snapOrCurrent(Long snap) {
|
||||
try (LockHold hold = LockHold.lock(snLock.readLock())) {
|
||||
return snap == null ? this.currentSnap : snap.longValue();
|
||||
}
|
||||
}
|
||||
|
||||
public void createOverlaySpace(String base, String name) {
|
||||
if (overlays.contains(name)) {
|
||||
return;
|
||||
}
|
||||
client.createOverlaySpace(id, base, name);
|
||||
}
|
||||
|
||||
public void createOverlaySpace(Address repl, Address orig) {
|
||||
createOverlaySpace(repl.getAddressSpace().getName(), orig.getAddressSpace().getName());
|
||||
}
|
||||
|
||||
public void putBytes(Address addr, byte[] data, Long snap) {
|
||||
client.putBytes(id, snapOrCurrent(snap), addr, data);
|
||||
}
|
||||
|
||||
public void setMemoryState(AddressRange range, MemoryState state, Long snap) {
|
||||
client.setMemoryState(id, snapOrCurrent(snap), range, state);
|
||||
}
|
||||
|
||||
public void deleteBytes(AddressRange range, Long snap) {
|
||||
client.deleteBytes(id, snapOrCurrent(snap), range);
|
||||
}
|
||||
|
||||
public void putRegisters(String ppath, RegisterValue[] values, Long snap) {
|
||||
client.putRegisters(id, snapOrCurrent(snap), ppath, values);
|
||||
}
|
||||
|
||||
public void deleteRegisters(String ppath, String[] names, Long snap) {
|
||||
client.deleteRegisters(id, snapOrCurrent(snap), ppath, names);
|
||||
}
|
||||
|
||||
public void createRootObject(SchemaContext schemaContext, String schema) {
|
||||
client.createRootObject(id, schemaContext, schema);
|
||||
}
|
||||
|
||||
public void createObject(String path) {
|
||||
client.createObject(id, path);
|
||||
}
|
||||
|
||||
public void handleCreateObject(ReplyCreateObject reply) {
|
||||
RmiTraceObject obj = new RmiTraceObject(this, reply.getObject());
|
||||
try (RmiTransaction tx = startTx("CreateObject", false); LockHold hold = LockHold.lock(snLock.readLock())) {
|
||||
obj.insert(currentSnap, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCreateTrace(ReplyCreateTrace reply) {
|
||||
}
|
||||
|
||||
public void insertObject(String path) {
|
||||
Lifespan span = getLifespan();
|
||||
client.insertObject(id, path, span, Resolution.CR_ADJUST);
|
||||
}
|
||||
|
||||
private Lifespan getLifespan() {
|
||||
try (LockHold hold = LockHold.lock(snLock.readLock())) {
|
||||
return Lifespan.nowOn(currentSnap);
|
||||
}
|
||||
}
|
||||
|
||||
public void setValue(String ppath, String key, Object value) {
|
||||
Lifespan span = getLifespan();
|
||||
client.setValue(id, ppath, span, key, value, null);
|
||||
}
|
||||
|
||||
public void retainValues(String ppath, Set<String> keys, ValueKinds kinds) {
|
||||
Lifespan span = getLifespan();
|
||||
client.retainValues(id, ppath, span, kinds, keys);
|
||||
}
|
||||
|
||||
public void activate(String path) {
|
||||
if (path == null) {
|
||||
Msg.error(this, "Attempt to activate null");
|
||||
}
|
||||
client.activate(id, path);
|
||||
}
|
||||
|
||||
public void disassemble(Address start, Long snap) {
|
||||
client.disassemble(id, snapOrCurrent(snap), start);
|
||||
}
|
||||
|
||||
public void handleInvokeMethod(XRequestInvokeMethod req) {
|
||||
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
|
||||
client.handleInvokeMethod(id, req);
|
||||
}
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectId(Long objectId) {
|
||||
return client.proxyObjectId(id, objectId);
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectPath(String path) {
|
||||
return client.proxyObjectPath(id, path);
|
||||
}
|
||||
|
||||
public RmiTraceObject proxyObjectPath(Long objectId, String path) {
|
||||
return client.proxyObjectPath(id, objectId, path);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.client.tracermi;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
|
||||
public class RmiTraceObject {
|
||||
|
||||
private RmiTrace trace;
|
||||
private ObjSpec spec;
|
||||
private String path;
|
||||
|
||||
public RmiTraceObject(RmiTrace trace, ObjSpec spec) {
|
||||
this.trace = trace;
|
||||
this.spec = spec;
|
||||
this.path = spec.getPath().getPath();
|
||||
}
|
||||
|
||||
public RmiTraceObject(RmiTrace trace, Long id, String path) {
|
||||
this.trace = trace;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static RmiTraceObject fromId(RmiTrace trace, long id) {
|
||||
return new RmiTraceObject(trace, id, null);
|
||||
}
|
||||
|
||||
public static RmiTraceObject fromPath(RmiTrace trace, String path) {
|
||||
return new RmiTraceObject(trace, null, path);
|
||||
}
|
||||
|
||||
public void insert(long snap, Resolution resolution) {
|
||||
if (resolution == null) {
|
||||
resolution = Resolution.CR_ADJUST;
|
||||
}
|
||||
Lifespan span = Lifespan.nowOn(snap);
|
||||
trace.client.insertObject(trace.getId(), spec, span, resolution);
|
||||
}
|
||||
|
||||
public void remove(long snap, boolean tree) {
|
||||
Lifespan span = Lifespan.nowOn(snap);
|
||||
trace.client.removeObject(trace.getId(), spec, span, tree);
|
||||
}
|
||||
|
||||
public void setValue(String key, Object value, long snap, String resolution) {
|
||||
Lifespan span = Lifespan.nowOn(snap);
|
||||
trace.client.setValue(trace.getId(), path, span, key, value, resolution);
|
||||
}
|
||||
|
||||
public void retainValues(Set<String> keys, long snap, ValueKinds kinds) {
|
||||
Lifespan span = Lifespan.nowOn(snap);
|
||||
trace.client.retainValues(trace.getId(), path, span, kinds, keys);
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
trace.client.activate(trace.getId(), path);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* ###
|
||||
* 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 java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import ghidra.util.LockHold;
|
||||
|
||||
public class RmiTransaction implements AutoCloseable {
|
||||
|
||||
private RmiTrace trace;
|
||||
private int id;
|
||||
private ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private boolean closed = false;
|
||||
|
||||
public RmiTransaction(RmiTrace trace, int id) {
|
||||
this.trace = trace;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
trace.endTx(id, false);
|
||||
}
|
||||
|
||||
public void abort() {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
trace.endTx(id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
commit();
|
||||
}
|
||||
|
||||
public RmiTransaction startTx(String description, boolean b) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -94,6 +94,13 @@ public class LaunchFailureDialog extends OptionDialog {
|
|||
result.trace() != null;
|
||||
}
|
||||
|
||||
protected static String shorten(String title) {
|
||||
if (title.length() > 80) {
|
||||
return title.substring(0, 77) + "...";
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
protected static String htmlContent(TerminalSession session) {
|
||||
String content = session.content().trim();
|
||||
List<String> lines = Arrays.asList(content.split("\n"));
|
||||
|
@ -108,7 +115,7 @@ public class LaunchFailureDialog extends OptionDialog {
|
|||
<div style="font:bold;">Title: %s</div>%s
|
||||
<div style="background:black;color:white;">
|
||||
<pre>%s</pre>""".formatted(
|
||||
session.title(),
|
||||
HTMLUtilities.escapeHTML(shorten(session.title())),
|
||||
note,
|
||||
content);
|
||||
}
|
||||
|
|
|
@ -244,6 +244,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
this.plugin = plugin;
|
||||
plugin.addHandler(this);
|
||||
this.socket = socket;
|
||||
if (socket == null) {
|
||||
throw new RuntimeException("Socket cannot be null");
|
||||
}
|
||||
this.in = socket.getInputStream();
|
||||
this.out = socket.getOutputStream();
|
||||
|
||||
|
|
|
@ -391,8 +391,9 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
boolean allowSuitableObject) {
|
||||
Map<String, ActionEntry> result = new HashMap<>();
|
||||
for (RemoteMethod m : methods) {
|
||||
result.put(m.name(), createEntry(m, context, allowContextObject, allowCoordsObject,
|
||||
allowSuitableObject));
|
||||
ActionEntry entry = createEntry(m, context, allowContextObject, allowCoordsObject,
|
||||
allowSuitableObject);
|
||||
result.put(m.name(), entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue