mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-06-02 16:47:43 +08:00
GP-3836: Add Trace RMI 'Connections' pane.
This commit is contained in:
@@ -62,12 +62,15 @@ SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
|
||||
|
||||
# TODO: Symbols
|
||||
|
||||
|
||||
class ErrorWithCode(Exception):
|
||||
def __init__(self,code):
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
def __str__(self)->str:
|
||||
return repr(self.code)
|
||||
|
||||
|
||||
class State(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -115,6 +118,7 @@ class State(object):
|
||||
|
||||
STATE = State()
|
||||
|
||||
|
||||
def ghidra_trace_connect(address=None):
|
||||
"""
|
||||
Connect Python to Ghidra for tracing
|
||||
@@ -124,7 +128,8 @@ def ghidra_trace_connect(address=None):
|
||||
|
||||
STATE.require_no_client()
|
||||
if address is None:
|
||||
raise RuntimeError("'ghidra_trace_connect': missing required argument 'address'")
|
||||
raise RuntimeError(
|
||||
"'ghidra_trace_connect': missing required argument 'address'")
|
||||
|
||||
parts = address.split(':')
|
||||
if len(parts) != 2:
|
||||
@@ -133,7 +138,9 @@ def ghidra_trace_connect(address=None):
|
||||
try:
|
||||
c = socket.socket()
|
||||
c.connect((host, int(port)))
|
||||
STATE.client = Client(c, methods.REGISTRY)
|
||||
# TODO: Can we get version info from the DLL?
|
||||
STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY)
|
||||
print(f"Connected to {STATE.client.description} at {address}")
|
||||
except ValueError:
|
||||
raise RuntimeError("port must be numeric")
|
||||
|
||||
@@ -243,7 +250,8 @@ def ghidra_trace_create(command=None, initial_break=True, timeout=None, start_tr
|
||||
if timeout != None:
|
||||
util.base._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
||||
if initial_break:
|
||||
util.base._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK)
|
||||
util.base._control.AddEngineOptions(
|
||||
DbgEng.DEBUG_ENGINITIAL_BREAK)
|
||||
util.base.wait(timeout)
|
||||
else:
|
||||
util.base.create(command, initial_break)
|
||||
@@ -267,7 +275,7 @@ def ghidra_trace_info():
|
||||
print("Not connected to Ghidra\n")
|
||||
return
|
||||
host, port = STATE.client.s.getpeername()
|
||||
print("Connected to Ghidra at {}:{}\n".format(host, port))
|
||||
print(f"Connected to {STATE.client.description} at {host}:{port}\n")
|
||||
if STATE.trace is None:
|
||||
print("No trace\n")
|
||||
return
|
||||
@@ -565,28 +573,29 @@ def ghidra_trace_remove_obj(path):
|
||||
|
||||
|
||||
def to_bytes(value):
|
||||
return bytes(ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0,len(value)))
|
||||
return bytes(ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0, len(value)))
|
||||
|
||||
|
||||
def to_string(value, encoding):
|
||||
b = bytes(ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0,len(value)))
|
||||
b = bytes(ord(value[i]) if type(value[i]) == str else int(
|
||||
value[i]) for i in range(0, len(value)))
|
||||
return str(b, encoding)
|
||||
|
||||
|
||||
def to_bool_list(value):
|
||||
return [bool(value[i]) for i in range(0,len(value))]
|
||||
return [bool(value[i]) for i in range(0, len(value))]
|
||||
|
||||
|
||||
def to_int_list(value):
|
||||
return [ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0,len(value))]
|
||||
return [ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0, len(value))]
|
||||
|
||||
|
||||
def to_short_list(value):
|
||||
return [ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0,len(value))]
|
||||
return [ord(value[i]) if type(value[i]) == str else int(value[i]) for i in range(0, len(value))]
|
||||
|
||||
|
||||
def to_string_list(value, encoding):
|
||||
return [to_string(value[i], encoding) for i in range(0,len(value))]
|
||||
return [to_string(value[i], encoding) for i in range(0, len(value))]
|
||||
|
||||
|
||||
def eval_value(value, schema=None):
|
||||
@@ -841,13 +850,15 @@ def put_processes(running=False):
|
||||
procobj.set_value('_state', istate)
|
||||
if running == False:
|
||||
procobj.set_value('_pid', p[0])
|
||||
pidstr = ('0x{:x}' if radix == 16 else '0{:o}' if radix == 8 else '{}').format(p[0])
|
||||
pidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(p[0])
|
||||
procobj.set_value('_display', pidstr)
|
||||
procobj.set_value('Name', str(p[1]))
|
||||
procobj.set_value('PEB', hex(p[2]))
|
||||
procobj.insert()
|
||||
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
||||
|
||||
|
||||
def put_state(event_process):
|
||||
STATE.require_no_tx()
|
||||
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
||||
@@ -873,10 +884,10 @@ def ghidra_trace_put_processes():
|
||||
def put_available():
|
||||
radix = util.get_convenience_variable('output-radix')
|
||||
keys = []
|
||||
result = dbg().cmd(".tlist")
|
||||
result = dbg().cmd(".tlist")
|
||||
lines = result.split("\n")
|
||||
for i in lines:
|
||||
i = i.strip();
|
||||
i = i.strip()
|
||||
if i == "":
|
||||
continue
|
||||
if i.startswith("0n") is False:
|
||||
@@ -933,7 +944,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
prot = {4: 'HW_EXECUTE', 2: 'READ', 1: 'WRITE'}[prot]
|
||||
else:
|
||||
width = ' '
|
||||
prot = 'SW_EXECUTE'
|
||||
prot = 'SW_EXECUTE'
|
||||
|
||||
if address is not None: # Implies execution break
|
||||
base, addr = mapper.map(nproc, address)
|
||||
@@ -968,7 +979,6 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
ikeys.append(k)
|
||||
|
||||
|
||||
|
||||
def put_breakpoints():
|
||||
target = util.get_target()
|
||||
nproc = util.selected_process()
|
||||
@@ -1039,9 +1049,12 @@ def put_regions():
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(start_base, start_addr.space)
|
||||
regobj.set_value('_range', start_addr.extend(r.RegionSize))
|
||||
regobj.set_value('_readable', r.Protect == None or r.Protect&0x66 != 0)
|
||||
regobj.set_value('_writable', r.Protect == None or r.Protect&0xCC != 0)
|
||||
regobj.set_value('_executable', r.Protect == None or r.Protect&0xF0 != 0)
|
||||
regobj.set_value('_readable', r.Protect ==
|
||||
None or r.Protect & 0x66 != 0)
|
||||
regobj.set_value('_writable', r.Protect ==
|
||||
None or r.Protect & 0xCC != 0)
|
||||
regobj.set_value('_executable', r.Protect ==
|
||||
None or r.Protect & 0xF0 != 0)
|
||||
regobj.set_value('_offset', hex(r.BaseAddress))
|
||||
regobj.set_value('Base', hex(r.BaseAddress))
|
||||
regobj.set_value('Size', hex(r.RegionSize))
|
||||
@@ -1146,7 +1159,7 @@ def put_threads(running=False):
|
||||
tid = t[0]
|
||||
tobj.set_value('_tid', tid)
|
||||
tidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
||||
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
||||
nproc, i, tidstr))
|
||||
tobj.set_value('_display', compute_thread_display(tidstr, t))
|
||||
@@ -1201,7 +1214,8 @@ def put_frames():
|
||||
fobj.set_value('StackOffset', hex(f.StackOffset))
|
||||
fobj.set_value('ReturnOffset', hex(f.ReturnOffset))
|
||||
fobj.set_value('FrameOffset', hex(f.FrameOffset))
|
||||
fobj.set_value('_display', "#{} {}".format(f.FrameNumber, hex(f.InstructionOffset)))
|
||||
fobj.set_value('_display', "#{} {}".format(
|
||||
f.FrameNumber, hex(f.InstructionOffset)))
|
||||
fobj.insert()
|
||||
STATE.trace.proxy_object_path(STACK_PATTERN.format(
|
||||
procnum=nproc, tnum=nthrd)).retain_values(keys)
|
||||
@@ -1326,7 +1340,7 @@ def repl():
|
||||
dbg().wait()
|
||||
else:
|
||||
pass
|
||||
#dbg().dispatch_events()
|
||||
# dbg().dispatch_events()
|
||||
except KeyboardInterrupt as e:
|
||||
print("")
|
||||
print("You have left the dbgeng REPL and are now at the Python3 interpreter.")
|
||||
|
||||
@@ -201,7 +201,9 @@ def ghidra_trace_connect(address, *, is_mi, **kwargs):
|
||||
try:
|
||||
c = socket.socket()
|
||||
c.connect((host, int(port)))
|
||||
STATE.client = Client(c, methods.REGISTRY)
|
||||
STATE.client = Client(
|
||||
c, "gdb-" + util.GDB_VERSION.full, methods.REGISTRY)
|
||||
print(f"Connected to {STATE.client.description} at {address}")
|
||||
except ValueError:
|
||||
raise gdb.GdbError("port must be numeric")
|
||||
|
||||
@@ -320,9 +322,11 @@ def ghidra_trace_info(*, is_mi, **kwargs):
|
||||
return
|
||||
host, port = STATE.client.s.getpeername()
|
||||
if is_mi:
|
||||
result['connection'] = "{}:{}".format(host, port)
|
||||
result['description'] = STATE.client.description
|
||||
result['address'] = f"{host}:{port}"
|
||||
else:
|
||||
gdb.write("Connected to Ghidra at {}:{}\n".format(host, port))
|
||||
gdb.write(
|
||||
f"Connected to {STATE.client.description} at {host}:{port}\n")
|
||||
if STATE.trace is None:
|
||||
if is_mi:
|
||||
result['tracing'] = False
|
||||
|
||||
+1
-2
@@ -21,12 +21,11 @@ import javax.swing.Icon;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.dbg.DebuggerConsoleLogger;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
@ServiceInfo(defaultProviderName = "ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin")
|
||||
public interface DebuggerConsoleService extends DebuggerConsoleLogger {
|
||||
public interface DebuggerConsoleService {
|
||||
|
||||
/**
|
||||
* Log a message to the console
|
||||
|
||||
+19
@@ -19,6 +19,7 @@ import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.async.AsyncReference;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
@@ -330,6 +331,24 @@ public interface DebuggerTraceManagerService {
|
||||
activate(resolveTrace(trace));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve coordinates for the given target using the manager's "best judgment"
|
||||
*
|
||||
* @see #resolveTrace(Trace)
|
||||
* @param target the target
|
||||
* @return the best coordinates
|
||||
*/
|
||||
DebuggerCoordinates resolveTarget(Target target);
|
||||
|
||||
/**
|
||||
* Activate the given target
|
||||
*
|
||||
* @param target the desired target
|
||||
*/
|
||||
default void activateTarget(Target target) {
|
||||
activate(resolveTarget(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve coordinates for the given platform using the manager's "best judgment"
|
||||
*
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/* ###
|
||||
* 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.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import ghidra.debug.api.progress.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A service for publishing and subscribing to tasks and progress notifications.
|
||||
*
|
||||
* <p>
|
||||
* This is an attempt to de-couple the concepts of task monitoring and task execution. The
|
||||
* {@link PluginTool} has a system for submitting background tasks. It queues the task. When it
|
||||
* reaches the front of the queue, it creates a {@link TaskMonitor}, starts a thread, and executes
|
||||
* the task. Unfortunately, this tightly couples the progress reporting system with the execution
|
||||
* model, which is not ideal. Moreover, the task queuing system is the only simple way to obtain a
|
||||
* {@link TaskMonitor} with any semblance of central management or consistent presentation.
|
||||
* Providers can (and often do) create their own {@link TaskMonitor}s, usually placed at the bottom
|
||||
* of the provider when it is, e.g., updating a table.
|
||||
*
|
||||
* <p>
|
||||
* This service attempts to provide a centralized system for creating and presenting
|
||||
* {@link TaskMonitor}s separate from the execution model. No particular execution model is
|
||||
* required. Nor is the task implicitly associated to a specific thread. A client may use a single
|
||||
* thread for all tasks, a single thread for each task, many threads for a task, etc. In fact, a
|
||||
* client could even use an {@link ExecutorService}, without any care to how tasks are executed.
|
||||
* Instead, a task need simply request a monitor, pass its handle as needed, and close it when
|
||||
* finished. The information generated by such monitors is then forwarded to the subscriber which
|
||||
* can determine how to present them.
|
||||
*/
|
||||
@ServiceInfo(
|
||||
defaultProviderName = "ghidra.app.plugin.core.debug.service.progress.ProgressServicePlugin")
|
||||
public interface ProgressService {
|
||||
/**
|
||||
* Publish a task and create a monitor for it
|
||||
*
|
||||
* <p>
|
||||
* This and the methods on {@link TaskMonitor} are the mechanism for clients to publish task and
|
||||
* progress information. The monitor returned also extends {@link AutoCloseable}, allowing it to
|
||||
* be used fairly safely when the execution model involves a single thread.
|
||||
*
|
||||
* <pre>
|
||||
* try (CloseableTaskMonitor monitor = progressService.publishTask()) {
|
||||
* // Do the computation and update the monitor accordingly.
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If the above idiom is not used, e.g., because the monitor is passed among several
|
||||
* {@link CompletableFuture}s, the client must take care to close it. While the service may make
|
||||
* some effort to clean up dropped handles, this is just a safeguard to prevent stale monitors
|
||||
* from being presented indefinitely. The service may complain loudly when it detects dropped
|
||||
* monitor handles.
|
||||
*
|
||||
* @return the monitor
|
||||
*/
|
||||
CloseableTaskMonitor publishTask();
|
||||
|
||||
/**
|
||||
* Collect all the tasks currently in progress
|
||||
*
|
||||
* <p>
|
||||
* The subscriber ought to call this immediately after adding its listener, in order to catch up
|
||||
* on tasks already in progress.
|
||||
*
|
||||
* @return a collection of in-progress monitor proxies
|
||||
*/
|
||||
Collection<MonitorReceiver> getAllMonitors();
|
||||
|
||||
/**
|
||||
* Subscribe to task and progress events
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addProgressListener(ProgressListener listener);
|
||||
|
||||
/**
|
||||
* Un-subscribe from task and progress events
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeProgressListener(ProgressListener listener);
|
||||
}
|
||||
@@ -19,8 +19,7 @@ import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collection;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
|
||||
/**
|
||||
@@ -105,4 +104,26 @@ public interface TraceRmiService {
|
||||
* @return the connections
|
||||
*/
|
||||
Collection<TraceRmiConnection> getAllConnections();
|
||||
|
||||
/**
|
||||
* Get all of the acceptors currently listening for a connection
|
||||
*
|
||||
* @return the acceptors
|
||||
*/
|
||||
Collection<TraceRmiAcceptor> getAllAcceptors();
|
||||
|
||||
/**
|
||||
* Add a listener for events on the Trace RMI service
|
||||
*
|
||||
* @param listener the listener to add
|
||||
*/
|
||||
void addTraceServiceListener(TraceRmiServiceListener listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for events on the Trace RMI service
|
||||
*
|
||||
* @param listener the listener to remove
|
||||
*/
|
||||
void removeTraceServiceListener(TraceRmiServiceListener listener);
|
||||
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/* ###
|
||||
* 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.debug.api.progress;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public interface CloseableTaskMonitor extends TaskMonitor, AutoCloseable {
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/* ###
|
||||
* 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.debug.api.progress;
|
||||
|
||||
import ghidra.debug.api.progress.ProgressListener.Disposal;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* The subscriber side of a published {@link TaskMonitor}
|
||||
*
|
||||
* <p>
|
||||
* This only gives a subset of the expected task monitor interface. This is the subset a
|
||||
* <em>user</em> would need to monitor and/or cancel the task. All the mechanisms for updating the
|
||||
* monitor are only available to the publishing client.
|
||||
*/
|
||||
public interface MonitorReceiver {
|
||||
/**
|
||||
* Get the current message for the monitor
|
||||
*
|
||||
* @return the message
|
||||
*/
|
||||
String getMessage();
|
||||
|
||||
/**
|
||||
* Check if the monitor indicates progress at all
|
||||
*
|
||||
* <p>
|
||||
* If the task is indeterminate, then its {@link #getMaximum()} and {@link #getProgress()}
|
||||
* methods are meaningless.
|
||||
*
|
||||
* @return true if indeterminate (no progress shown), false if determinate (progress shown)
|
||||
*/
|
||||
boolean isIndeterminate();
|
||||
|
||||
/**
|
||||
* Get the maximum value of progress
|
||||
*
|
||||
* <p>
|
||||
* The implication is that when {@link #getProgress()} returns the maximum, the task is
|
||||
* complete.
|
||||
*
|
||||
* @return the maximum progress
|
||||
*/
|
||||
long getMaximum();
|
||||
|
||||
/**
|
||||
* Get the progress value, if applicable
|
||||
*
|
||||
* @return the progress, or {@link TaskMonitor#NO_PROGRESS_VALUE} if un-set or not applicable
|
||||
*/
|
||||
long getProgress();
|
||||
|
||||
/**
|
||||
* Check if the task can be cancelled
|
||||
*
|
||||
* @return true if cancel is enabled, false if not
|
||||
*/
|
||||
boolean isCancelEnabled();
|
||||
|
||||
/**
|
||||
* Request the task be cancelled
|
||||
*
|
||||
* <p>
|
||||
* Note it is up to the client publishing the task to adhere to this request. In general, the
|
||||
* computation should occasionally call {@link TaskMonitor#checkCancelled()}. In particular, the
|
||||
* subscribing client <em>cannot</em> presume the task is cancelled purely by virtue of calling
|
||||
* this method successfully. Instead, it should listen for
|
||||
* {@link ProgressListener#monitorDisposed(MonitorReceiver, Disposal)}.
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* Check if the task is cancelled
|
||||
*
|
||||
* @return true if cancelled, false if not
|
||||
*/
|
||||
boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Check if the monitor is still valid
|
||||
*
|
||||
* <p>
|
||||
* A monitor becomes invalid when it is closed or cleaned.
|
||||
*
|
||||
* @return true if still valid, false if invalid
|
||||
*/
|
||||
boolean isValid();
|
||||
|
||||
/**
|
||||
* Check if the monitor should be rendered with the progress value
|
||||
*
|
||||
* <p>
|
||||
* Regardless of this value, the monitor will render a progress bar and a numeric percentage. If
|
||||
* this is set to true (the default), the it will also display "{progress} of {maximum}" in
|
||||
* text.
|
||||
*
|
||||
* @return true to render the actual progress value, false for only a percentage.
|
||||
*/
|
||||
boolean isShowProgressValue();
|
||||
}
|
||||
+52
@@ -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.debug.api.progress;
|
||||
|
||||
public interface ProgressListener {
|
||||
enum Disposal {
|
||||
/**
|
||||
* The monitor was properly closed
|
||||
*/
|
||||
CLOSED,
|
||||
/**
|
||||
* The monitor was <em>not</em> closed. Instead, it was cleaned by the garbage collector.
|
||||
*/
|
||||
CLEANED;
|
||||
}
|
||||
|
||||
void monitorCreated(MonitorReceiver monitor);
|
||||
|
||||
void monitorDisposed(MonitorReceiver monitor, Disposal disposal);
|
||||
|
||||
void messageUpdated(MonitorReceiver monitor, String message);
|
||||
|
||||
void progressUpdated(MonitorReceiver monitor, long progress);
|
||||
|
||||
/**
|
||||
* Some other attribute has been updated
|
||||
*
|
||||
* <ul>
|
||||
* <li>cancelled</li>
|
||||
* <li>cancel enabled</li>
|
||||
* <li>indeterminate</li>
|
||||
* <li>maximum</li>
|
||||
* <li>show progress value in percent string</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param monitor the monitor
|
||||
*/
|
||||
void attributeUpdated(MonitorReceiver monitor);
|
||||
}
|
||||
+15
-2
@@ -19,6 +19,8 @@ import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* An acceptor to receive a single Trace RMI connection from a back-end
|
||||
*/
|
||||
@@ -27,12 +29,13 @@ public interface TraceRmiAcceptor {
|
||||
* Accept a single connection
|
||||
*
|
||||
* <p>
|
||||
* This acceptor is no longer valid after the connection is accepted.
|
||||
* This acceptor is no longer valid after the connection is accepted. If accepting the
|
||||
* connection fails, e.g., because of a timeout, this acceptor is no longer valid.
|
||||
*
|
||||
* @return the connection, if successful
|
||||
* @throws IOException if there was an error
|
||||
*/
|
||||
TraceRmiConnection accept() throws IOException;
|
||||
TraceRmiConnection accept() throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Get the address (and port) where the acceptor is listening
|
||||
@@ -48,4 +51,14 @@ public interface TraceRmiAcceptor {
|
||||
* @throws SocketException if there's a protocol error
|
||||
*/
|
||||
void setTimeout(int millis) throws SocketException;
|
||||
|
||||
/**
|
||||
* Cancel the connection
|
||||
*
|
||||
* <p>
|
||||
* If a different thread has called {@link #accept()}, it will fail. In this case, both
|
||||
* {@linkplain TraceRmiServiceListener#acceptCancelled(TraceRmiAcceptor)} and
|
||||
* {@linkplain TraceRmiServiceListener#acceptFailed(Exception)} may be invoked.
|
||||
*/
|
||||
void cancel();
|
||||
}
|
||||
|
||||
+19
@@ -17,9 +17,11 @@ package ghidra.debug.api.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
@@ -37,6 +39,16 @@ import ghidra.trace.model.Trace;
|
||||
* to both parent and child, then it should create and publish a second target.
|
||||
*/
|
||||
public interface TraceRmiConnection extends AutoCloseable {
|
||||
/**
|
||||
* Get the client-given description of this connection
|
||||
*
|
||||
* <p>
|
||||
* If the connection is still being negotiated, this will return a string indicating that.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Get the address of the back end debugger
|
||||
*
|
||||
@@ -137,4 +149,11 @@ public interface TraceRmiConnection extends AutoCloseable {
|
||||
* @return true if the trace is a target, false otherwise.
|
||||
*/
|
||||
boolean isTarget(Trace trace);
|
||||
|
||||
/**
|
||||
* Get all the valid targets created by this connection
|
||||
*
|
||||
* @return the collection of valid targets
|
||||
*/
|
||||
Collection<Target> getTargets();
|
||||
}
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/* ###
|
||||
* 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.debug.api.tracermi;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.TargetPublicationListener;
|
||||
|
||||
/**
|
||||
* A listener for Trace RMI Service events
|
||||
*/
|
||||
public interface TraceRmiServiceListener {
|
||||
/**
|
||||
* The mechanism for creating a connection
|
||||
*/
|
||||
enum ConnectMode {
|
||||
/**
|
||||
* The connection was established via {@link TraceRmiService#connect(SocketAddress)}
|
||||
*/
|
||||
CONNECT,
|
||||
/**
|
||||
* The connection was established via {@link TraceRmiService#acceptOne(SocketAddress)}
|
||||
*/
|
||||
ACCEPT_ONE,
|
||||
/**
|
||||
* The connection was established by the server. See {@link TraceRmiService#startServer()}
|
||||
*/
|
||||
SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* The server has been started on the given address
|
||||
*
|
||||
* @param address the server's address
|
||||
*/
|
||||
default void serverStarted(SocketAddress address) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The server has been stopped
|
||||
*/
|
||||
default void serverStopped() {
|
||||
}
|
||||
|
||||
/**
|
||||
* A new connection has been established
|
||||
*
|
||||
* @param connection the new connection
|
||||
* @param mode the mechanism creating the connection
|
||||
* @param if by {@link TraceRmiService#acceptOne(SocketAddress)}, the acceptor that created this
|
||||
* connection
|
||||
*/
|
||||
default void connected(TraceRmiConnection connection, ConnectMode mode,
|
||||
TraceRmiAcceptor acceptor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A connection was lost or closed
|
||||
*
|
||||
* <p>
|
||||
* <b>TODO</b>: Do we care to indicate why?
|
||||
*
|
||||
* @param connection the connection that has been closed
|
||||
*/
|
||||
default void disconnected(TraceRmiConnection connection) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The service is waiting for an inbound connection
|
||||
*
|
||||
* <p>
|
||||
* The acceptor remains valid until one of three events occurs:
|
||||
* {@linkplain} #connected(TraceRmiConnection, ConnectMode, TraceRmiAcceptor)},
|
||||
* {@linkplain} #acceptCancelled(TraceRmiAcceptor)}, or {@linkplain} #acceptFailed(Exception)}.
|
||||
*
|
||||
* @param acceptor the acceptor waiting
|
||||
*/
|
||||
default void waitingAccept(TraceRmiAcceptor acceptor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The client cancelled an inbound acceptor via {@link TraceRmiAcceptor#cancel()}
|
||||
*
|
||||
* @param acceptor the acceptor that was cancelled
|
||||
*/
|
||||
default void acceptCancelled(TraceRmiAcceptor acceptor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The service failed to complete an inbound connection
|
||||
*
|
||||
* @param acceptor the acceptor that failed
|
||||
* @param e the exception causing the failure
|
||||
*/
|
||||
default void acceptFailed(TraceRmiAcceptor acceptor, Exception e) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A new target was created by a Trace RMI connection
|
||||
*
|
||||
* <p>
|
||||
* The added benefit of this method compared to the {@link TargetPublicationListener} is that it
|
||||
* identifies <em>which connection</em>
|
||||
*
|
||||
* @param connection the connection creating the target
|
||||
* @param target the target
|
||||
* @see TargetPublicationListener#targetPublished(Target)
|
||||
* @see TargetPublicationListener#targetWithdrawn(Target)
|
||||
*/
|
||||
default void targetPublished(TraceRmiConnection connection, Target target) {
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
|
||||
+20
-2
@@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.gui.tracermi.connection;
|
||||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceInactiveCoordinatesPluginEvent;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
@@ -29,14 +31,30 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
||||
""",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
status = PluginStatus.STABLE,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
TraceInactiveCoordinatesPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
TraceRmiService.class,
|
||||
})
|
||||
public class TraceRmiConnectionManagerPlugin extends Plugin {
|
||||
private final TraceRmiConnectionManagerProvider provider;
|
||||
|
||||
public TraceRmiConnectionManagerPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
this.provider = new TraceRmiConnectionManagerProvider(this);
|
||||
}
|
||||
|
||||
// TODO: Add the actual provider. This will probably replace DebuggerTargetsPlugin.
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
provider.coordinates(evt.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceInactiveCoordinatesPluginEvent evt) {
|
||||
provider.coordinates(evt.getCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+532
File diff suppressed because it is too large
Load Diff
+39
@@ -0,0 +1,39 @@
|
||||
/* ###
|
||||
* 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.connection;
|
||||
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import docking.widgets.tree.GTree;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.TraceRmiManagerNode;
|
||||
|
||||
public class TraceRmiManagerActionContext extends DefaultActionContext {
|
||||
private final TreePath path;
|
||||
|
||||
public TraceRmiManagerActionContext(TraceRmiConnectionManagerProvider provider,
|
||||
TreePath path, GTree tree) {
|
||||
super(provider, path, tree);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public TraceRmiManagerNode getSelectedNode() {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return (TraceRmiManagerNode) path.getLastPathComponent();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/* ###
|
||||
* 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.connection.tree;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
|
||||
public abstract class AbstractTraceRmiManagerNode extends GTreeNode implements TraceRmiManagerNode {
|
||||
protected final TraceRmiConnectionManagerProvider provider;
|
||||
protected final String name;
|
||||
|
||||
public AbstractTraceRmiManagerNode(TraceRmiConnectionManagerProvider provider, String name) {
|
||||
this.provider = provider;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/* ###
|
||||
* 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.connection.tree;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
|
||||
public class TraceRmiAcceptorNode extends AbstractTraceRmiManagerNode {
|
||||
|
||||
private static final Icon ICON = DebuggerResources.ICON_CONNECT_ACCEPT;
|
||||
|
||||
private final TraceRmiAcceptor acceptor;
|
||||
|
||||
public TraceRmiAcceptorNode(TraceRmiConnectionManagerProvider provider,
|
||||
TraceRmiAcceptor acceptor) {
|
||||
super(provider, "ACCEPTING: " + acceptor.getAddress());
|
||||
this.acceptor = acceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Trace RMI Acceptor listening at " + acceptor.getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public TraceRmiAcceptor getAcceptor() {
|
||||
return acceptor;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/* ###
|
||||
* 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.connection.tree;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
|
||||
public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
|
||||
private static final Icon ICON = DebuggerResources.ICON_CONNECTION;
|
||||
|
||||
private final TraceRmiConnection connection;
|
||||
private final Map<Target, TraceRmiTargetNode> targetNodes = new HashMap<>();
|
||||
|
||||
public TraceRmiConnectionNode(TraceRmiConnectionManagerProvider provider,
|
||||
TraceRmiConnection connection) {
|
||||
// TODO: Can the connector identify/describe itself for this display?
|
||||
super(provider, "Connected: " + connection.getRemoteAddress());
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return connection.getDescription() + " at " + connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Trace RMI Connection to " + connection.getDescription() + " at " +
|
||||
connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TraceRmiTargetNode newTargetNode(Target target) {
|
||||
return new TraceRmiTargetNode(provider, this, target);
|
||||
}
|
||||
|
||||
private TraceRmiTargetNode addTargetNode(Target target) {
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.computeIfAbsent(target, this::newTargetNode);
|
||||
}
|
||||
addNode(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
private void removeTargetNode(Target target) {
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.remove(target);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
public TraceRmiTargetNode targetPublished(Target target) {
|
||||
return addTargetNode(target);
|
||||
}
|
||||
|
||||
public void targetWithdrawn(Target target) {
|
||||
removeTargetNode(target);
|
||||
}
|
||||
|
||||
public TraceRmiConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.dbg;
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
public interface DebuggerConsoleLogger {
|
||||
public interface TraceRmiManagerNode {
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/* ###
|
||||
* 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.connection.tree;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
|
||||
public class TraceRmiServerNode extends AbstractTraceRmiManagerNode {
|
||||
private static final Icon ICON = DebuggerResources.ICON_THREAD; // TODO: Different name?
|
||||
|
||||
public TraceRmiServerNode(TraceRmiConnectionManagerProvider provider) {
|
||||
super(provider, "Server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
TraceRmiService service = provider.getTraceRmiService();
|
||||
if (service == null) {
|
||||
return "<SERVICE MISSING>";
|
||||
}
|
||||
if (!service.isServerStarted()) {
|
||||
return "Server: CLOSED";
|
||||
}
|
||||
return "Server: LISTENING " + service.getServerAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return getDisplayText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/* ###
|
||||
* 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.connection.tree;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.TargetPublicationListener;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class TraceRmiServiceNode extends AbstractTraceRmiManagerNode
|
||||
implements TraceRmiServiceListener, TargetPublicationListener {
|
||||
private static final String DESCRIPTION = "The TraceRmi service";
|
||||
|
||||
final TraceRmiServerNode serverNode;
|
||||
final Map<TraceRmiConnection, TraceRmiConnectionNode> connectionNodes = new HashMap<>();
|
||||
final Map<TraceRmiAcceptor, TraceRmiAcceptorNode> acceptorNodes = new HashMap<>();
|
||||
// weak because each connection node keeps the strong map
|
||||
final Map<Target, TraceRmiTargetNode> targetNodes = new WeakHashMap<>();
|
||||
|
||||
public TraceRmiServiceNode(TraceRmiConnectionManagerProvider provider) {
|
||||
super(provider, "<root>");
|
||||
this.serverNode = new TraceRmiServerNode(provider);
|
||||
|
||||
addNode(serverNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TraceRmiConnectionNode newConnectionNode(TraceRmiConnection connection) {
|
||||
return new TraceRmiConnectionNode(provider, connection);
|
||||
}
|
||||
|
||||
private void addConnectionNode(TraceRmiConnection connection) {
|
||||
TraceRmiConnectionNode node;
|
||||
synchronized (connectionNodes) {
|
||||
node = connectionNodes.computeIfAbsent(connection, this::newConnectionNode);
|
||||
}
|
||||
addNode(node);
|
||||
}
|
||||
|
||||
private void removeConnectionNode(TraceRmiConnection connection) {
|
||||
TraceRmiConnectionNode node;
|
||||
synchronized (connectionNodes) {
|
||||
node = connectionNodes.remove(connection);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
private TraceRmiAcceptorNode newAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
return new TraceRmiAcceptorNode(provider, acceptor);
|
||||
}
|
||||
|
||||
private void addAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
TraceRmiAcceptorNode node;
|
||||
synchronized (acceptorNodes) {
|
||||
node = acceptorNodes.computeIfAbsent(acceptor, this::newAcceptorNode);
|
||||
}
|
||||
addNode(node);
|
||||
}
|
||||
|
||||
private void removeAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
TraceRmiAcceptorNode node;
|
||||
synchronized (acceptorNodes) {
|
||||
node = acceptorNodes.remove(acceptor);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverStarted(SocketAddress address) {
|
||||
serverNode.fireNodeChanged();
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverStopped() {
|
||||
serverNode.fireNodeChanged();
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(TraceRmiConnection connection, ConnectMode mode,
|
||||
TraceRmiAcceptor acceptor) {
|
||||
addConnectionNode(connection);
|
||||
removeAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(TraceRmiConnection connection) {
|
||||
removeConnectionNode(connection);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitingAccept(TraceRmiAcceptor acceptor) {
|
||||
addAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptCancelled(TraceRmiAcceptor acceptor) {
|
||||
removeAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptFailed(TraceRmiAcceptor acceptor, Exception e) {
|
||||
removeAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetPublished(TraceRmiConnection connection, Target target) {
|
||||
TraceRmiConnectionNode cxNode;
|
||||
synchronized (connectionNodes) {
|
||||
cxNode = connectionNodes.get(connection);
|
||||
}
|
||||
if (cxNode == null) {
|
||||
Msg.warn(this,
|
||||
"Target published on a connection I don't have! " + connection + " " + target);
|
||||
return;
|
||||
}
|
||||
TraceRmiTargetNode tNode = cxNode.targetPublished(target);
|
||||
if (tNode == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (targetNodes) {
|
||||
targetNodes.put(target, tNode);
|
||||
}
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetPublished(Target target) {
|
||||
// Dont care. Using targetPublished(connection, target) instead
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetWithdrawn(Target target) {
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.remove(target);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.getConnectionNode().targetWithdrawn(target);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
public void coordinates(DebuggerCoordinates coordinates) {
|
||||
Target target = coordinates.getTarget();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.get(target);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.fireNodeChanged();
|
||||
}
|
||||
}
|
||||
+64
@@ -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.gui.tracermi.connection.tree;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.target.Target;
|
||||
|
||||
public class TraceRmiTargetNode extends AbstractTraceRmiManagerNode {
|
||||
private static final Icon ICON = DebuggerResources.ICON_RECORD;
|
||||
|
||||
private final TraceRmiConnectionNode connectionNode;
|
||||
private final Target target;
|
||||
|
||||
public TraceRmiTargetNode(TraceRmiConnectionManagerProvider provider,
|
||||
TraceRmiConnectionNode connectionNode, Target target) {
|
||||
super(provider, target.getTrace().getName());
|
||||
this.connectionNode = connectionNode;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return target.getTrace().getName() + " (snap=" + target.getSnap() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Target: " + target.getTrace().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public TraceRmiConnectionNode getConnectionNode() {
|
||||
return connectionNode;
|
||||
}
|
||||
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -33,8 +33,8 @@ import db.Transaction;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.terminal.TerminalListener;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
|
||||
-47
@@ -1,47 +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.service.rmi.trace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
|
||||
public class DefaultTraceRmiAcceptor extends TraceRmiServer implements TraceRmiAcceptor {
|
||||
|
||||
public DefaultTraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
super(plugin, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws IOException {
|
||||
socket = new ServerSocket();
|
||||
bind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceRmiHandler accept() throws IOException {
|
||||
TraceRmiHandler handler = super.accept();
|
||||
close();
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
+15
-29
@@ -13,38 +13,43 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TraceRmiServer {
|
||||
public abstract class AbstractTraceRmiListener {
|
||||
protected final TraceRmiPlugin plugin;
|
||||
protected final SocketAddress address;
|
||||
|
||||
protected ServerSocket socket;
|
||||
|
||||
public TraceRmiServer(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
public AbstractTraceRmiListener(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
this.plugin = plugin;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address);
|
||||
}
|
||||
protected abstract void bind() throws IOException;
|
||||
|
||||
public void start() throws IOException {
|
||||
socket = new ServerSocket();
|
||||
bind();
|
||||
new Thread(this::serviceLoop, "trace-rmi server " + socket.getLocalSocketAddress()).start();
|
||||
startServiceLoop();
|
||||
}
|
||||
|
||||
protected abstract void startServiceLoop();
|
||||
|
||||
public void setTimeout(int millis) throws SocketException {
|
||||
socket.setSoTimeout(millis);
|
||||
}
|
||||
|
||||
protected abstract ConnectMode getConnectMode();
|
||||
|
||||
/**
|
||||
* Accept a connection and handle its requests.
|
||||
*
|
||||
@@ -54,36 +59,17 @@ public class TraceRmiServer {
|
||||
*
|
||||
* @return the handler
|
||||
* @throws IOException on error
|
||||
* @throws CancelledException if the accept is cancelled
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
protected TraceRmiHandler accept() throws IOException {
|
||||
protected TraceRmiHandler doAccept(TraceRmiAcceptor acceptor) throws IOException {
|
||||
Socket client = socket.accept();
|
||||
TraceRmiHandler handler = new TraceRmiHandler(plugin, client);
|
||||
handler.start();
|
||||
plugin.listeners.invoke().connected(handler, getConnectMode(), acceptor);
|
||||
return handler;
|
||||
}
|
||||
|
||||
protected void serviceLoop() {
|
||||
try {
|
||||
accept();
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (socket.isClosed()) {
|
||||
return;
|
||||
}
|
||||
Msg.error("Error accepting TraceRmi client", e);
|
||||
return;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error("Error closing TraceRmi service", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
socket.close();
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/* ###
|
||||
* 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.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class DefaultTraceRmiAcceptor extends AbstractTraceRmiListener implements TraceRmiAcceptor {
|
||||
private boolean cancelled = false;
|
||||
|
||||
public DefaultTraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
super(plugin, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startServiceLoop() {
|
||||
// Don't. Instead, client calls accept()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address, 1);
|
||||
plugin.addAcceptor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectMode getConnectMode() {
|
||||
return ConnectMode.ACCEPT_ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceRmiHandler accept() throws IOException, CancelledException {
|
||||
try {
|
||||
TraceRmiHandler handler = doAccept(this);
|
||||
close();
|
||||
return handler;
|
||||
}
|
||||
catch (Exception e) {
|
||||
close();
|
||||
if (cancelled) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
plugin.listeners.invoke().acceptFailed(this, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
plugin.removeAcceptor(this);
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled = true;
|
||||
close();
|
||||
plugin.listeners.invoke().acceptCancelled(this);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler.*;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiError;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.tracermi.RemoteParameter;
|
||||
+54
-31
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
@@ -41,9 +41,12 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
@@ -65,7 +68,6 @@ import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TraceRmiHandler implements TraceRmiConnection {
|
||||
public static final String VERSION = "10.4";
|
||||
@@ -180,7 +182,14 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
byTrace.put(openTrace.trace, openTrace);
|
||||
first.complete(openTrace);
|
||||
|
||||
plugin.publishTarget(openTrace.target);
|
||||
plugin.publishTarget(TraceRmiHandler.this, openTrace.target);
|
||||
}
|
||||
|
||||
public synchronized List<Target> getTargets() {
|
||||
return byId.values()
|
||||
.stream()
|
||||
.map(ot -> ot.target)
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public CompletableFuture<OpenTrace> getFirstAsync() {
|
||||
@@ -192,7 +201,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
private final Socket socket;
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
private final CompletableFuture<Void> negotiate = new CompletableFuture<>();
|
||||
private final CompletableFuture<String> negotiate = new CompletableFuture<>();
|
||||
private final CompletableFuture<Void> closed = new CompletableFuture<>();
|
||||
private final Set<TerminalSession> terminals = new LinkedHashSet<>();
|
||||
|
||||
@@ -276,8 +285,8 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
DoId nextKey = openTraces.idSet().iterator().next();
|
||||
OpenTrace open = openTraces.removeById(nextKey);
|
||||
if (traceManager == null || traceManager.isSaveTracesByDefault()) {
|
||||
try {
|
||||
open.trace.save("Save on Disconnect", plugin.getTaskMonitor());
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
open.trace.save("Save on Disconnect", monitor);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Could not save " + open.trace);
|
||||
@@ -289,6 +298,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
open.trace.release(this);
|
||||
}
|
||||
closed.complete(null);
|
||||
plugin.listeners.invoke().disconnected(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -344,18 +354,19 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
protected DomainFile createDeconflictedFile(DomainFolder parent, DomainObject object)
|
||||
throws InvalidNameException, CancelledException, IOException {
|
||||
String name = object.getName();
|
||||
TaskMonitor monitor = plugin.getTaskMonitor();
|
||||
for (int nextId = 1; nextId < 100; nextId++) {
|
||||
try {
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
name = object.getName() + "." + nextId;
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
for (int nextId = 1; nextId < 100; nextId++) {
|
||||
try {
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
name = object.getName() + "." + nextId;
|
||||
}
|
||||
}
|
||||
name = object.getName() + "." + System.currentTimeMillis();
|
||||
// Don't catch it this last time
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
name = object.getName() + "." + System.currentTimeMillis();
|
||||
// Don't catch it this last time
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -911,16 +922,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
OpenTrace open = requireOpenTrace(req.getOid());
|
||||
long snap = req.getSnap().getSnap();
|
||||
|
||||
/**
|
||||
* TODO: Is this composition of laziness upon laziness efficient enough?
|
||||
*
|
||||
* <p>
|
||||
* Can experiment with ordering of address-set-view "expression" to optimize early
|
||||
* termination.
|
||||
*
|
||||
* <p>
|
||||
* Want addresses satisfying {@code known | (readOnly & everKnown)}
|
||||
*/
|
||||
// Want addresses satisfying {@code known | (readOnly & everKnown)}
|
||||
TraceMemoryManager memoryManager = open.trace.getMemoryManager();
|
||||
AddressSetView readOnly =
|
||||
memoryManager.getRegionsAddressSetWith(snap, r -> !r.isWrite());
|
||||
@@ -939,8 +941,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
dis.setInitialContext(DebuggerDisassemblerPlugin.deriveAlternativeDefaultContext(
|
||||
host.getLanguage(), host.getLanguage().getLanguageID(), start));
|
||||
|
||||
TaskMonitor monitor = plugin.getTaskMonitor();
|
||||
dis.applyToTyped(open.trace.getFixedProgramView(snap), monitor);
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
dis.applyToTyped(open.trace.getFixedProgramView(snap), monitor);
|
||||
}
|
||||
|
||||
return ReplyDisassemble.newBuilder()
|
||||
.setLength(dis.getDisassembledAddressSet().getNumAddresses())
|
||||
@@ -1027,8 +1030,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
new SchemaName(m.getReturnType().getName()));
|
||||
methodRegistry.add(rm);
|
||||
}
|
||||
negotiate.complete(null);
|
||||
return ReplyNegotiate.getDefaultInstance();
|
||||
negotiate.complete(req.getDescription());
|
||||
return ReplyNegotiate.newBuilder()
|
||||
.setDescription(Application.getName() + " " + Application.getApplicationVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
protected ReplyPutBytes handlePutBytes(RequestPutBytes req) {
|
||||
@@ -1110,7 +1115,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
protected ReplySaveTrace handleSaveTrace(RequestSaveTrace req)
|
||||
throws CancelledException, IOException {
|
||||
OpenTrace open = requireOpenTrace(req.getOid());
|
||||
open.trace.save("TraceRMI", plugin.getTaskMonitor());
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
open.trace.save("TraceRMI", monitor);
|
||||
}
|
||||
return ReplySaveTrace.getDefaultInstance();
|
||||
}
|
||||
|
||||
@@ -1271,9 +1278,25 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
return openTraces.getByTrace(trace) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Target> getTargets() {
|
||||
return openTraces.getTargets();
|
||||
}
|
||||
|
||||
public void registerTerminals(Collection<TerminalSession> terminals) {
|
||||
synchronized (this.terminals) {
|
||||
this.terminals.addAll(terminals);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
// NOTE: Negotiation happens during construction, so unless this is called internally,
|
||||
// or there's some error, we should always have a read description.
|
||||
String description = negotiate.getNow("(Negotiating...)");
|
||||
if (description.isBlank()) {
|
||||
return "Trace RMI";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
||||
+70
-13
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
@@ -24,14 +24,16 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.task.ConsoleTaskMonitor;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Connect to back-end debuggers via Trace RMI",
|
||||
@@ -56,26 +58,41 @@ import ghidra.util.task.TaskMonitor;
|
||||
public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||
private static final int DEFAULT_PORT = 15432;
|
||||
|
||||
static class FallbackTaskMonitor extends ConsoleTaskMonitor implements CloseableTaskMonitor {
|
||||
@Override
|
||||
public void close() {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTargetService targetService;
|
||||
@AutoServiceConsumed
|
||||
private ProgressService progressService;
|
||||
@SuppressWarnings("unused")
|
||||
private final Wiring autoServiceWiring;
|
||||
|
||||
private final TaskMonitor monitor = new ConsoleTaskMonitor();
|
||||
|
||||
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
|
||||
private TraceRmiServer server;
|
||||
|
||||
private final Set<TraceRmiHandler> handlers = new LinkedHashSet<>();
|
||||
private final Set<DefaultTraceRmiAcceptor> acceptors = new LinkedHashSet<>();
|
||||
|
||||
final ListenerSet<TraceRmiServiceListener> listeners =
|
||||
new ListenerSet<>(TraceRmiServiceListener.class, true);
|
||||
|
||||
private final CloseableTaskMonitor fallbackMonitor = new FallbackTaskMonitor();
|
||||
|
||||
public TraceRmiPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||
}
|
||||
|
||||
public TaskMonitor getTaskMonitor() {
|
||||
// TODO: Create one in the Debug Console?
|
||||
return monitor;
|
||||
protected CloseableTaskMonitor createMonitor() {
|
||||
if (progressService == null) {
|
||||
return fallbackMonitor;
|
||||
}
|
||||
return progressService.publishTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,14 +119,16 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||
}
|
||||
server = new TraceRmiServer(this, serverAddress);
|
||||
server.start();
|
||||
listeners.invoke().serverStarted(server.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServer() {
|
||||
if (server != null) {
|
||||
server.close();
|
||||
server = null;
|
||||
listeners.invoke().serverStopped();
|
||||
}
|
||||
server = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,6 +143,7 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||
socket.connect(address);
|
||||
TraceRmiHandler handler = new TraceRmiHandler(this, socket);
|
||||
handler.start();
|
||||
listeners.invoke().connected(handler, ConnectMode.CONNECT, null);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@@ -131,25 +151,52 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||
public DefaultTraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
|
||||
DefaultTraceRmiAcceptor acceptor = new DefaultTraceRmiAcceptor(this, address);
|
||||
acceptor.start();
|
||||
listeners.invoke().waitingAccept(acceptor);
|
||||
return acceptor;
|
||||
}
|
||||
|
||||
void addHandler(TraceRmiHandler handler) {
|
||||
handlers.add(handler);
|
||||
synchronized (handlers) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void removeHandler(TraceRmiHandler handler) {
|
||||
handlers.remove(handler);
|
||||
synchronized (handlers) {
|
||||
handlers.remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiConnection> getAllConnections() {
|
||||
return List.copyOf(handlers);
|
||||
synchronized (handlers) {
|
||||
return List.copyOf(handlers);
|
||||
}
|
||||
}
|
||||
|
||||
void publishTarget(TraceRmiTarget target) {
|
||||
void addAcceptor(DefaultTraceRmiAcceptor acceptor) {
|
||||
synchronized (acceptors) {
|
||||
acceptors.add(acceptor);
|
||||
}
|
||||
}
|
||||
|
||||
void removeAcceptor(DefaultTraceRmiAcceptor acceptor) {
|
||||
synchronized (acceptors) {
|
||||
acceptors.remove(acceptor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiAcceptor> getAllAcceptors() {
|
||||
synchronized (acceptors) {
|
||||
return List.copyOf(acceptors);
|
||||
}
|
||||
}
|
||||
|
||||
void publishTarget(TraceRmiHandler handler, TraceRmiTarget target) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
targetService.publishTarget(target);
|
||||
listeners.invoke().targetPublished(handler, target);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,4 +205,14 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||
targetService.withdrawTarget(target);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTraceServiceListener(TraceRmiServiceListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTraceServiceListener(TraceRmiServiceListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/* ###
|
||||
* 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.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class TraceRmiServer extends AbstractTraceRmiListener {
|
||||
public TraceRmiServer(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
super(plugin, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startServiceLoop() {
|
||||
new Thread(this::serviceLoop, "trace-rmi server " + socket.getLocalSocketAddress()).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectMode getConnectMode() {
|
||||
return ConnectMode.SERVER;
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected void serviceLoop() {
|
||||
try {
|
||||
doAccept(null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (socket.isClosed()) {
|
||||
return;
|
||||
}
|
||||
Msg.error("Error accepting TraceRmi client", e);
|
||||
return;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error("Error closing TraceRmi service", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import ghidra.program.model.address.AddressOverflowException;
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ package ghidra.app.services;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ package ghidra.debug.spi.tracermi;
|
||||
import java.util.Collection;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.app.services.InternalTraceRmiService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.framework.options.Options;
|
||||
|
||||
@@ -427,9 +427,11 @@ message Method {
|
||||
message RequestNegotiate {
|
||||
string version = 1;
|
||||
repeated Method methods = 2;
|
||||
string description = 3;
|
||||
}
|
||||
|
||||
message ReplyNegotiate {
|
||||
string description = 1;
|
||||
}
|
||||
|
||||
message XRequestInvokeMethod {
|
||||
|
||||
@@ -720,7 +720,7 @@ class Client(object):
|
||||
return Client._read_obj_desc(msg.child_desc), sch.OBJECT
|
||||
raise ValueError("Could not read value: {}".format(msg))
|
||||
|
||||
def __init__(self, s, method_registry: MethodRegistry):
|
||||
def __init__(self, s, description: str, method_registry: MethodRegistry):
|
||||
self._traces = {}
|
||||
self._next_trace_id = 1
|
||||
self.tlock = Lock()
|
||||
@@ -732,7 +732,7 @@ class Client(object):
|
||||
self.slock = Lock()
|
||||
self.receiver.start()
|
||||
self._method_registry = method_registry
|
||||
self._negotiate()
|
||||
self.description = self._negotiate(description)
|
||||
|
||||
def close(self):
|
||||
self.s.close()
|
||||
@@ -1083,15 +1083,16 @@ class Client(object):
|
||||
return reply.length
|
||||
return self._batch_or_now(root, 'reply_disassemble', _handle)
|
||||
|
||||
def _negotiate(self):
|
||||
def _negotiate(self, description: str):
|
||||
root = bufs.RootMessage()
|
||||
root.request_negotiate.version = VERSION
|
||||
root.request_negotiate.description = description
|
||||
self._write_methods(root.request_negotiate.methods,
|
||||
self._method_registry._methods.values())
|
||||
|
||||
def _handle(reply):
|
||||
pass
|
||||
self._now(root, 'reply_negotiate', _handle)
|
||||
return reply.description
|
||||
return self._now(root, 'reply_negotiate', _handle)
|
||||
|
||||
def _handle_invoke_method(self, request):
|
||||
if request.HasField('oid'):
|
||||
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
/* ###
|
||||
* 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.connection;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.InvocationDialogHelper;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient.Tx;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.services.DebuggerControlService;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
TraceRmiConnectionManagerProvider provider;
|
||||
TraceRmiService traceRmiService;
|
||||
DebuggerControlService controlService;
|
||||
|
||||
@Before
|
||||
public void setUpConnectionManager() throws Exception {
|
||||
controlService = addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
traceRmiService = addPlugin(tool, TraceRmiPlugin.class);
|
||||
addPlugin(tool, TraceRmiConnectionManagerPlugin.class);
|
||||
provider = waitForComponentProvider(TraceRmiConnectionManagerProvider.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionAccept() throws Exception {
|
||||
performEnabledAction(provider, provider.actionConnectAccept, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", "localhost"),
|
||||
Map.entry("port", 0)));
|
||||
waitForPass(() -> Unique.assertOne(traceRmiService.getAllAcceptors()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionConnect() throws Exception {
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open()) {
|
||||
server.bind(new InetSocketAddress("localhost", 0), 1);
|
||||
if (!(server.getLocalAddress() instanceof InetSocketAddress sockaddr)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
performEnabledAction(provider, provider.actionConnectOutbound, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", sockaddr.getHostString()),
|
||||
Map.entry("port", sockaddr.getPort())));
|
||||
try (SocketChannel channel = server.accept()) {
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate("Test client");
|
||||
client.recvNegotiate();
|
||||
waitForPass(() -> Unique.assertOne(traceRmiService.getAllConnections()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionStartServer() throws Exception {
|
||||
performEnabledAction(provider, provider.actionStartServer, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", "localhost"),
|
||||
Map.entry("port", 0)));
|
||||
waitForPass(() -> assertTrue(traceRmiService.isServerStarted()));
|
||||
waitForPass(() -> assertFalse(provider.actionStartServer.isEnabled()));
|
||||
|
||||
traceRmiService.stopServer();
|
||||
waitForPass(() -> assertTrue(provider.actionStartServer.isEnabled()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionStopServer() throws Exception {
|
||||
waitForPass(() -> assertFalse(provider.actionStopServer.isEnabled()));
|
||||
traceRmiService.startServer();
|
||||
waitForSwing();
|
||||
performEnabledAction(provider, provider.actionStopServer, true);
|
||||
assertFalse(traceRmiService.isServerStarted());
|
||||
|
||||
waitForPass(() -> assertFalse(provider.actionStopServer.isEnabled()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCloseOnAcceptor() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
TraceRmiAcceptorNode node =
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor);
|
||||
assertNotNull(node);
|
||||
provider.tree.setSelectedNode(node);
|
||||
// Tree uses a task queue for selection requests
|
||||
waitForPass(() -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes())));
|
||||
|
||||
performEnabledAction(provider, provider.actionCloseConnection, true);
|
||||
try {
|
||||
acceptor.accept();
|
||||
fail();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCloseOnConnection() throws Exception {
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
TraceRmiConnectionNode node =
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection);
|
||||
assertNotNull(node);
|
||||
provider.tree.setSelectedNode(node);
|
||||
// Tree uses a task queue for selection requests
|
||||
waitForPass(
|
||||
() -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes())));
|
||||
|
||||
performEnabledAction(provider, provider.actionCloseConnection, true);
|
||||
waitForPass(() -> assertTrue(cx.connection.isClosed()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCloseAll() throws Exception {
|
||||
traceRmiService.startServer();
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
performEnabledAction(provider, provider.actionCloseAll, true);
|
||||
|
||||
waitForPass(() -> assertFalse(traceRmiService.isServerStarted()));
|
||||
waitForPass(() -> assertTrue(cx.connection.isClosed()));
|
||||
try {
|
||||
acceptor.accept();
|
||||
fail();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerNode() throws Exception {
|
||||
TraceRmiServerNode node = TraceRmiConnectionTreeHelper.getServerNode(provider.rootNode);
|
||||
assertEquals("Server: CLOSED", node.getDisplayText());
|
||||
traceRmiService.startServer();
|
||||
waitForPass(() -> assertEquals("Server: LISTENING " + traceRmiService.getServerAddress(),
|
||||
node.getDisplayText()));
|
||||
traceRmiService.stopServer();
|
||||
waitForPass(() -> assertEquals("Server: CLOSED", node.getDisplayText()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptHasNode() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
TraceRmiAcceptorNode node =
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor);
|
||||
assertNotNull(node);
|
||||
assertEquals("ACCEPTING: " + acceptor.getAddress(), node.getDisplayText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptThenCancelNoNode() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
assertNotNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor));
|
||||
|
||||
acceptor.cancel();
|
||||
waitForPass(() -> traceRmiService.getAllAcceptors().isEmpty());
|
||||
assertNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor));
|
||||
}
|
||||
|
||||
record Cx(SocketChannel channel, TestTraceRmiClient client,
|
||||
TraceRmiConnection connection)
|
||||
implements AutoCloseable {
|
||||
public static Cx complete(TraceRmiAcceptor acceptor, String description)
|
||||
throws IOException, CancelledException {
|
||||
SocketChannel channel = null;
|
||||
TraceRmiConnection connection = null;
|
||||
try {
|
||||
channel = SocketChannel.open(acceptor.getAddress());
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate(description);
|
||||
connection = acceptor.accept();
|
||||
client.recvNegotiate();
|
||||
return new Cx(channel, client, connection);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
public static Cx toServer(TraceRmiService service, String description) throws IOException {
|
||||
SocketChannel channel = null;
|
||||
try {
|
||||
channel = SocketChannel.open(service.getServerAddress());
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate(description);
|
||||
client.recvNegotiate();
|
||||
return new Cx(channel, client,
|
||||
waitForPass(() -> Unique.assertOne(service.getAllConnections())));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
public static Cx connect(TraceRmiService service, String description)
|
||||
throws IOException, InterruptedException, ExecutionException, TimeoutException {
|
||||
SocketChannel channel = null;
|
||||
CompletableFuture<TraceRmiConnection> future = null;
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open()) {
|
||||
server.bind(new InetSocketAddress("localhost", 0), 1);
|
||||
future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return service.connect(server.getLocalAddress());
|
||||
}
|
||||
catch (IOException e) {
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
});
|
||||
channel = server.accept();
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate(description);
|
||||
client.recvNegotiate();
|
||||
return new Cx(channel, client, future.get(1, TimeUnit.SECONDS));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
connection.close();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptThenSuccessNodes() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
assertNotNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor));
|
||||
|
||||
try (Cx cx = Cx.complete(acceptor, "Test client")) {
|
||||
waitForPass(() -> traceRmiService.getAllAcceptors().isEmpty());
|
||||
waitForPass(() -> assertNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode)
|
||||
.get(acceptor)));
|
||||
waitForPass(() -> assertEquals(cx.connection,
|
||||
Unique.assertOne(traceRmiService.getAllConnections())));
|
||||
|
||||
TraceRmiConnectionNode node =
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection);
|
||||
assertNotNull(node);
|
||||
assertEquals("Test client at " + cx.connection.getRemoteAddress(),
|
||||
node.getDisplayText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerConnectNode() throws Exception {
|
||||
traceRmiService.startServer();
|
||||
try (Cx cx = Cx.toServer(traceRmiService, "Test client")) {
|
||||
waitForPass(() -> traceRmiService.getAllAcceptors().isEmpty());
|
||||
|
||||
TraceRmiConnectionNode node = waitForValue(
|
||||
() -> TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection));
|
||||
assertEquals("Test client at " + cx.connection.getRemoteAddress(),
|
||||
node.getDisplayText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectThenSuccessNodes() throws Exception {
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
waitForPass(() -> assertEquals(cx.connection,
|
||||
Unique.assertOne(traceRmiService.getAllConnections())));
|
||||
|
||||
TraceRmiConnectionNode node =
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection);
|
||||
assertNotNull(node);
|
||||
assertEquals("Test client at " + cx.connection.getRemoteAddress(),
|
||||
node.getDisplayText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFrontEndCloseNoNodes() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
try (Cx cx = Cx.complete(acceptor, "Test client")) {
|
||||
assertNotNull(TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection));
|
||||
|
||||
cx.connection.close();
|
||||
waitForPass(() -> assertTrue(traceRmiService.getAllConnections().isEmpty()));
|
||||
waitForPass(() -> assertNull(
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackEndCloseNoNodes() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
try (Cx cx = Cx.complete(acceptor, "Test client")) {
|
||||
assertNotNull(TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection));
|
||||
|
||||
cx.channel.close();
|
||||
waitForPass(() -> assertTrue(traceRmiService.getAllConnections().isEmpty()));
|
||||
waitForPass(() -> assertNull(
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTargetNode() throws Exception {
|
||||
SchemaContext ctx = XmlSchemaContext.deserialize("""
|
||||
<context>
|
||||
<schema name="Root" elementResync="NEVER" attributeResync="NEVER" />
|
||||
</context>
|
||||
""");
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
cx.client.createTrace(1, "bash");
|
||||
try (Tx tx = cx.client.new Tx(1, 1, "Create snapshots")) {
|
||||
cx.client.snapshot(1, 0, "First snapshot");
|
||||
cx.client.createRootObject(1, ctx.getSchema(new SchemaName("Root")));
|
||||
cx.client.snapshot(1, 1, "Stepped");
|
||||
}
|
||||
cx.client.activate(1, "");
|
||||
Target target = waitForValue(() -> traceManager.getCurrent().getTarget());
|
||||
|
||||
TraceRmiTargetNode node =
|
||||
TraceRmiConnectionTreeHelper.getTargetNodeMap(provider.rootNode).get(target);
|
||||
assertEquals("bash (snap=1)", node.getDisplayText());
|
||||
|
||||
provider.tree.setSelectedNode(node);
|
||||
// Tree uses a task queue for selection requests
|
||||
waitForPass(
|
||||
() -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes())));
|
||||
|
||||
traceManager.activateSnap(0);
|
||||
waitForPass(() -> {
|
||||
assertEquals(0, traceManager.getCurrentSnap());
|
||||
assertEquals(ControlMode.RO_TRACE,
|
||||
controlService.getCurrentMode(target.getTrace()));
|
||||
});
|
||||
|
||||
triggerEnter(provider.tree);
|
||||
waitForPass(() -> {
|
||||
assertEquals(1, traceManager.getCurrentSnap());
|
||||
assertEquals(ControlMode.RO_TARGET,
|
||||
controlService.getCurrentMode(target.getTrace()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* 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.connection.tree;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
|
||||
public class TraceRmiConnectionTreeHelper {
|
||||
public static Map<TraceRmiAcceptor, TraceRmiAcceptorNode> getAcceptorNodeMap(
|
||||
TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.acceptorNodes;
|
||||
}
|
||||
|
||||
public static Map<TraceRmiConnection, TraceRmiConnectionNode> getConnectionNodeMap(
|
||||
TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.connectionNodes;
|
||||
}
|
||||
|
||||
public static Map<Target, TraceRmiTargetNode> getTargetNodeMap(
|
||||
TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.targetNodes;
|
||||
}
|
||||
|
||||
public static TraceRmiServerNode getServerNode(TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.serverNode;
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/* ###
|
||||
* 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.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.protobuf.AbstractMessage;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/* ###
|
||||
* 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.service.tracermi;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.rmi.trace.TraceRmi.Compiler;
|
||||
|
||||
public class TestTraceRmiClient {
|
||||
final ProtobufSocket<RootMessage> socket;
|
||||
|
||||
public TestTraceRmiClient(SocketChannel channel) {
|
||||
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
|
||||
}
|
||||
|
||||
public void sendNegotiate(String description) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestNegotiate(RequestNegotiate.newBuilder()
|
||||
.setVersion(TraceRmiHandler.VERSION)
|
||||
.setDescription(description))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void recvNegotiate() throws IOException {
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyNegotiate(ReplyNegotiate.newBuilder()
|
||||
.setDescription(
|
||||
Application.getName() + " " +
|
||||
Application.getApplicationVersion()))
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void createTrace(int id, String name) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(id))
|
||||
.setLanguage(Language.newBuilder()
|
||||
.setId("Toy:BE:64:default"))
|
||||
.setCompiler(Compiler.newBuilder()
|
||||
.setId("default"))
|
||||
.setPath(FilePath.newBuilder()
|
||||
.setPath("test/" + name)))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyCreateTrace(ReplyCreateTrace.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void startTx(int traceId, int txId, String description) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestStartTx(RequestStartTx.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setTxid(TxId.newBuilder().setId(txId))
|
||||
.setDescription(description))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyStartTx(ReplyStartTx.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void endTx(int traceId, int txId) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestEndTx(RequestEndTx.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setTxid(TxId.newBuilder().setId(txId))
|
||||
.setAbort(false))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyEndTx(ReplyEndTx.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public class Tx implements AutoCloseable {
|
||||
private final int traceId;
|
||||
private final int txId;
|
||||
|
||||
public Tx(int traceId, int txId, String description) throws IOException {
|
||||
this.traceId = traceId;
|
||||
this.txId = txId;
|
||||
startTx(traceId, txId, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
endTx(traceId, txId);
|
||||
}
|
||||
}
|
||||
|
||||
public void snapshot(int traceId, long snap, String description) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestSnapshot(RequestSnapshot.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder()
|
||||
.setSnap(snap))
|
||||
.setDescription(description))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplySnapshot(ReplySnapshot.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void createRootObject(int traceId, TargetObjectSchema schema) throws IOException {
|
||||
String xmlCtx = XmlSchemaContext.serialize(schema.getContext());
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestCreateRootObject(RequestCreateRootObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSchemaContext(xmlCtx)
|
||||
.setRootSchema(schema.getName().toString()))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyCreateObject(ReplyCreateObject.newBuilder()
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setId(0)))
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void activate(int traceId, String path) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestActivate(RequestActivate.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path))))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyActivate(ReplyActivate.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -28,6 +28,7 @@ import ghidra.async.AsyncPairingQueue;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
@@ -89,6 +90,11 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Test Trace RMI connnection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return new InetSocketAddress("localhost", 0);
|
||||
@@ -185,4 +191,9 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Target> getTargets() {
|
||||
return List.copyOf(targets.values());
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,8 @@ src/main/resources/images/breakpoints-disable-all.png||GHIDRA||||END|
|
||||
src/main/resources/images/breakpoints-enable-all.png||GHIDRA||||END|
|
||||
src/main/resources/images/breakpoints-make-effective.png||GHIDRA||||END|
|
||||
src/main/resources/images/conf.png||GHIDRA||||END|
|
||||
src/main/resources/images/connect-accept.png||GHIDRA||||END|
|
||||
src/main/resources/images/connect-outbound.png||GHIDRA||||END|
|
||||
src/main/resources/images/connect.png||GHIDRA||||END|
|
||||
src/main/resources/images/console.png||GHIDRA||||END|
|
||||
src/main/resources/images/debugger.png||GHIDRA||||END|
|
||||
@@ -157,6 +159,8 @@ src/main/svg/breakpoints-clear-all.svg||GHIDRA||||END|
|
||||
src/main/svg/breakpoints-disable-all.svg||GHIDRA||||END|
|
||||
src/main/svg/breakpoints-enable-all.svg||GHIDRA||||END|
|
||||
src/main/svg/breakpoints-make-effective.svg||GHIDRA||||END|
|
||||
src/main/svg/connect-accept.svg||GHIDRA||||END|
|
||||
src/main/svg/connect-outbound.svg||GHIDRA||||END|
|
||||
src/main/svg/connect.svg||GHIDRA||||END|
|
||||
src/main/svg/console.svg||GHIDRA||||END|
|
||||
src/main/svg/debugger.svg||GHIDRA||||END|
|
||||
|
||||
@@ -89,6 +89,8 @@ icon.debugger.tree.object = icon.debugger.object.unpopulated
|
||||
|
||||
icon.debugger = debugger.png
|
||||
icon.debugger.connect = connect.png
|
||||
icon.debugger.connect.accept = connect-accept.png
|
||||
icon.debugger.connect.outbound = connect-outbound.png
|
||||
icon.debugger.disconnect = disconnect.png
|
||||
icon.debugger.process = process.png
|
||||
icon.debugger.thread = thread.png
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user