mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-23 20:59:58 +00:00
GP-3836: Add Trace RMI 'Connections' pane.
This commit is contained in:
parent
5fd01c739d
commit
bf8f7c8f78
@ -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):
|
||||
@ -114,7 +117,8 @@ class State(object):
|
||||
|
||||
|
||||
STATE = State()
|
||||
|
||||
|
||||
|
||||
def ghidra_trace_connect(address=None):
|
||||
"""
|
||||
Connect Python to Ghidra for tracing
|
||||
@ -124,8 +128,9 @@ 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:
|
||||
raise RuntimeError("address must be in the form 'host:port'")
|
||||
@ -133,11 +138,13 @@ def ghidra_trace_connect(address=None):
|
||||
try:
|
||||
c = socket.socket()
|
||||
c.connect((host, int(port)))
|
||||
STATE.client = Client(c, methods.REGISTRY)
|
||||
except ValueError:
|
||||
# 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")
|
||||
|
||||
|
||||
|
||||
def ghidra_trace_listen(address='0.0.0.0:0'):
|
||||
"""
|
||||
Listen for Ghidra to connect for tracing
|
||||
@ -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
|
||||
@ -363,7 +371,7 @@ def put_bytes(start, end, pages, display_result):
|
||||
if end - start <= 0:
|
||||
return {'count': 0}
|
||||
buf = dbg().read(start, end - start)
|
||||
|
||||
|
||||
count = 0
|
||||
if buf != None:
|
||||
base, addr = trace.memory_mapper.map(nproc, start)
|
||||
@ -405,7 +413,7 @@ def ghidra_trace_putmem(items):
|
||||
address = items[0]
|
||||
length = items[1]
|
||||
pages = items[2] if len(items) > 2 else True
|
||||
|
||||
|
||||
STATE.require_tx()
|
||||
return putmem(address, length, pages, True)
|
||||
|
||||
@ -418,7 +426,7 @@ def ghidra_trace_putval(items):
|
||||
items = items.split(" ")
|
||||
value = items[0]
|
||||
pages = items[1] if len(items) > 1 else True
|
||||
|
||||
|
||||
STATE.require_tx()
|
||||
try:
|
||||
start = util.parse_and_eval(value)
|
||||
@ -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):
|
||||
@ -614,9 +623,9 @@ def eval_value(value, schema=None):
|
||||
return to_string_list(value, 'utf-8'), schema
|
||||
if schema == sch.CHAR_ARR:
|
||||
return to_string(value, 'utf-8'), sch.CHAR_ARR
|
||||
if schema == sch.STRING:
|
||||
if schema == sch.STRING:
|
||||
return to_string(value, 'utf-8'), sch.STRING
|
||||
|
||||
|
||||
return value, schema
|
||||
|
||||
|
||||
@ -796,7 +805,7 @@ def ghidra_trace_activate(path=None):
|
||||
This has no effect if the current trace is not current in Ghidra. If path is
|
||||
omitted, this will activate the current frame.
|
||||
"""
|
||||
|
||||
|
||||
activate(path)
|
||||
|
||||
|
||||
@ -822,7 +831,7 @@ def ghidra_trace_disassemble(address):
|
||||
|
||||
def compute_proc_state(nproc=None):
|
||||
status = dbg()._control.GetExecutionStatus()
|
||||
if status == DbgEng.DEBUG_STATUS_BREAK:
|
||||
if status == DbgEng.DEBUG_STATUS_BREAK:
|
||||
return 'STOPPED'
|
||||
return 'RUNNING'
|
||||
|
||||
@ -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:
|
||||
@ -930,10 +941,10 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
|
||||
width, prot = bp.GetDataParameters()
|
||||
width = str(width)
|
||||
prot = {4: 'HW_EXECUTE', 2: 'READ', 1: 'WRITE'}[prot]
|
||||
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()
|
||||
@ -998,7 +1008,7 @@ def ghidra_trace_put_breakpoints():
|
||||
STATE.require_tx()
|
||||
with STATE.client.batch() as b:
|
||||
put_breakpoints()
|
||||
|
||||
|
||||
|
||||
def put_environment():
|
||||
epath = ENV_PATTERN.format(procnum=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))
|
||||
@ -1089,13 +1102,13 @@ def put_modules():
|
||||
modobj.set_value('Size', hex(size))
|
||||
modobj.set_value('Flags', hex(size))
|
||||
modobj.insert()
|
||||
|
||||
# TODO: would be nice to list sections, but currently we have no API for
|
||||
|
||||
# TODO: would be nice to list sections, but currently we have no API for
|
||||
# it as far as I am aware
|
||||
# sec_keys = []
|
||||
# STATE.trace.proxy_object_path(
|
||||
# mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
||||
|
||||
|
||||
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
||||
procnum=nproc)).retain_values(mod_keys)
|
||||
|
||||
@ -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
|
||||
|
@ -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,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);
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,532 @@
|
||||
/* ###
|
||||
* 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 java.awt.AWTEvent;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.*;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.WindowPosition;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.tree.*;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginUtils;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter {
|
||||
public static final String TITLE = "Connections";
|
||||
public static final HelpLocation HELP =
|
||||
new HelpLocation(PluginUtils.getPluginNameFromClass(TraceRmiConnectionManagerPlugin.class),
|
||||
DebuggerResources.HELP_ANCHOR_PLUGIN);
|
||||
|
||||
private static final String GROUP_SERVER = "2. Server";
|
||||
private static final String GROUP_CONNECT = "1. Connect";
|
||||
private static final String GROUP_MAINTENANCE = "3. Maintenance";
|
||||
|
||||
private static final ParameterDescription<String> PARAM_ADDRESS =
|
||||
ParameterDescription.create(String.class, "address", true, "localhost",
|
||||
"Host/Address", "Address or hostname for interface(s) to listen on");
|
||||
private static final ParameterDescription<Integer> PARAM_PORT =
|
||||
ParameterDescription.create(Integer.class, "port", true, 0,
|
||||
"Port", "TCP port number, 0 for ephemeral");
|
||||
private static final TargetParameterMap PARAMETERS = TargetParameterMap.ofEntries(
|
||||
Map.entry(PARAM_ADDRESS.name, PARAM_ADDRESS),
|
||||
Map.entry(PARAM_PORT.name, PARAM_PORT));
|
||||
|
||||
interface StartServerAction {
|
||||
String NAME = "Start Server";
|
||||
String DESCRIPTION = "Start a TCP server for incoming connections (indefinitely)";
|
||||
String GROUP = GROUP_SERVER;
|
||||
String HELP_ANCHOR = "start_server";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface StopServerAction {
|
||||
String NAME = "Stop Server";
|
||||
String DESCRIPTION = "Close the TCP server";
|
||||
String GROUP = GROUP_SERVER;
|
||||
String HELP_ANCHOR = "stop_server";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectAcceptAction {
|
||||
String NAME = "Connect by Accept";
|
||||
String DESCRIPTION = "Accept a single inbound TCP connection";
|
||||
String GROUP = GROUP_CONNECT;
|
||||
Icon ICON = DebuggerResources.ICON_CONNECT_ACCEPT;
|
||||
String HELP_ANCHOR = "connect_accept";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectOutboundAction {
|
||||
String NAME = "Connect Outbound";
|
||||
String DESCRIPTION = "Connect to a listening agent/plugin by TCP";
|
||||
String GROUP = GROUP_CONNECT;
|
||||
Icon ICON = DebuggerResources.ICON_CONNECT_OUTBOUND;
|
||||
String HELP_ANCHOR = "connect_outbound";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface CloseConnectionAction {
|
||||
String NAME = "Close";
|
||||
String DESCRIPTION = "Close a connection or server";
|
||||
String GROUP = GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "close";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.popupMenuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.popupMenuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface CloseAllAction {
|
||||
String NAME = "Close All";
|
||||
String DESCRIPTION = "Close all connections and the server";
|
||||
String GROUP = GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "close_all";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
class InjectableGTree extends GTree {
|
||||
public InjectableGTree(GTreeNode root) {
|
||||
super(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows the test framework to use reflection to access this method.
|
||||
*/
|
||||
@Override
|
||||
protected void processEvent(AWTEvent e) {
|
||||
super.processEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final TraceRmiConnectionManagerPlugin plugin;
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
TraceRmiService traceRmiService;
|
||||
// @AutoServiceConsumed via method
|
||||
DebuggerTargetService targetService;
|
||||
@AutoServiceConsumed
|
||||
DebuggerConsoleService consoleService;
|
||||
@AutoServiceConsumed
|
||||
DebuggerTraceManagerService traceManagerService;
|
||||
@AutoServiceConsumed
|
||||
DebuggerControlService controlService;
|
||||
@SuppressWarnings("unused")
|
||||
private final Wiring autoServiceWiring;
|
||||
|
||||
private JPanel mainPanel;
|
||||
protected GTree tree;
|
||||
protected TraceRmiServiceNode rootNode = new TraceRmiServiceNode(this);
|
||||
|
||||
DockingAction actionStartServer;
|
||||
DockingAction actionStopServer;
|
||||
DockingAction actionConnectAccept;
|
||||
DockingAction actionConnectOutbound;
|
||||
DockingAction actionCloseConnection;
|
||||
DockingAction actionCloseAll;
|
||||
|
||||
TraceRmiManagerActionContext myActionContext;
|
||||
|
||||
public TraceRmiConnectionManagerProvider(TraceRmiConnectionManagerPlugin plugin) {
|
||||
super(plugin.getTool(), TITLE, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
setTitle(TITLE);
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_TARGETS);
|
||||
setHelpLocation(HELP);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
||||
buildMainPanel();
|
||||
|
||||
setDefaultWindowPosition(WindowPosition.LEFT);
|
||||
setVisible(true);
|
||||
createActions();
|
||||
}
|
||||
|
||||
private void buildMainPanel() {
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
tree = new InjectableGTree(rootNode);
|
||||
tree.setRootVisible(false);
|
||||
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
mainPanel.add(tree);
|
||||
|
||||
tree.getGTSelectionModel().addGTreeSelectionListener(evt -> {
|
||||
setContext();
|
||||
});
|
||||
tree.addGTModelListener((AnyChangeTreeModelListener) e -> {
|
||||
setContext();
|
||||
});
|
||||
// TODO: Double-click or ENTER (activate) should open and/or activate trace/snap
|
||||
tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateSelectedNode();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
tree.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateSelectedNode();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void activateSelectedNode() {
|
||||
List<GTreeNode> selList = tree.getSelectedNodes();
|
||||
if (selList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
assert selList.size() == 1;
|
||||
GTreeNode sel = selList.get(0);
|
||||
nodeActivated((TraceRmiManagerNode) sel);
|
||||
}
|
||||
|
||||
private void nodeActivated(TraceRmiManagerNode node) {
|
||||
if (node instanceof TraceRmiTargetNode tNode) {
|
||||
if (traceManagerService == null) {
|
||||
return;
|
||||
}
|
||||
Target target = tNode.getTarget();
|
||||
traceManagerService.activateTarget(target);
|
||||
if (controlService == null) {
|
||||
return;
|
||||
}
|
||||
if (!controlService.getCurrentMode(target.getTrace()).isTarget()) {
|
||||
controlService.setCurrentMode(target.getTrace(), ControlMode.RO_TARGET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
actionStartServer = StartServerAction.builder(plugin)
|
||||
.enabledWhen(this::isActionStartServerEnabled)
|
||||
.onAction(this::doActionStartServerActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
actionStopServer = StopServerAction.builder(plugin)
|
||||
.enabledWhen(this::isActionStopServerEnabled)
|
||||
.onAction(this::doActionStopServerActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
actionConnectAccept = ConnectAcceptAction.builder(plugin)
|
||||
.enabledWhen(this::isActionConnectAcceptEnabled)
|
||||
.onAction(this::doActionConnectAcceptActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
actionConnectOutbound = ConnectOutboundAction.builder(plugin)
|
||||
.enabledWhen(this::isActionConnectOutboundEnabled)
|
||||
.onAction(this::doActionConnectOutboundActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
actionCloseConnection = CloseConnectionAction.builder(plugin)
|
||||
.withContext(TraceRmiManagerActionContext.class)
|
||||
.enabledWhen(this::isActionCloseConnectionEnabled)
|
||||
.onAction(this::doActionCloseConnectionActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
actionCloseAll = CloseAllAction.builder(plugin)
|
||||
.enabledWhen(this::isActionCloseAllEnabled)
|
||||
.onAction(this::doActionCloseAllActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (myActionContext == null) {
|
||||
return super.getActionContext(event);
|
||||
}
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
private void setContext() {
|
||||
myActionContext = new TraceRmiManagerActionContext(this, tree.getSelectionPath(), tree);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
private boolean isActionStartServerEnabled(ActionContext __) {
|
||||
return traceRmiService != null && !traceRmiService.isServerStarted();
|
||||
}
|
||||
|
||||
private InetSocketAddress promptSocketAddress(String title, String okText) {
|
||||
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
|
||||
title, okText, DebuggerResources.ICON_CONNECTION);
|
||||
Map<String, ?> arguments;
|
||||
do {
|
||||
dialog.forgetMemorizedArguments();
|
||||
arguments = dialog.promptArguments(PARAMETERS);
|
||||
}
|
||||
while (dialog.isResetRequested());
|
||||
if (arguments == null) {
|
||||
return null;
|
||||
}
|
||||
String address = PARAM_ADDRESS.get(arguments);
|
||||
int port = PARAM_PORT.get(arguments);
|
||||
return new InetSocketAddress(address, port);
|
||||
}
|
||||
|
||||
private void doActionStartServerActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start");
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
traceRmiService.setServerAddress(sockaddr);
|
||||
traceRmiService.startServer();
|
||||
if (consoleService != null) {
|
||||
consoleService.log(DebuggerResources.ICON_CONNECTION,
|
||||
"TraceRmi Server listening at " + traceRmiService.getServerAddress());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Could not start TraceRmi server: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionStopServerEnabled(ActionContext __) {
|
||||
return traceRmiService != null && traceRmiService.isServerStarted();
|
||||
}
|
||||
|
||||
private void doActionStopServerActivated(ActionContext __) {
|
||||
traceRmiService.stopServer();
|
||||
if (consoleService != null) {
|
||||
consoleService.log(DebuggerResources.ICON_DISCONNECT, "TraceRmi Server stopped");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionConnectAcceptEnabled(ActionContext __) {
|
||||
return traceRmiService != null;
|
||||
}
|
||||
|
||||
private void doActionConnectAcceptActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen");
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> {
|
||||
// TODO: Progress entry
|
||||
try {
|
||||
TraceRmiAcceptor acceptor = traceRmiService.acceptOne(sockaddr);
|
||||
acceptor.accept();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Nothing. User should already know.
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(this, null, "Accept",
|
||||
"Could not accept Trace RMI Connection on " + sockaddr + ": " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isActionConnectOutboundEnabled(ActionContext __) {
|
||||
return traceRmiService != null;
|
||||
}
|
||||
|
||||
private void doActionConnectOutboundActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect");
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> {
|
||||
// TODO: Progress entry?
|
||||
try {
|
||||
traceRmiService.connect(sockaddr);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(this, null, "Connect",
|
||||
"Could connect to Trace RMI at " + sockaddr + ": " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isActionCloseConnectionEnabled(TraceRmiManagerActionContext context) {
|
||||
TraceRmiManagerNode node = context.getSelectedNode();
|
||||
if (node instanceof TraceRmiConnectionNode) {
|
||||
return true;
|
||||
}
|
||||
if (node instanceof TraceRmiAcceptorNode) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doActionCloseConnectionActivated(TraceRmiManagerActionContext context) {
|
||||
TraceRmiManagerNode node = context.getSelectedNode();
|
||||
if (node instanceof TraceRmiConnectionNode cxNode) {
|
||||
try {
|
||||
cxNode.getConnection().close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "Close Connection",
|
||||
"Could not close Trace RMI connection: " + e);
|
||||
}
|
||||
}
|
||||
else if (node instanceof TraceRmiAcceptorNode acNode) {
|
||||
acNode.getAcceptor().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionCloseAllEnabled(ActionContext __) {
|
||||
return traceRmiService != null;
|
||||
}
|
||||
|
||||
private void doActionCloseAllActivated(ActionContext __) {
|
||||
try {
|
||||
doActionStopServerActivated(__);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Could not close server: " + e);
|
||||
}
|
||||
for (TraceRmiConnection connection : traceRmiService.getAllConnections()) {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Could not close " + connection + ": " + e);
|
||||
}
|
||||
}
|
||||
for (TraceRmiAcceptor acceptor : traceRmiService.getAllAcceptors()) {
|
||||
try {
|
||||
acceptor.cancel();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Could not cancel " + acceptor + ": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setTraceRmiService(TraceRmiService traceRmiService) {
|
||||
if (this.traceRmiService != null) {
|
||||
this.traceRmiService.removeTraceServiceListener(rootNode);
|
||||
}
|
||||
this.traceRmiService = traceRmiService;
|
||||
if (this.traceRmiService != null) {
|
||||
this.traceRmiService.addTraceServiceListener(rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setTargetService(DebuggerTargetService targetService) {
|
||||
if (this.targetService != null) {
|
||||
this.targetService.removeTargetPublicationListener(rootNode);
|
||||
}
|
||||
this.targetService = targetService;
|
||||
if (this.targetService != null) {
|
||||
this.targetService.addTargetPublicationListener(rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
public TraceRmiService getTraceRmiService() {
|
||||
return traceRmiService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates, whether active or inactive, for a trace changed
|
||||
*
|
||||
* @param coordinates the coordinates
|
||||
*/
|
||||
public void coordinates(DebuggerCoordinates coordinates) {
|
||||
if (rootNode == null) {
|
||||
return;
|
||||
}
|
||||
rootNode.coordinates(coordinates);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
@ -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.*;
|
||||
|
@ -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.*;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.*;
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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'):
|
||||
|
@ -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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -68,6 +68,8 @@ public interface DebuggerResources {
|
||||
Icon ICON_DEBUGGER = new GIcon("icon.debugger");
|
||||
|
||||
Icon ICON_CONNECTION = new GIcon("icon.debugger.connect");
|
||||
Icon ICON_CONNECT_ACCEPT = new GIcon("icon.debugger.connect.accept");
|
||||
Icon ICON_CONNECT_OUTBOUND = new GIcon("icon.debugger.connect.outbound");
|
||||
Icon ICON_DISCONNECT = new GIcon("icon.debugger.disconnect");
|
||||
|
||||
Icon ICON_PROCESS = new GIcon("icon.debugger.process");
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.console;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -22,8 +23,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.table.TableCellEditor;
|
||||
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.ActionList;
|
||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
||||
|
||||
@ -36,8 +39,13 @@ public class ConsoleActionsCellEditor extends AbstractCellEditor
|
||||
|
||||
protected ActionList value;
|
||||
|
||||
protected Color bg = new Color(0); // Initial cached value
|
||||
|
||||
public ConsoleActionsCellEditor() {
|
||||
ConsoleActionsCellRenderer.configureBox(box);
|
||||
Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4);
|
||||
Border outerBorder = BorderFactory.createLineBorder(Palette.YELLOW, 1);
|
||||
box.setBorder(BorderFactory.createCompoundBorder(outerBorder, innerBorder));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,7 +57,11 @@ public class ConsoleActionsCellEditor extends AbstractCellEditor
|
||||
public Component getTableCellEditorComponent(JTable table, Object v, boolean isSelected,
|
||||
int row, int column) {
|
||||
// I can't think of when you'd be "editing" a non-selected cell.
|
||||
box.setBackground(table.getSelectionBackground());
|
||||
if (bg.getRGB() != table.getSelectionBackground().getRGB()) {
|
||||
bg = new Color(table.getSelectionBackground().getRGB());
|
||||
}
|
||||
box.setBackground(bg);
|
||||
box.setOpaque(true);
|
||||
|
||||
value = (ActionList) v;
|
||||
ConsoleActionsCellRenderer.populateBox(box, buttonCache, value,
|
||||
|
@ -84,6 +84,7 @@ public class ConsoleActionsCellRenderer extends AbstractGhidraColumnRenderer<Act
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data); // A bit of a waste, but sets the background
|
||||
box.setBackground(getBackground());
|
||||
box.setBorder(getBorder());
|
||||
|
||||
ActionList value = (ActionList) data.getValue();
|
||||
populateBox(box, buttonCache, value, button -> {
|
||||
|
@ -152,7 +152,7 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
|
||||
* @param ctx the context
|
||||
* @return the the log entry
|
||||
*/
|
||||
public LogRow getLogRow(ActionContext ctx) {
|
||||
public LogRow<?> getLogRow(ActionContext ctx) {
|
||||
return provider.getLogRow(ctx);
|
||||
}
|
||||
}
|
||||
|
@ -32,23 +32,29 @@ import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.*;
|
||||
import docking.actions.PopupActionProvider;
|
||||
import docking.widgets.table.ColumnSortState.SortDirection;
|
||||
import docking.widgets.table.CustomToStringCellRenderer;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ClearAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
|
||||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||
import ghidra.app.services.ProgressService;
|
||||
import ghidra.debug.api.progress.MonitorReceiver;
|
||||
import ghidra.debug.api.progress.ProgressListener;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.options.annotation.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import resources.Icons;
|
||||
|
||||
public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
implements PopupActionProvider {
|
||||
@ -57,19 +63,37 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE);
|
||||
static final int MIN_ROW_HEIGHT = 16;
|
||||
|
||||
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow> {
|
||||
LEVEL("Level", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false),
|
||||
MESSAGE("Message", String.class, LogRow::getMessage, SortDirection.ASCENDING, false),
|
||||
ACTIONS("Actions", ActionList.class, LogRow::getActions, SortDirection.DESCENDING, true),
|
||||
TIME("Time", Date.class, LogRow::getDate, SortDirection.DESCENDING, false);
|
||||
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow<?>> {
|
||||
ICON("Icon", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false),
|
||||
MESSAGE("Message", Object.class, LogRow::getMessage, SortDirection.ASCENDING, false) {
|
||||
@Override
|
||||
public GColumnRenderer<?> getRenderer() {
|
||||
return HtmlOrProgressCellRenderer.INSTANCE;
|
||||
}
|
||||
},
|
||||
ACTIONS("Actions", ActionList.class, LogRow::getActions, SortDirection.DESCENDING, true) {
|
||||
private static final ConsoleActionsCellRenderer RENDERER =
|
||||
new ConsoleActionsCellRenderer();
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<?> getRenderer() {
|
||||
return RENDERER;
|
||||
}
|
||||
},
|
||||
TIME("Time", Date.class, LogRow::getDate, SortDirection.DESCENDING, false) {
|
||||
@Override
|
||||
public GColumnRenderer<?> getRenderer() {
|
||||
return CustomToStringCellRenderer.TIME_24HMSms;
|
||||
}
|
||||
};
|
||||
|
||||
private final String header;
|
||||
private final Function<LogRow, ?> getter;
|
||||
private final Function<LogRow<?>, ?> getter;
|
||||
private final Class<?> cls;
|
||||
private final SortDirection defaultSortDirection;
|
||||
private final boolean editable;
|
||||
|
||||
<T> LogTableColumns(String header, Class<T> cls, Function<LogRow, T> getter,
|
||||
<T> LogTableColumns(String header, Class<T> cls, Function<LogRow<?>, T> getter,
|
||||
SortDirection defaultSortDirection, boolean editable) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
@ -89,17 +113,17 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(LogRow row) {
|
||||
public Object getValueOf(LogRow<?> row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(LogRow row) {
|
||||
public boolean isEditable(LogRow<?> row) {
|
||||
return editable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(LogRow row, Object value) {
|
||||
public void setValueOf(LogRow<?> row, Object value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -164,14 +188,26 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
* <p>
|
||||
* This class is public for access by test cases only.
|
||||
*/
|
||||
public static class LogRow {
|
||||
public interface LogRow<T> {
|
||||
Icon getIcon();
|
||||
|
||||
T getMessage();
|
||||
|
||||
ActionList getActions();
|
||||
|
||||
Date getDate();
|
||||
|
||||
ActionContext getActionContext();
|
||||
}
|
||||
|
||||
static class MessageLogRow implements LogRow<String> {
|
||||
private final Icon icon;
|
||||
private final String message;
|
||||
private final Date date;
|
||||
private final ActionContext context;
|
||||
private final ActionList actions;
|
||||
|
||||
public LogRow(Icon icon, String message, Date date, ActionContext context,
|
||||
public MessageLogRow(Icon icon, String message, Date date, ActionContext context,
|
||||
ActionList actions) {
|
||||
this.icon = icon;
|
||||
this.message = message;
|
||||
@ -180,32 +216,154 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
this.actions = Objects.requireNonNull(actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionList getActions() {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
static class MonitorLogRow implements LogRow<MonitorReceiver> {
|
||||
static final GIcon ICON = new GIcon("icon.pending");
|
||||
|
||||
private final MonitorReceiver monitor;
|
||||
private final Date date;
|
||||
private final ActionContext context;
|
||||
private final ActionList actions;
|
||||
|
||||
public MonitorLogRow(MonitorReceiver monitor, Date date, ActionContext context,
|
||||
ActionList actions) {
|
||||
this.monitor = monitor;
|
||||
this.date = date;
|
||||
this.context = context;
|
||||
this.actions = Objects.requireNonNull(actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MonitorReceiver getMessage() {
|
||||
return monitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionList getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
private class ListenerForProgress implements ProgressListener {
|
||||
final Map<MonitorReceiver, MonitorRowConsoleActionContext> contexts = new HashMap<>();
|
||||
CancelAction cancelAction = new CancelAction(plugin);
|
||||
|
||||
ActionContext contextFor(MonitorReceiver monitor) {
|
||||
return contexts.computeIfAbsent(monitor, MonitorRowConsoleActionContext::new);
|
||||
}
|
||||
|
||||
ActionList bindActions(ActionContext context) {
|
||||
ActionList actions = new ActionList();
|
||||
actions.add(new BoundAction(cancelAction, context));
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void monitorCreated(MonitorReceiver monitor) {
|
||||
ActionContext context = contextFor(monitor);
|
||||
logRow(new MonitorLogRow(monitor, new Date(), context, bindActions(context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void monitorDisposed(MonitorReceiver monitor, Disposal disposal) {
|
||||
ActionContext context = contexts.remove(monitor);
|
||||
if (context == null) {
|
||||
context = new MonitorRowConsoleActionContext(monitor);
|
||||
}
|
||||
removeFromLog(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageUpdated(MonitorReceiver monitor, String message) {
|
||||
LogRow<?> logRow = logTableModel.getMap().get(contextFor(monitor));
|
||||
logTableModel.updateItem(logRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progressUpdated(MonitorReceiver monitor, long progress) {
|
||||
LogRow<?> logRow = logTableModel.getMap().get(contextFor(monitor));
|
||||
logTableModel.updateItem(logRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeUpdated(MonitorReceiver monitor) {
|
||||
LogRow<?> logRow = logTableModel.getMap().get(contextFor(monitor));
|
||||
logTableModel.updateItem(logRow);
|
||||
}
|
||||
}
|
||||
|
||||
static class CancelAction extends DockingAction {
|
||||
static final Icon ICON = Icons.STOP_ICON;
|
||||
|
||||
public CancelAction(Plugin owner) {
|
||||
super("Cancel", owner.getName());
|
||||
setToolBarData(new ToolBarData(ICON));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (!(context instanceof MonitorRowConsoleActionContext ctx)) {
|
||||
return;
|
||||
}
|
||||
ctx.getMonitor().cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (!(context instanceof MonitorRowConsoleActionContext ctx)) {
|
||||
return false;
|
||||
}
|
||||
MonitorReceiver monitor = ctx.getMonitor();
|
||||
return monitor.isCancelEnabled() && !monitor.isCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
LogTableColumns, ActionContext, LogRow, LogRow> {
|
||||
LogTableColumns, ActionContext, LogRow<?>, LogRow<?>> {
|
||||
|
||||
public LogTableModel(PluginTool tool) {
|
||||
super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r, r -> r);
|
||||
super(tool, "Log", LogTableColumns.class, r -> r == null ? null : r.getActionContext(),
|
||||
r -> r, r -> r);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -215,7 +373,6 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
protected static class LogTable extends GhidraTable {
|
||||
|
||||
public LogTable(LogTableModel model) {
|
||||
super(model);
|
||||
}
|
||||
@ -255,12 +412,11 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
ActionList actions =
|
||||
(ActionList) getModel().getValueAt(r, convertColumnIndexToModel(c));
|
||||
if (actions != null && !actions.isEmpty()) {
|
||||
return ACTION_BUTTON_SIZE;
|
||||
return ACTION_BUTTON_SIZE + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (renderer instanceof CustomToStringCellRenderer<?>) {
|
||||
CustomToStringCellRenderer<?> custom = (CustomToStringCellRenderer<?>) renderer;
|
||||
if (renderer instanceof HtmlOrProgressCellRenderer custom) {
|
||||
int colWidth = getColumnModel().getColumn(c).getWidth();
|
||||
prepareRenderer(renderer, r, c);
|
||||
return custom.getRowHeight(colWidth);
|
||||
@ -271,6 +427,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
|
||||
private final DebuggerConsolePlugin plugin;
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
private ProgressService progressService;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@ -287,18 +445,21 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
|
||||
protected final LogTableModel logTableModel;
|
||||
protected GhidraTable logTable;
|
||||
private GhidraTableFilterPanel<LogRow> logFilterPanel;
|
||||
private GhidraTableFilterPanel<LogRow<?>> logFilterPanel;
|
||||
|
||||
private Deque<LogRow> buffer = new ArrayDeque<>();
|
||||
private Deque<LogRow<?>> buffer = new ArrayDeque<>();
|
||||
|
||||
private final JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
private final ListenerForProgress progressListener;
|
||||
|
||||
DockingAction actionClear;
|
||||
DockingAction actionSelectNone;
|
||||
|
||||
public DebuggerConsoleProvider(DebuggerConsolePlugin plugin) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
this.progressListener = new ListenerForProgress();
|
||||
|
||||
logTableModel = new LogTableModel(tool);
|
||||
|
||||
@ -329,24 +490,21 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel);
|
||||
mainPanel.add(logFilterPanel, BorderLayout.NORTH);
|
||||
|
||||
logTable.setRowHeight(ACTION_BUTTON_SIZE);
|
||||
logTable.setRowHeight(ACTION_BUTTON_SIZE + 2);
|
||||
TableColumnModel columnModel = logTable.getColumnModel();
|
||||
|
||||
TableColumn levelCol = columnModel.getColumn(LogTableColumns.LEVEL.ordinal());
|
||||
levelCol.setMaxWidth(24);
|
||||
levelCol.setMinWidth(24);
|
||||
TableColumn iconCol = columnModel.getColumn(LogTableColumns.ICON.ordinal());
|
||||
iconCol.setMaxWidth(24);
|
||||
iconCol.setMinWidth(24);
|
||||
|
||||
TableColumn msgCol = columnModel.getColumn(LogTableColumns.MESSAGE.ordinal());
|
||||
msgCol.setPreferredWidth(150);
|
||||
msgCol.setCellRenderer(CustomToStringCellRenderer.HTML);
|
||||
|
||||
TableColumn actCol = columnModel.getColumn(LogTableColumns.ACTIONS.ordinal());
|
||||
actCol.setPreferredWidth(50);
|
||||
actCol.setCellRenderer(new ConsoleActionsCellRenderer());
|
||||
actCol.setCellEditor(new ConsoleActionsCellEditor());
|
||||
|
||||
TableColumn timeCol = columnModel.getColumn(LogTableColumns.TIME.ordinal());
|
||||
timeCol.setCellRenderer(CustomToStringCellRenderer.TIME_24HMSms);
|
||||
timeCol.setPreferredWidth(15);
|
||||
}
|
||||
|
||||
@ -362,8 +520,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
|
||||
private void activatedClear(ActionContext ctx) {
|
||||
synchronized (buffer) {
|
||||
logTableModel.clear();
|
||||
buffer.clear();
|
||||
logTableModel.deleteItemsWith(r -> !(r instanceof MonitorLogRow));
|
||||
buffer.removeIf(r -> !(r instanceof MonitorLogRow));
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,7 +534,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
if (logTable.getSelectedRowCount() != 1) {
|
||||
return super.getActionContext(event);
|
||||
}
|
||||
LogRow sel = logFilterPanel.getSelectedItem();
|
||||
LogRow<?> sel = logFilterPanel.getSelectedItem();
|
||||
if (sel == null) {
|
||||
// I guess this can happen because of timing?
|
||||
return super.getActionContext(event);
|
||||
@ -407,12 +565,13 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
protected void log(Icon icon, String message, ActionContext context) {
|
||||
logRow(new LogRow(icon, message, new Date(), context, computeToolbarActions(context)));
|
||||
logRow(
|
||||
new MessageLogRow(icon, message, new Date(), context, computeToolbarActions(context)));
|
||||
}
|
||||
|
||||
protected void logRow(LogRow row) {
|
||||
protected void logRow(LogRow<?> row) {
|
||||
synchronized (buffer) {
|
||||
LogRow old = logTableModel.deleteKey(row.getActionContext());
|
||||
LogRow<?> old = logTableModel.deleteKey(row.getActionContext());
|
||||
if (old != null) {
|
||||
buffer.remove(old);
|
||||
}
|
||||
@ -438,14 +597,14 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
|
||||
protected void logEvent(LogEvent event) {
|
||||
ActionContext context = new LogRowConsoleActionContext();
|
||||
logRow(new LogRow(iconForLevel(event.getLevel()),
|
||||
logRow(new MessageLogRow(iconForLevel(event.getLevel()),
|
||||
"<html>" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()),
|
||||
new Date(event.getTimeMillis()), context, computeToolbarActions(context)));
|
||||
}
|
||||
|
||||
protected void removeFromLog(ActionContext context) {
|
||||
synchronized (buffer) {
|
||||
LogRow r = logTableModel.deleteKey(context);
|
||||
LogRow<?> r = logTableModel.deleteKey(context);
|
||||
buffer.remove(r);
|
||||
}
|
||||
}
|
||||
@ -508,7 +667,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
|
||||
public List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
|
||||
return streamActions(context)
|
||||
.filter(a -> a.isAddToPopup(context))
|
||||
.collect(Collectors.toList());
|
||||
@ -518,14 +677,41 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||
synchronized (buffer) {
|
||||
return logTableModel.getModelData()
|
||||
.stream()
|
||||
.filter(r -> ctxCls.isInstance(r.context))
|
||||
.filter(r -> ctxCls.isInstance(r.getActionContext()))
|
||||
.count();
|
||||
}
|
||||
}
|
||||
|
||||
public LogRow getLogRow(ActionContext ctx) {
|
||||
public LogRow<?> getLogRow(ActionContext ctx) {
|
||||
synchronized (buffer) {
|
||||
return logTableModel.getMap().get(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setProgressService(ProgressService progressService) {
|
||||
if (this.progressService != null) {
|
||||
this.progressService.removeProgressListener(progressListener);
|
||||
}
|
||||
this.progressService = progressService;
|
||||
if (this.progressService != null) {
|
||||
this.progressService.addProgressListener(progressListener);
|
||||
}
|
||||
resyncProgressRows();
|
||||
}
|
||||
|
||||
private void resyncProgressRows() {
|
||||
synchronized (buffer) {
|
||||
logTableModel.deleteItemsWith(r -> r instanceof MonitorLogRow);
|
||||
if (progressService == null) {
|
||||
return;
|
||||
}
|
||||
for (MonitorReceiver monitor : progressService.getAllMonitors()) {
|
||||
if (!monitor.isValid()) {
|
||||
continue;
|
||||
}
|
||||
progressListener.monitorCreated(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.gui.console;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JTable;
|
||||
|
||||
import docking.widgets.table.CustomToStringCellRenderer;
|
||||
import ghidra.debug.api.progress.MonitorReceiver;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public enum HtmlOrProgressCellRenderer implements GColumnRenderer<Object> {
|
||||
INSTANCE;
|
||||
|
||||
static final CustomToStringCellRenderer<String> FOR_STRING =
|
||||
CustomToStringCellRenderer.HTML;
|
||||
static final MonitorCellRenderer FOR_MONITOR = MonitorCellRenderer.INSTANCE;
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
if (value == null) {
|
||||
return FOR_STRING.getTableCellRendererComponent(table, value, isSelected,
|
||||
hasFocus, row, column);
|
||||
}
|
||||
if (value instanceof String message) {
|
||||
return FOR_STRING.getTableCellRendererComponent(table, message, isSelected,
|
||||
hasFocus, row, column);
|
||||
}
|
||||
if (value instanceof MonitorReceiver monitor) {
|
||||
return FOR_MONITOR.getTableCellRendererComponent(table, monitor, isSelected,
|
||||
hasFocus, row, column);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
int getRowHeight(int colWidth) {
|
||||
return FOR_STRING.getRowHeight(colWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(Object t, Settings settings) {
|
||||
if (t == null) {
|
||||
return FOR_STRING.getFilterString(null, settings);
|
||||
}
|
||||
if (t instanceof String message) {
|
||||
return FOR_STRING.getFilterString(message, settings);
|
||||
}
|
||||
if (t instanceof MonitorReceiver monitor) {
|
||||
return FOR_MONITOR.getFilterString(monitor, settings);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
/* ###
|
||||
* 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.console;
|
||||
|
||||
import java.awt.*;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.debug.api.progress.MonitorReceiver;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class MonitorCellRenderer extends JPanel
|
||||
implements GColumnRenderer<MonitorReceiver> {
|
||||
static final MonitorCellRenderer INSTANCE = new MonitorCellRenderer();
|
||||
|
||||
private static final Color BACKGROUND_COLOR = new GColor("color.bg.table.row");
|
||||
private static final Color ALT_BACKGROUND_COLOR = new GColor("color.bg.table.row.alt");
|
||||
private static final String DISABLE_ALTERNATING_ROW_COLORS_PROPERTY =
|
||||
"disable.alternating.row.colors";
|
||||
|
||||
private static boolean getAlternateRowColors() {
|
||||
return !Boolean.getBoolean(DISABLE_ALTERNATING_ROW_COLORS_PROPERTY);
|
||||
}
|
||||
|
||||
static class CachedColor {
|
||||
Color cached;
|
||||
|
||||
Color copy(Color c) {
|
||||
if (cached == null || cached.getRGB() != c.getRGB()) {
|
||||
cached = new Color(c.getRGB());
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
protected CachedColor selFg = new CachedColor();
|
||||
protected CachedColor selBg = new CachedColor();
|
||||
|
||||
protected final Border focusBorder;
|
||||
protected final Border noFocusBorder;
|
||||
protected final JProgressBar bar = new JProgressBar();
|
||||
protected final JLabel label = new JLabel();
|
||||
|
||||
public MonitorCellRenderer() {
|
||||
super(new BorderLayout());
|
||||
noFocusBorder = BorderFactory.createEmptyBorder(1, 5, 1, 5);
|
||||
Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4);
|
||||
Border outerBorder = BorderFactory.createLineBorder(Palette.YELLOW, 1);
|
||||
focusBorder = BorderFactory.createCompoundBorder(outerBorder, innerBorder);
|
||||
|
||||
add(bar);
|
||||
add(label, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
protected Color getAlternatingBackgroundColor(int row) {
|
||||
if (!getAlternateRowColors() || (row & 1) == 1) {
|
||||
return BACKGROUND_COLOR;
|
||||
}
|
||||
return ALT_BACKGROUND_COLOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Component getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
setOpaque(true);
|
||||
if (isSelected) {
|
||||
setForeground(selFg.copy(table.getSelectionForeground()));
|
||||
label.setForeground(selFg.copy(table.getSelectionForeground()));
|
||||
setBackground(selBg.copy(table.getSelectionBackground()));
|
||||
}
|
||||
else {
|
||||
setForeground(table.getForeground());
|
||||
label.setForeground(table.getForeground());
|
||||
setBackground(getAlternatingBackgroundColor(row));
|
||||
}
|
||||
setBorder(hasFocus ? focusBorder : noFocusBorder);
|
||||
|
||||
if (!(value instanceof MonitorReceiver monitor)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (monitor.isCancelled()) {
|
||||
label.setText("(cancelled) " + monitor.getMessage());
|
||||
}
|
||||
else {
|
||||
label.setText(monitor.getMessage());
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
long progress = monitor.getProgress();
|
||||
long maximum = monitor.getMaximum();
|
||||
if (progress != TaskMonitor.NO_PROGRESS_VALUE) {
|
||||
if (progress <= 0) {
|
||||
sb.append("0%");
|
||||
}
|
||||
else if (progress >= maximum) {
|
||||
sb.append("100%");
|
||||
}
|
||||
else {
|
||||
sb.append(NumberFormat.getPercentInstance().format((float) progress / maximum));
|
||||
}
|
||||
if (monitor.isShowProgressValue()) {
|
||||
sb.append(" (");
|
||||
sb.append(progress);
|
||||
sb.append(" of ");
|
||||
sb.append(maximum);
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
bar.setString(sb.toString());
|
||||
bar.setStringPainted(true);
|
||||
BoundedRangeModel model = bar.getModel();
|
||||
try {
|
||||
model.setValueIsAdjusting(true);
|
||||
model.setMaximum(Integer.MAX_VALUE);
|
||||
if (progress == TaskMonitor.NO_PROGRESS_VALUE) {
|
||||
bar.setIndeterminate(true);
|
||||
model.setValue(0);
|
||||
}
|
||||
else {
|
||||
bar.setIndeterminate(monitor.isIndeterminate());
|
||||
double val = Integer.MAX_VALUE;
|
||||
val *= progress;
|
||||
val /= maximum;
|
||||
model.setValue((int) val);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
model.setValueIsAdjusting(false);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(MonitorReceiver t, Settings settings) {
|
||||
return t.getMessage();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* ###
|
||||
* 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.console;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import ghidra.debug.api.progress.MonitorReceiver;
|
||||
|
||||
public class MonitorRowConsoleActionContext extends DefaultActionContext {
|
||||
private MonitorReceiver monitor;
|
||||
|
||||
public MonitorRowConsoleActionContext(MonitorReceiver monitor) {
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof MonitorRowConsoleActionContext that)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.monitor, that.monitor)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public MonitorReceiver getMonitor() {
|
||||
return monitor;
|
||||
}
|
||||
}
|
@ -205,7 +205,7 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||
protected boolean resetRequested;
|
||||
|
||||
private final PluginTool tool;
|
||||
private Map<String, ParameterDescription<?>> parameters;
|
||||
Map<String, ParameterDescription<?>> parameters;
|
||||
|
||||
// TODO: Not sure this is the best keying, but I think it works.
|
||||
private Map<NameTypePair, Object> memorized = new HashMap<>();
|
||||
@ -275,13 +275,13 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||
close();
|
||||
}
|
||||
|
||||
private void invoke(ActionEvent evt) {
|
||||
void invoke(ActionEvent evt) {
|
||||
this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false);
|
||||
this.resetRequested = false;
|
||||
close();
|
||||
}
|
||||
|
||||
private void reset(ActionEvent evt) {
|
||||
void reset(ActionEvent evt) {
|
||||
this.arguments = new LinkedHashMap<>();
|
||||
this.resetRequested = true;
|
||||
close();
|
||||
@ -343,6 +343,10 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||
return type.cast(memorized.get(new NameTypePair(name, type)));
|
||||
}
|
||||
|
||||
public void forgetMemorizedArguments() {
|
||||
memorized.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
PropertyEditor editor = (PropertyEditor) evt.getSource();
|
||||
|
@ -0,0 +1,159 @@
|
||||
/* ###
|
||||
* 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.progress;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.CancelledListener;
|
||||
|
||||
public class DefaultCloseableTaskMonitor implements CloseableTaskMonitor {
|
||||
private static final Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
static class State implements Runnable {
|
||||
private final DefaultMonitorReceiver receiver;
|
||||
|
||||
State(DefaultMonitorReceiver receiver) {
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
receiver.clean();
|
||||
}
|
||||
}
|
||||
|
||||
private final DefaultMonitorReceiver receiver;
|
||||
private final State state;
|
||||
@SuppressWarnings("unused")
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public DefaultCloseableTaskMonitor(ProgressServicePlugin plugin) {
|
||||
this.receiver = new DefaultMonitorReceiver(plugin);
|
||||
this.state = new State(receiver);
|
||||
this.cleanable = CLEANER.register(this, state);
|
||||
}
|
||||
|
||||
DefaultMonitorReceiver getReceiver() {
|
||||
return receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return receiver.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowProgressValue(boolean showProgressValue) {
|
||||
receiver.setShowProgressValue(showProgressValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
receiver.setMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return receiver.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(long value) {
|
||||
receiver.setProgress(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(long max) {
|
||||
receiver.setProgress(0);
|
||||
receiver.setMaximum(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaximum(long max) {
|
||||
receiver.setMaximum(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximum() {
|
||||
return receiver.getMaximum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndeterminate(boolean indeterminate) {
|
||||
receiver.setIndeterminate(indeterminate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndeterminate() {
|
||||
return receiver.isIndeterminate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCanceled() throws CancelledException {
|
||||
if (receiver.isCancelled()) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementProgress(long incrementAmount) {
|
||||
receiver.incrementProgress(incrementAmount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getProgress() {
|
||||
return receiver.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
receiver.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCancelledListener(CancelledListener listener) {
|
||||
receiver.addCancelledListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCancelledListener(CancelledListener listener) {
|
||||
receiver.removeCancelledListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelEnabled(boolean enable) {
|
||||
receiver.setCancelEnabled(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelEnabled() {
|
||||
return receiver.isCancelEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCanceled() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
receiver.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
/* ###
|
||||
* 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.progress;
|
||||
|
||||
import ghidra.debug.api.progress.MonitorReceiver;
|
||||
import ghidra.debug.api.progress.ProgressListener.Disposal;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.task.CancelledListener;
|
||||
|
||||
public class DefaultMonitorReceiver implements MonitorReceiver {
|
||||
private final ProgressServicePlugin plugin;
|
||||
private final ListenerSet<CancelledListener> listeners =
|
||||
new ListenerSet<>(CancelledListener.class, true);
|
||||
private final Object lock = new Object();
|
||||
|
||||
private boolean cancelled = false;
|
||||
private boolean indeterminate = false;
|
||||
private boolean cancelEnabled = true;
|
||||
private boolean showProgressValue = true;
|
||||
|
||||
private boolean valid = true;
|
||||
|
||||
private String message;
|
||||
private long maximum;
|
||||
private long progress;
|
||||
|
||||
public DefaultMonitorReceiver(ProgressServicePlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
synchronized (lock) {
|
||||
if (this.cancelled == true) {
|
||||
return;
|
||||
}
|
||||
this.cancelled = true;
|
||||
}
|
||||
listeners.invoke().cancelled();
|
||||
plugin.listeners.invoke().attributeUpdated(this);
|
||||
}
|
||||
|
||||
void setShowProgressValue(boolean showProgressValue) {
|
||||
synchronized (lock) {
|
||||
if (this.showProgressValue == showProgressValue) {
|
||||
return;
|
||||
}
|
||||
this.showProgressValue = showProgressValue;
|
||||
}
|
||||
plugin.listeners.invoke().attributeUpdated(this);
|
||||
}
|
||||
|
||||
void setMessage(String message) {
|
||||
synchronized (lock) {
|
||||
this.message = message;
|
||||
}
|
||||
plugin.listeners.invoke().messageUpdated(this, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
synchronized (lock) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
void setProgress(long progress) {
|
||||
synchronized (lock) {
|
||||
this.progress = progress;
|
||||
}
|
||||
plugin.listeners.invoke().progressUpdated(this, progress);
|
||||
}
|
||||
|
||||
void incrementProgress(long amount) {
|
||||
long progress;
|
||||
synchronized (lock) {
|
||||
progress = this.progress + amount;
|
||||
this.progress = progress;
|
||||
}
|
||||
plugin.listeners.invoke().progressUpdated(this, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
void setMaximum(long maximum) {
|
||||
synchronized (lock) {
|
||||
if (this.maximum == maximum) {
|
||||
return;
|
||||
}
|
||||
this.maximum = maximum;
|
||||
}
|
||||
plugin.listeners.invoke().attributeUpdated(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximum() {
|
||||
synchronized (lock) {
|
||||
return maximum;
|
||||
}
|
||||
}
|
||||
|
||||
void setIndeterminate(boolean indeterminate) {
|
||||
synchronized (lock) {
|
||||
if (this.indeterminate == indeterminate) {
|
||||
return;
|
||||
}
|
||||
this.indeterminate = indeterminate;
|
||||
}
|
||||
plugin.listeners.invoke().attributeUpdated(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndeterminate() {
|
||||
return indeterminate;
|
||||
}
|
||||
|
||||
void addCancelledListener(CancelledListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
void removeCancelledListener(CancelledListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
void setCancelEnabled(boolean cancelEnabled) {
|
||||
synchronized (lock) {
|
||||
if (this.cancelEnabled == cancelEnabled) {
|
||||
return;
|
||||
}
|
||||
this.cancelEnabled = cancelEnabled;
|
||||
}
|
||||
plugin.listeners.invoke().attributeUpdated(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelEnabled() {
|
||||
return cancelEnabled;
|
||||
}
|
||||
|
||||
public boolean isShowProgressValue() {
|
||||
return showProgressValue;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
if (!this.valid) {
|
||||
return;
|
||||
}
|
||||
this.valid = false;
|
||||
}
|
||||
plugin.disposeMonitor(this, Disposal.CLOSED);
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
synchronized (lock) {
|
||||
if (!this.valid) {
|
||||
return;
|
||||
}
|
||||
this.valid = false;
|
||||
}
|
||||
plugin.disposeMonitor(this, Disposal.CLEANED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return this.valid;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/* ###
|
||||
* 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.progress;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.services.ProgressService;
|
||||
import ghidra.debug.api.progress.*;
|
||||
import ghidra.debug.api.progress.ProgressListener.Disposal;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@PluginInfo(
|
||||
category = PluginCategoryNames.MISC,
|
||||
shortDescription = "Service for monitoring task progress",
|
||||
description = """
|
||||
Implements a pub-sub model for notifying of tasks and progress. Publishers can create
|
||||
task monitors and update them using the TaskMonitor interface. Subscribers (there ought
|
||||
to only be one) are notified of the tasks and render progress in a component provider.
|
||||
""",
|
||||
servicesProvided = { ProgressService.class },
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.STABLE)
|
||||
public class ProgressServicePlugin extends Plugin implements ProgressService {
|
||||
ListenerSet<ProgressListener> listeners = new ListenerSet<>(ProgressListener.class, true);
|
||||
|
||||
Set<MonitorReceiver> monitors = new HashSet<>();
|
||||
|
||||
public ProgressServicePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableTaskMonitor publishTask() {
|
||||
DefaultCloseableTaskMonitor monitor = new DefaultCloseableTaskMonitor(this);
|
||||
synchronized (monitors) {
|
||||
monitors.add(monitor.getReceiver());
|
||||
}
|
||||
listeners.invoke().monitorCreated(monitor.getReceiver());
|
||||
return monitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MonitorReceiver> getAllMonitors() {
|
||||
synchronized (monitors) {
|
||||
return Set.copyOf(monitors);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProgressListener(ProgressListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProgressListener(ProgressListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
void disposeMonitor(DefaultMonitorReceiver monitor, Disposal disposal) {
|
||||
boolean changed;
|
||||
synchronized (monitors) {
|
||||
changed = monitors.remove(monitor);
|
||||
}
|
||||
if (changed) {
|
||||
listeners.invoke().monitorDisposed(monitor, disposal);
|
||||
}
|
||||
}
|
||||
}
|
@ -102,7 +102,7 @@ public abstract class AbstractTarget implements Target {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
if (context.getContextObject() instanceof MarkerLocation ml) {
|
||||
if (context != null && context.getContextObject() instanceof MarkerLocation ml) {
|
||||
Address address = findAddress(ml);
|
||||
if (address != null) {
|
||||
return address;
|
||||
|
@ -1084,6 +1084,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
return getCurrentFor(trace).trace(trace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerCoordinates resolveTarget(Target target) {
|
||||
Trace trace = target == null ? null : target.getTrace();
|
||||
return getCurrentFor(trace).target(target).snap(target.getSnap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerCoordinates resolvePlatform(TracePlatform platform) {
|
||||
Trace trace = platform == null ? null : platform.getTrace();
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 609 B |
Binary file not shown.
After Width: | Height: | Size: 393 B |
35
Ghidra/Debug/Debugger/src/main/svg/connect-accept.svg
Normal file
35
Ghidra/Debug/Debugger/src/main/svg/connect-accept.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg4819"
|
||||
version="1.1"
|
||||
viewBox="0 0 16 16">
|
||||
<defs
|
||||
id="defs4821" />
|
||||
<metadata
|
||||
id="metadata4824">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
id="path1465"
|
||||
d="M 8,1.5 C 4.4160714,1.5 1.5,4.4160714 1.5,8 c 0.00175,1.1999382 0.3850235,2.351348 1.0097656,3.369141 l -2.21679685,2.216797 2.12109375,2.121093 2.21875,-2.21875 C 5.6498713,14.113396 6.7985466,14.497314 7.9980469,14.5 H 8 c 3.583929,0 6.5,-2.916071 6.5,-6.5 V 7.5 h -3 V 8 C 11.5,9.9389189 9.9389189,11.5 8,11.5 6.0610811,11.5 4.5,9.9389189 4.5,8 4.5,6.0610811 6.0610811,4.5 8,4.5 h 0.5 v -3 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
id="circle842"
|
||||
d="m 8,2.0000001 a 6,6 0 0 0 -6,6 6,6 0 0 0 1.113282,3.4726559 L 1,13.585938 2.414063,15 4.529297,12.884766 A 6,6 0 0 0 8,14 6,6 0 0 0 14,8.0000001 H 12 A 4,4 0 0 1 8,12 4,4 0 0 1 4,8.0000001 a 4,4 0 0 1 4,-4 z"
|
||||
style="opacity:1;fill:#d4aa00;fill-opacity:1;stroke:none;stroke-width:1.99999976;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
35
Ghidra/Debug/Debugger/src/main/svg/connect-outbound.svg
Normal file
35
Ghidra/Debug/Debugger/src/main/svg/connect-outbound.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg4819"
|
||||
height="16"
|
||||
width="16">
|
||||
<defs
|
||||
id="defs4821" />
|
||||
<metadata
|
||||
id="metadata4824">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
id="path878"
|
||||
d="M 13.585938,0.29296875 13.232422,0.64648438 9.1230469,4.7558594 C 8.757941,4.6294826 8.3901339,4.5007931 8.0019531,4.5 H 8 C 6.0729256,4.5 4.4999999,6.0729256 4.5,8 c 1e-7,1.9270743 1.5729257,3.5 3.5,3.5 1.9270743,0 3.5,-1.5729257 3.5,-3.5 V 7.99805 C 11.499207,7.6098662 11.370517,7.2420589 11.244141,6.8769531 l 4.46289,-4.4628906 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
style="opacity:1;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 13.585938,1 9.291016,5.2949219 A 3,3 0 0 0 8,5 3,3 0 0 0 5,8.0000001 3,3 0 0 0 8,11 3,3 0 0 0 11,8.0000001 3,3 0 0 0 10.705078,6.7089844 L 15,2.4140625 Z"
|
||||
id="path838" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.console;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -24,7 +24,13 @@ import docking.DefaultActionContext;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.service.progress.ProgressServicePlugin;
|
||||
import ghidra.app.services.ProgressService;
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
DebuggerConsolePlugin consolePlugin;
|
||||
@ -43,18 +49,18 @@ public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
@Test
|
||||
public void testActions() throws Exception {
|
||||
consolePlugin.addResolutionAction(new ActionBuilder("Add", name.getMethodName())
|
||||
.toolBarIcon(DebuggerResources.ICON_ADD)
|
||||
.description("Add")
|
||||
.withContext(TestConsoleActionContext.class)
|
||||
.onAction(ctx -> Msg.info(this, "Add clicked"))
|
||||
.build());
|
||||
.toolBarIcon(DebuggerResources.ICON_ADD)
|
||||
.description("Add")
|
||||
.withContext(TestConsoleActionContext.class)
|
||||
.onAction(ctx -> Msg.info(this, "Add clicked"))
|
||||
.build());
|
||||
consolePlugin.addResolutionAction(new ActionBuilder("Delete", name.getMethodName())
|
||||
.popupMenuIcon(DebuggerResources.ICON_DELETE)
|
||||
.popupMenuPath("Delete")
|
||||
.description("Delete")
|
||||
.withContext(TestConsoleActionContext.class)
|
||||
.onAction(ctx -> Msg.info(this, "Delete clicked"))
|
||||
.build());
|
||||
.popupMenuIcon(DebuggerResources.ICON_DELETE)
|
||||
.popupMenuPath("Delete")
|
||||
.description("Delete")
|
||||
.withContext(TestConsoleActionContext.class)
|
||||
.onAction(ctx -> Msg.info(this, "Delete clicked"))
|
||||
.build());
|
||||
|
||||
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "<html><b>Test message</b></html>",
|
||||
new TestConsoleActionContext());
|
||||
@ -75,4 +81,40 @@ public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
|
||||
waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProgress() throws Exception {
|
||||
ProgressService progressService = addPlugin(tool, ProgressServicePlugin.class);
|
||||
try (CloseableTaskMonitor monitor1 = progressService.publishTask();
|
||||
CloseableTaskMonitor monitor2 = progressService.publishTask()) {
|
||||
monitor1.initialize(10, "Testing 1");
|
||||
monitor2.initialize(10, "Testing 2");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Thread.sleep(100);
|
||||
monitor1.increment();
|
||||
Thread.sleep(100);
|
||||
monitor2.increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefTaskMonitor() throws Exception {
|
||||
tool.execute(new Task("Test") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
monitor.initialize(10, "Testing");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
monitor.increment();
|
||||
}
|
||||
}
|
||||
});
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
|
@ -1598,7 +1598,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
|
||||
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
||||
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage().contains("recovery"));
|
||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage() instanceof String message &&
|
||||
message.contains("recovery"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1626,7 +1627,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
|
||||
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
||||
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage().contains("version"));
|
||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage() instanceof String message &&
|
||||
message.contains("version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1643,7 +1645,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
consolePlugin.log(DebuggerResources.ICON_MODULES, "Test resolution", ctx);
|
||||
waitForSwing();
|
||||
|
||||
LogRow row = consolePlugin.getLogRow(ctx);
|
||||
LogRow<?> row = consolePlugin.getLogRow(ctx);
|
||||
assertEquals(1, row.getActions().size());
|
||||
BoundAction boundAction = row.getActions().get(0);
|
||||
assertEquals(listingProvider.actionOpenProgram, boundAction.action);
|
||||
|
@ -0,0 +1,48 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class InvocationDialogHelper {
|
||||
|
||||
public static InvocationDialogHelper waitFor() {
|
||||
DebuggerMethodInvocationDialog dialog =
|
||||
AbstractDockingTest.waitForDialogComponent(DebuggerMethodInvocationDialog.class);
|
||||
return new InvocationDialogHelper(dialog);
|
||||
}
|
||||
|
||||
private final DebuggerMethodInvocationDialog dialog;
|
||||
|
||||
public InvocationDialogHelper(DebuggerMethodInvocationDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
public void dismissWithArguments(Map<String, Object> args) {
|
||||
for (Map.Entry<String, Object> a : args.entrySet()) {
|
||||
ParameterDescription<?> p = dialog.parameters.get(a.getKey());
|
||||
assertNotNull(p);
|
||||
dialog.setMemorizedArgument(a.getKey(), p.type.asSubclass(Object.class), a.getValue());
|
||||
}
|
||||
Swing.runNow(() -> dialog.invoke(null));
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@ import org.junit.experimental.categories.Category;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import generic.test.rule.Repeated;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.services.DebuggerControlService;
|
||||
|
@ -150,11 +150,19 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||
return names.computeIfAbsent(name, SchemaName::new);
|
||||
}
|
||||
|
||||
private String requireAttributeValue(Element elem, String name) {
|
||||
String value = elem.getAttributeValue(name);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Missing attribute " + name + " in " + elem);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public TargetObjectSchema schemaFromXml(Element schemaElem) {
|
||||
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue("name", "")));
|
||||
|
||||
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, "interface")) {
|
||||
String ifaceName = ifaceElem.getAttributeValue("name");
|
||||
String ifaceName = requireAttributeValue(ifaceElem, "name");
|
||||
Class<? extends TargetObject> iface = TargetObject.INTERFACES_BY_NAME.get(ifaceName);
|
||||
if (iface == null) {
|
||||
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
|
||||
@ -166,18 +174,18 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||
|
||||
builder.setCanonicalContainer(parseBoolean(schemaElem, "canonical"));
|
||||
builder.setElementResyncMode(
|
||||
ResyncMode.valueOf(schemaElem.getAttributeValue("elementResync")));
|
||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, "elementResync")));
|
||||
builder.setAttributeResyncMode(
|
||||
ResyncMode.valueOf(schemaElem.getAttributeValue("attributeResync")));
|
||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, "attributeResync")));
|
||||
|
||||
for (Element elemElem : XmlUtilities.getChildren(schemaElem, "element")) {
|
||||
SchemaName schema = name(elemElem.getAttributeValue("schema"));
|
||||
SchemaName schema = name(requireAttributeValue(elemElem, "schema"));
|
||||
String index = elemElem.getAttributeValue("index", "");
|
||||
builder.addElementSchema(index, schema, elemElem);
|
||||
}
|
||||
|
||||
for (Element attrElem : XmlUtilities.getChildren(schemaElem, "attribute")) {
|
||||
SchemaName schema = name(attrElem.getAttributeValue("schema"));
|
||||
SchemaName schema = name(requireAttributeValue(attrElem, "schema"));
|
||||
boolean required = parseBoolean(attrElem, "required");
|
||||
boolean fixed = parseBoolean(attrElem, "fixed");
|
||||
boolean hidden = parseBoolean(attrElem, "hidden");
|
||||
|
@ -63,6 +63,7 @@ icon.run = play.png
|
||||
icon.spreadsheet = application-vnd.oasis.opendocument.spreadsheet-template.png
|
||||
icon.pulldown = menu16.gif
|
||||
icon.window = application_xp.png
|
||||
icon.pending = hourglass.png
|
||||
icon.zoom.in = zoom_in.png
|
||||
icon.zoom.out = zoom_out.png
|
||||
|
||||
@ -102,7 +103,7 @@ icon.widget.pathmanager.reset = trash-empty.png
|
||||
|
||||
icon.widget.table.header.help = info_small.png
|
||||
icon.widget.table.header.help.hovered = info_small_hover.png
|
||||
icon.widget.table.header.pending = hourglass.png
|
||||
icon.widget.table.header.pending = icon.pending
|
||||
|
||||
icon.dialog.error.expandable.report = icon.spreadsheet
|
||||
icon.dialog.error.expandable.exception = program_obj.png
|
||||
|
@ -29,7 +29,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
|
@ -33,7 +33,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
|
@ -32,7 +32,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
|
@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.gui;
|
||||
import java.util.Set;
|
||||
|
||||
import db.Transaction;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TestTraceRmiConnection;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TestTraceRmiConnection.*;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.*;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
|
@ -24,7 +24,7 @@ import org.junit.experimental.categories.Category;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiTarget;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
|
||||
import ghidra.trace.database.target.DBTraceObjectManager;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
@ -22,7 +22,7 @@ import java.util.*;
|
||||
import org.junit.Before;
|
||||
|
||||
import db.Transaction;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiTarget;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
@ -46,7 +46,7 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TestTraceRmiConnection.TestRemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
|
||||
import ghidra.app.services.DebuggerControlService;
|
||||
import ghidra.app.services.DebuggerEmulationService;
|
||||
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
|
||||
|
@ -23,7 +23,7 @@ import org.junit.Before;
|
||||
|
||||
import db.Transaction;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiTarget;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
Loading…
Reference in New Issue
Block a user