mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-3836: Add Trace RMI 'Connections' pane.
This commit is contained in:
parent
5fd01c739d
commit
bf8f7c8f78
82 changed files with 3836 additions and 270 deletions
|
@ -62,12 +62,15 @@ SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
|
||||||
|
|
||||||
# TODO: Symbols
|
# TODO: Symbols
|
||||||
|
|
||||||
|
|
||||||
class ErrorWithCode(Exception):
|
class ErrorWithCode(Exception):
|
||||||
def __init__(self,code):
|
def __init__(self, code):
|
||||||
self.code = code
|
self.code = code
|
||||||
|
|
||||||
def __str__(self)->str:
|
def __str__(self)->str:
|
||||||
return repr(self.code)
|
return repr(self.code)
|
||||||
|
|
||||||
|
|
||||||
class State(object):
|
class State(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -114,7 +117,8 @@ class State(object):
|
||||||
|
|
||||||
|
|
||||||
STATE = State()
|
STATE = State()
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_connect(address=None):
|
def ghidra_trace_connect(address=None):
|
||||||
"""
|
"""
|
||||||
Connect Python to Ghidra for tracing
|
Connect Python to Ghidra for tracing
|
||||||
|
@ -124,8 +128,9 @@ def ghidra_trace_connect(address=None):
|
||||||
|
|
||||||
STATE.require_no_client()
|
STATE.require_no_client()
|
||||||
if address is None:
|
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(':')
|
parts = address.split(':')
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
raise RuntimeError("address must be in the form 'host:port'")
|
raise RuntimeError("address must be in the form 'host:port'")
|
||||||
|
@ -133,11 +138,13 @@ def ghidra_trace_connect(address=None):
|
||||||
try:
|
try:
|
||||||
c = socket.socket()
|
c = socket.socket()
|
||||||
c.connect((host, int(port)))
|
c.connect((host, int(port)))
|
||||||
STATE.client = Client(c, methods.REGISTRY)
|
# TODO: Can we get version info from the DLL?
|
||||||
except ValueError:
|
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")
|
raise RuntimeError("port must be numeric")
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_listen(address='0.0.0.0:0'):
|
def ghidra_trace_listen(address='0.0.0.0:0'):
|
||||||
"""
|
"""
|
||||||
Listen for Ghidra to connect for tracing
|
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:
|
if timeout != None:
|
||||||
util.base._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
util.base._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
||||||
if initial_break:
|
if initial_break:
|
||||||
util.base._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK)
|
util.base._control.AddEngineOptions(
|
||||||
|
DbgEng.DEBUG_ENGINITIAL_BREAK)
|
||||||
util.base.wait(timeout)
|
util.base.wait(timeout)
|
||||||
else:
|
else:
|
||||||
util.base.create(command, initial_break)
|
util.base.create(command, initial_break)
|
||||||
|
@ -267,7 +275,7 @@ def ghidra_trace_info():
|
||||||
print("Not connected to Ghidra\n")
|
print("Not connected to Ghidra\n")
|
||||||
return
|
return
|
||||||
host, port = STATE.client.s.getpeername()
|
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:
|
if STATE.trace is None:
|
||||||
print("No trace\n")
|
print("No trace\n")
|
||||||
return
|
return
|
||||||
|
@ -363,7 +371,7 @@ def put_bytes(start, end, pages, display_result):
|
||||||
if end - start <= 0:
|
if end - start <= 0:
|
||||||
return {'count': 0}
|
return {'count': 0}
|
||||||
buf = dbg().read(start, end - start)
|
buf = dbg().read(start, end - start)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
if buf != None:
|
if buf != None:
|
||||||
base, addr = trace.memory_mapper.map(nproc, start)
|
base, addr = trace.memory_mapper.map(nproc, start)
|
||||||
|
@ -405,7 +413,7 @@ def ghidra_trace_putmem(items):
|
||||||
address = items[0]
|
address = items[0]
|
||||||
length = items[1]
|
length = items[1]
|
||||||
pages = items[2] if len(items) > 2 else True
|
pages = items[2] if len(items) > 2 else True
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
return putmem(address, length, pages, True)
|
return putmem(address, length, pages, True)
|
||||||
|
|
||||||
|
@ -418,7 +426,7 @@ def ghidra_trace_putval(items):
|
||||||
items = items.split(" ")
|
items = items.split(" ")
|
||||||
value = items[0]
|
value = items[0]
|
||||||
pages = items[1] if len(items) > 1 else True
|
pages = items[1] if len(items) > 1 else True
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
try:
|
try:
|
||||||
start = util.parse_and_eval(value)
|
start = util.parse_and_eval(value)
|
||||||
|
@ -565,28 +573,29 @@ def ghidra_trace_remove_obj(path):
|
||||||
|
|
||||||
|
|
||||||
def to_bytes(value):
|
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):
|
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)
|
return str(b, encoding)
|
||||||
|
|
||||||
|
|
||||||
def to_bool_list(value):
|
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):
|
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):
|
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):
|
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):
|
def eval_value(value, schema=None):
|
||||||
|
@ -614,9 +623,9 @@ def eval_value(value, schema=None):
|
||||||
return to_string_list(value, 'utf-8'), schema
|
return to_string_list(value, 'utf-8'), schema
|
||||||
if schema == sch.CHAR_ARR:
|
if schema == sch.CHAR_ARR:
|
||||||
return to_string(value, 'utf-8'), 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 to_string(value, 'utf-8'), sch.STRING
|
||||||
|
|
||||||
return value, schema
|
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
|
This has no effect if the current trace is not current in Ghidra. If path is
|
||||||
omitted, this will activate the current frame.
|
omitted, this will activate the current frame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
activate(path)
|
activate(path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -822,7 +831,7 @@ def ghidra_trace_disassemble(address):
|
||||||
|
|
||||||
def compute_proc_state(nproc=None):
|
def compute_proc_state(nproc=None):
|
||||||
status = dbg()._control.GetExecutionStatus()
|
status = dbg()._control.GetExecutionStatus()
|
||||||
if status == DbgEng.DEBUG_STATUS_BREAK:
|
if status == DbgEng.DEBUG_STATUS_BREAK:
|
||||||
return 'STOPPED'
|
return 'STOPPED'
|
||||||
return 'RUNNING'
|
return 'RUNNING'
|
||||||
|
|
||||||
|
@ -841,13 +850,15 @@ def put_processes(running=False):
|
||||||
procobj.set_value('_state', istate)
|
procobj.set_value('_state', istate)
|
||||||
if running == False:
|
if running == False:
|
||||||
procobj.set_value('_pid', p[0])
|
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('_display', pidstr)
|
||||||
procobj.set_value('Name', str(p[1]))
|
procobj.set_value('Name', str(p[1]))
|
||||||
procobj.set_value('PEB', hex(p[2]))
|
procobj.set_value('PEB', hex(p[2]))
|
||||||
procobj.insert()
|
procobj.insert()
|
||||||
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
def put_state(event_process):
|
def put_state(event_process):
|
||||||
STATE.require_no_tx()
|
STATE.require_no_tx()
|
||||||
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
||||||
|
@ -873,10 +884,10 @@ def ghidra_trace_put_processes():
|
||||||
def put_available():
|
def put_available():
|
||||||
radix = util.get_convenience_variable('output-radix')
|
radix = util.get_convenience_variable('output-radix')
|
||||||
keys = []
|
keys = []
|
||||||
result = dbg().cmd(".tlist")
|
result = dbg().cmd(".tlist")
|
||||||
lines = result.split("\n")
|
lines = result.split("\n")
|
||||||
for i in lines:
|
for i in lines:
|
||||||
i = i.strip();
|
i = i.strip()
|
||||||
if i == "":
|
if i == "":
|
||||||
continue
|
continue
|
||||||
if i.startswith("0n") is False:
|
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:
|
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
|
||||||
width, prot = bp.GetDataParameters()
|
width, prot = bp.GetDataParameters()
|
||||||
width = str(width)
|
width = str(width)
|
||||||
prot = {4: 'HW_EXECUTE', 2: 'READ', 1: 'WRITE'}[prot]
|
prot = {4: 'HW_EXECUTE', 2: 'READ', 1: 'WRITE'}[prot]
|
||||||
else:
|
else:
|
||||||
width = ' '
|
width = ' '
|
||||||
prot = 'SW_EXECUTE'
|
prot = 'SW_EXECUTE'
|
||||||
|
|
||||||
if address is not None: # Implies execution break
|
if address is not None: # Implies execution break
|
||||||
base, addr = mapper.map(nproc, address)
|
base, addr = mapper.map(nproc, address)
|
||||||
|
@ -968,7 +979,6 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||||
ikeys.append(k)
|
ikeys.append(k)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def put_breakpoints():
|
def put_breakpoints():
|
||||||
target = util.get_target()
|
target = util.get_target()
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
|
@ -998,7 +1008,7 @@ def ghidra_trace_put_breakpoints():
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
with STATE.client.batch() as b:
|
with STATE.client.batch() as b:
|
||||||
put_breakpoints()
|
put_breakpoints()
|
||||||
|
|
||||||
|
|
||||||
def put_environment():
|
def put_environment():
|
||||||
epath = ENV_PATTERN.format(procnum=util.selected_process())
|
epath = ENV_PATTERN.format(procnum=util.selected_process())
|
||||||
|
@ -1039,9 +1049,12 @@ def put_regions():
|
||||||
if start_base != start_addr.space:
|
if start_base != start_addr.space:
|
||||||
STATE.trace.create_overlay_space(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('_range', start_addr.extend(r.RegionSize))
|
||||||
regobj.set_value('_readable', r.Protect == None or r.Protect&0x66 != 0)
|
regobj.set_value('_readable', r.Protect ==
|
||||||
regobj.set_value('_writable', r.Protect == None or r.Protect&0xCC != 0)
|
None or r.Protect & 0x66 != 0)
|
||||||
regobj.set_value('_executable', r.Protect == None or r.Protect&0xF0 != 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('_offset', hex(r.BaseAddress))
|
||||||
regobj.set_value('Base', hex(r.BaseAddress))
|
regobj.set_value('Base', hex(r.BaseAddress))
|
||||||
regobj.set_value('Size', hex(r.RegionSize))
|
regobj.set_value('Size', hex(r.RegionSize))
|
||||||
|
@ -1089,13 +1102,13 @@ def put_modules():
|
||||||
modobj.set_value('Size', hex(size))
|
modobj.set_value('Size', hex(size))
|
||||||
modobj.set_value('Flags', hex(size))
|
modobj.set_value('Flags', hex(size))
|
||||||
modobj.insert()
|
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
|
# it as far as I am aware
|
||||||
# sec_keys = []
|
# sec_keys = []
|
||||||
# STATE.trace.proxy_object_path(
|
# STATE.trace.proxy_object_path(
|
||||||
# mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
# mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
||||||
|
|
||||||
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
||||||
procnum=nproc)).retain_values(mod_keys)
|
procnum=nproc)).retain_values(mod_keys)
|
||||||
|
|
||||||
|
@ -1146,7 +1159,7 @@ def put_threads(running=False):
|
||||||
tid = t[0]
|
tid = t[0]
|
||||||
tobj.set_value('_tid', tid)
|
tobj.set_value('_tid', tid)
|
||||||
tidstr = ('0x{:x}' if radix ==
|
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(
|
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
||||||
nproc, i, tidstr))
|
nproc, i, tidstr))
|
||||||
tobj.set_value('_display', compute_thread_display(tidstr, t))
|
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('StackOffset', hex(f.StackOffset))
|
||||||
fobj.set_value('ReturnOffset', hex(f.ReturnOffset))
|
fobj.set_value('ReturnOffset', hex(f.ReturnOffset))
|
||||||
fobj.set_value('FrameOffset', hex(f.FrameOffset))
|
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()
|
fobj.insert()
|
||||||
STATE.trace.proxy_object_path(STACK_PATTERN.format(
|
STATE.trace.proxy_object_path(STACK_PATTERN.format(
|
||||||
procnum=nproc, tnum=nthrd)).retain_values(keys)
|
procnum=nproc, tnum=nthrd)).retain_values(keys)
|
||||||
|
@ -1326,7 +1340,7 @@ def repl():
|
||||||
dbg().wait()
|
dbg().wait()
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
#dbg().dispatch_events()
|
# dbg().dispatch_events()
|
||||||
except KeyboardInterrupt as e:
|
except KeyboardInterrupt as e:
|
||||||
print("")
|
print("")
|
||||||
print("You have left the dbgeng REPL and are now at the Python3 interpreter.")
|
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:
|
try:
|
||||||
c = socket.socket()
|
c = socket.socket()
|
||||||
c.connect((host, int(port)))
|
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:
|
except ValueError:
|
||||||
raise gdb.GdbError("port must be numeric")
|
raise gdb.GdbError("port must be numeric")
|
||||||
|
|
||||||
|
@ -320,9 +322,11 @@ def ghidra_trace_info(*, is_mi, **kwargs):
|
||||||
return
|
return
|
||||||
host, port = STATE.client.s.getpeername()
|
host, port = STATE.client.s.getpeername()
|
||||||
if is_mi:
|
if is_mi:
|
||||||
result['connection'] = "{}:{}".format(host, port)
|
result['description'] = STATE.client.description
|
||||||
|
result['address'] = f"{host}:{port}"
|
||||||
else:
|
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 STATE.trace is None:
|
||||||
if is_mi:
|
if is_mi:
|
||||||
result['tracing'] = False
|
result['tracing'] = False
|
||||||
|
|
|
@ -21,12 +21,11 @@ import javax.swing.Icon;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import ghidra.dbg.DebuggerConsoleLogger;
|
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
|
|
||||||
@ServiceInfo(defaultProviderName = "ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin")
|
@ServiceInfo(defaultProviderName = "ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin")
|
||||||
public interface DebuggerConsoleService extends DebuggerConsoleLogger {
|
public interface DebuggerConsoleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a message to the console
|
* Log a message to the console
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Collection;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.async.AsyncReference;
|
import ghidra.async.AsyncReference;
|
||||||
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
@ -330,6 +331,24 @@ public interface DebuggerTraceManagerService {
|
||||||
activate(resolveTrace(trace));
|
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"
|
* 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.net.SocketAddress;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
import ghidra.debug.api.tracermi.*;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,4 +104,26 @@ public interface TraceRmiService {
|
||||||
* @return the connections
|
* @return the connections
|
||||||
*/
|
*/
|
||||||
Collection<TraceRmiConnection> getAllConnections();
|
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.SocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An acceptor to receive a single Trace RMI connection from a back-end
|
* An acceptor to receive a single Trace RMI connection from a back-end
|
||||||
*/
|
*/
|
||||||
|
@ -27,12 +29,13 @@ public interface TraceRmiAcceptor {
|
||||||
* Accept a single connection
|
* Accept a single connection
|
||||||
*
|
*
|
||||||
* <p>
|
* <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
|
* @return the connection, if successful
|
||||||
* @throws IOException if there was an error
|
* @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
|
* 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
|
* @throws SocketException if there's a protocol error
|
||||||
*/
|
*/
|
||||||
void setTimeout(int millis) throws SocketException;
|
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.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.trace.model.Trace;
|
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.
|
* to both parent and child, then it should create and publish a second target.
|
||||||
*/
|
*/
|
||||||
public interface TraceRmiConnection extends AutoCloseable {
|
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
|
* 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.
|
* @return true if the trace is a target, false otherwise.
|
||||||
*/
|
*/
|
||||||
boolean isTarget(Trace trace);
|
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.net.InetSocketAddress;
|
||||||
import java.util.Objects;
|
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.script.GhidraScript;
|
||||||
import ghidra.app.services.TraceRmiService;
|
import ghidra.app.services.TraceRmiService;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
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.script.GhidraScript;
|
||||||
import ghidra.app.services.TraceRmiService;
|
import ghidra.app.services.TraceRmiService;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
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.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.app.services.TraceRmiService;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
@ -29,14 +31,30 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
""",
|
""",
|
||||||
category = PluginCategoryNames.DEBUGGER,
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME,
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED,
|
status = PluginStatus.STABLE,
|
||||||
|
eventsConsumed = {
|
||||||
|
TraceActivatedPluginEvent.class,
|
||||||
|
TraceInactiveCoordinatesPluginEvent.class,
|
||||||
|
},
|
||||||
servicesRequired = {
|
servicesRequired = {
|
||||||
TraceRmiService.class,
|
TraceRmiService.class,
|
||||||
})
|
})
|
||||||
public class TraceRmiConnectionManagerPlugin extends Plugin {
|
public class TraceRmiConnectionManagerPlugin extends Plugin {
|
||||||
|
private final TraceRmiConnectionManagerProvider provider;
|
||||||
|
|
||||||
public TraceRmiConnectionManagerPlugin(PluginTool tool) {
|
public TraceRmiConnectionManagerPlugin(PluginTool tool) {
|
||||||
super(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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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 docking.widgets.OptionDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
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.objects.components.DebuggerMethodInvocationDialog;
|
||||||
import ghidra.app.plugin.core.debug.service.rmi.trace.DefaultTraceRmiAcceptor;
|
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||||
import ghidra.app.plugin.core.terminal.TerminalListener;
|
import ghidra.app.plugin.core.terminal.TerminalListener;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.io.IOException;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
|
||||||
|
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||||
|
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
||||||
public class TraceRmiServer {
|
public abstract class AbstractTraceRmiListener {
|
||||||
protected final TraceRmiPlugin plugin;
|
protected final TraceRmiPlugin plugin;
|
||||||
protected final SocketAddress address;
|
protected final SocketAddress address;
|
||||||
|
|
||||||
protected ServerSocket socket;
|
protected ServerSocket socket;
|
||||||
|
|
||||||
public TraceRmiServer(TraceRmiPlugin plugin, SocketAddress address) {
|
public AbstractTraceRmiListener(TraceRmiPlugin plugin, SocketAddress address) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void bind() throws IOException {
|
protected abstract void bind() throws IOException;
|
||||||
socket.bind(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
socket = new ServerSocket();
|
socket = new ServerSocket();
|
||||||
bind();
|
bind();
|
||||||
new Thread(this::serviceLoop, "trace-rmi server " + socket.getLocalSocketAddress()).start();
|
startServiceLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract void startServiceLoop();
|
||||||
|
|
||||||
public void setTimeout(int millis) throws SocketException {
|
public void setTimeout(int millis) throws SocketException {
|
||||||
socket.setSoTimeout(millis);
|
socket.setSoTimeout(millis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract ConnectMode getConnectMode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accept a connection and handle its requests.
|
* Accept a connection and handle its requests.
|
||||||
*
|
*
|
||||||
|
@ -54,36 +59,17 @@ public class TraceRmiServer {
|
||||||
*
|
*
|
||||||
* @return the handler
|
* @return the handler
|
||||||
* @throws IOException on error
|
* @throws IOException on error
|
||||||
|
* @throws CancelledException if the accept is cancelled
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
protected TraceRmiHandler accept() throws IOException {
|
protected TraceRmiHandler doAccept(TraceRmiAcceptor acceptor) throws IOException {
|
||||||
Socket client = socket.accept();
|
Socket client = socket.accept();
|
||||||
TraceRmiHandler handler = new TraceRmiHandler(plugin, client);
|
TraceRmiHandler handler = new TraceRmiHandler(plugin, client);
|
||||||
handler.start();
|
handler.start();
|
||||||
|
plugin.listeners.invoke().connected(handler, getConnectMode(), acceptor);
|
||||||
return handler;
|
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() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
socket.close();
|
socket.close();
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.*;
|
import java.util.concurrent.*;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||||
|
|
||||||
import java.util.*;
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.debug.api.tracermi.TraceRmiError;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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;
|
import java.util.Map;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.debug.api.tracermi.RemoteParameter;
|
import ghidra.debug.api.tracermi.RemoteParameter;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.io.*;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -41,9 +41,12 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||||
import ghidra.dbg.util.PathPattern;
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||||
import ghidra.debug.api.target.ActionName;
|
import ghidra.debug.api.target.ActionName;
|
||||||
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
import ghidra.debug.api.tracermi.*;
|
import ghidra.debug.api.tracermi.*;
|
||||||
|
import ghidra.framework.Application;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||||
|
@ -65,7 +68,6 @@ import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.DuplicateFileException;
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
public class TraceRmiHandler implements TraceRmiConnection {
|
public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
public static final String VERSION = "10.4";
|
public static final String VERSION = "10.4";
|
||||||
|
@ -180,7 +182,14 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
byTrace.put(openTrace.trace, openTrace);
|
byTrace.put(openTrace.trace, openTrace);
|
||||||
first.complete(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() {
|
public CompletableFuture<OpenTrace> getFirstAsync() {
|
||||||
|
@ -192,7 +201,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
private final InputStream in;
|
private final InputStream in;
|
||||||
private final OutputStream out;
|
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 CompletableFuture<Void> closed = new CompletableFuture<>();
|
||||||
private final Set<TerminalSession> terminals = new LinkedHashSet<>();
|
private final Set<TerminalSession> terminals = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
@ -276,8 +285,8 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
DoId nextKey = openTraces.idSet().iterator().next();
|
DoId nextKey = openTraces.idSet().iterator().next();
|
||||||
OpenTrace open = openTraces.removeById(nextKey);
|
OpenTrace open = openTraces.removeById(nextKey);
|
||||||
if (traceManager == null || traceManager.isSaveTracesByDefault()) {
|
if (traceManager == null || traceManager.isSaveTracesByDefault()) {
|
||||||
try {
|
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||||
open.trace.save("Save on Disconnect", plugin.getTaskMonitor());
|
open.trace.save("Save on Disconnect", monitor);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.error(this, "Could not save " + open.trace);
|
Msg.error(this, "Could not save " + open.trace);
|
||||||
|
@ -289,6 +298,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
open.trace.release(this);
|
open.trace.release(this);
|
||||||
}
|
}
|
||||||
closed.complete(null);
|
closed.complete(null);
|
||||||
|
plugin.listeners.invoke().disconnected(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -344,18 +354,19 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
protected DomainFile createDeconflictedFile(DomainFolder parent, DomainObject object)
|
protected DomainFile createDeconflictedFile(DomainFolder parent, DomainObject object)
|
||||||
throws InvalidNameException, CancelledException, IOException {
|
throws InvalidNameException, CancelledException, IOException {
|
||||||
String name = object.getName();
|
String name = object.getName();
|
||||||
TaskMonitor monitor = plugin.getTaskMonitor();
|
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||||
for (int nextId = 1; nextId < 100; nextId++) {
|
for (int nextId = 1; nextId < 100; nextId++) {
|
||||||
try {
|
try {
|
||||||
return parent.createFile(name, object, monitor);
|
return parent.createFile(name, object, monitor);
|
||||||
}
|
}
|
||||||
catch (DuplicateFileException e) {
|
catch (DuplicateFileException e) {
|
||||||
name = object.getName() + "." + nextId;
|
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() {
|
public void start() {
|
||||||
|
@ -911,16 +922,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
OpenTrace open = requireOpenTrace(req.getOid());
|
OpenTrace open = requireOpenTrace(req.getOid());
|
||||||
long snap = req.getSnap().getSnap();
|
long snap = req.getSnap().getSnap();
|
||||||
|
|
||||||
/**
|
// Want addresses satisfying {@code known | (readOnly & everKnown)}
|
||||||
* 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)}
|
|
||||||
*/
|
|
||||||
TraceMemoryManager memoryManager = open.trace.getMemoryManager();
|
TraceMemoryManager memoryManager = open.trace.getMemoryManager();
|
||||||
AddressSetView readOnly =
|
AddressSetView readOnly =
|
||||||
memoryManager.getRegionsAddressSetWith(snap, r -> !r.isWrite());
|
memoryManager.getRegionsAddressSetWith(snap, r -> !r.isWrite());
|
||||||
|
@ -939,8 +941,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
dis.setInitialContext(DebuggerDisassemblerPlugin.deriveAlternativeDefaultContext(
|
dis.setInitialContext(DebuggerDisassemblerPlugin.deriveAlternativeDefaultContext(
|
||||||
host.getLanguage(), host.getLanguage().getLanguageID(), start));
|
host.getLanguage(), host.getLanguage().getLanguageID(), start));
|
||||||
|
|
||||||
TaskMonitor monitor = plugin.getTaskMonitor();
|
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||||
dis.applyToTyped(open.trace.getFixedProgramView(snap), monitor);
|
dis.applyToTyped(open.trace.getFixedProgramView(snap), monitor);
|
||||||
|
}
|
||||||
|
|
||||||
return ReplyDisassemble.newBuilder()
|
return ReplyDisassemble.newBuilder()
|
||||||
.setLength(dis.getDisassembledAddressSet().getNumAddresses())
|
.setLength(dis.getDisassembledAddressSet().getNumAddresses())
|
||||||
|
@ -1027,8 +1030,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
new SchemaName(m.getReturnType().getName()));
|
new SchemaName(m.getReturnType().getName()));
|
||||||
methodRegistry.add(rm);
|
methodRegistry.add(rm);
|
||||||
}
|
}
|
||||||
negotiate.complete(null);
|
negotiate.complete(req.getDescription());
|
||||||
return ReplyNegotiate.getDefaultInstance();
|
return ReplyNegotiate.newBuilder()
|
||||||
|
.setDescription(Application.getName() + " " + Application.getApplicationVersion())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ReplyPutBytes handlePutBytes(RequestPutBytes req) {
|
protected ReplyPutBytes handlePutBytes(RequestPutBytes req) {
|
||||||
|
@ -1110,7 +1115,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
protected ReplySaveTrace handleSaveTrace(RequestSaveTrace req)
|
protected ReplySaveTrace handleSaveTrace(RequestSaveTrace req)
|
||||||
throws CancelledException, IOException {
|
throws CancelledException, IOException {
|
||||||
OpenTrace open = requireOpenTrace(req.getOid());
|
OpenTrace open = requireOpenTrace(req.getOid());
|
||||||
open.trace.save("TraceRMI", plugin.getTaskMonitor());
|
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||||
|
open.trace.save("TraceRMI", monitor);
|
||||||
|
}
|
||||||
return ReplySaveTrace.getDefaultInstance();
|
return ReplySaveTrace.getDefaultInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1271,9 +1278,25 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
return openTraces.getByTrace(trace) != null;
|
return openTraces.getByTrace(trace) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Target> getTargets() {
|
||||||
|
return openTraces.getTargets();
|
||||||
|
}
|
||||||
|
|
||||||
public void registerTerminals(Collection<TerminalSession> terminals) {
|
public void registerTerminals(Collection<TerminalSession> terminals) {
|
||||||
synchronized (this.terminals) {
|
synchronized (this.terminals) {
|
||||||
this.terminals.addAll(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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.io.IOException;
|
||||||
import java.net.*;
|
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.TraceActivatedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.debug.api.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.*;
|
||||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.task.ConsoleTaskMonitor;
|
import ghidra.util.task.ConsoleTaskMonitor;
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Connect to back-end debuggers via Trace RMI",
|
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 {
|
public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||||
private static final int DEFAULT_PORT = 15432;
|
private static final int DEFAULT_PORT = 15432;
|
||||||
|
|
||||||
|
static class FallbackTaskMonitor extends ConsoleTaskMonitor implements CloseableTaskMonitor {
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerTargetService targetService;
|
private DebuggerTargetService targetService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private ProgressService progressService;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Wiring autoServiceWiring;
|
private final Wiring autoServiceWiring;
|
||||||
|
|
||||||
private final TaskMonitor monitor = new ConsoleTaskMonitor();
|
|
||||||
|
|
||||||
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
|
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
|
||||||
private TraceRmiServer server;
|
private TraceRmiServer server;
|
||||||
|
|
||||||
private final Set<TraceRmiHandler> handlers = new LinkedHashSet<>();
|
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) {
|
public TraceRmiPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaskMonitor getTaskMonitor() {
|
protected CloseableTaskMonitor createMonitor() {
|
||||||
// TODO: Create one in the Debug Console?
|
if (progressService == null) {
|
||||||
return monitor;
|
return fallbackMonitor;
|
||||||
|
}
|
||||||
|
return progressService.publishTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -102,14 +119,16 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||||
}
|
}
|
||||||
server = new TraceRmiServer(this, serverAddress);
|
server = new TraceRmiServer(this, serverAddress);
|
||||||
server.start();
|
server.start();
|
||||||
|
listeners.invoke().serverStarted(server.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopServer() {
|
public void stopServer() {
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.close();
|
server.close();
|
||||||
|
server = null;
|
||||||
|
listeners.invoke().serverStopped();
|
||||||
}
|
}
|
||||||
server = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -124,6 +143,7 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||||
socket.connect(address);
|
socket.connect(address);
|
||||||
TraceRmiHandler handler = new TraceRmiHandler(this, socket);
|
TraceRmiHandler handler = new TraceRmiHandler(this, socket);
|
||||||
handler.start();
|
handler.start();
|
||||||
|
listeners.invoke().connected(handler, ConnectMode.CONNECT, null);
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,25 +151,52 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||||
public DefaultTraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
|
public DefaultTraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
|
||||||
DefaultTraceRmiAcceptor acceptor = new DefaultTraceRmiAcceptor(this, address);
|
DefaultTraceRmiAcceptor acceptor = new DefaultTraceRmiAcceptor(this, address);
|
||||||
acceptor.start();
|
acceptor.start();
|
||||||
|
listeners.invoke().waitingAccept(acceptor);
|
||||||
return acceptor;
|
return acceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addHandler(TraceRmiHandler handler) {
|
void addHandler(TraceRmiHandler handler) {
|
||||||
handlers.add(handler);
|
synchronized (handlers) {
|
||||||
|
handlers.add(handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeHandler(TraceRmiHandler handler) {
|
void removeHandler(TraceRmiHandler handler) {
|
||||||
handlers.remove(handler);
|
synchronized (handlers) {
|
||||||
|
handlers.remove(handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<TraceRmiConnection> getAllConnections() {
|
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(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
targetService.publishTarget(target);
|
targetService.publishTarget(target);
|
||||||
|
listeners.invoke().targetPublished(handler, target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,4 +205,14 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||||
targetService.withdrawTarget(target);
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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;
|
import ghidra.program.model.address.AddressOverflowException;
|
||||||
|
|
|
@ -18,8 +18,8 @@ package ghidra.app.services;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.service.rmi.trace.DefaultTraceRmiAcceptor;
|
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,7 +18,7 @@ package ghidra.debug.spi.tracermi;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin;
|
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.app.services.InternalTraceRmiService;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
|
|
|
@ -427,9 +427,11 @@ message Method {
|
||||||
message RequestNegotiate {
|
message RequestNegotiate {
|
||||||
string version = 1;
|
string version = 1;
|
||||||
repeated Method methods = 2;
|
repeated Method methods = 2;
|
||||||
|
string description = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReplyNegotiate {
|
message ReplyNegotiate {
|
||||||
|
string description = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message XRequestInvokeMethod {
|
message XRequestInvokeMethod {
|
||||||
|
|
|
@ -720,7 +720,7 @@ class Client(object):
|
||||||
return Client._read_obj_desc(msg.child_desc), sch.OBJECT
|
return Client._read_obj_desc(msg.child_desc), sch.OBJECT
|
||||||
raise ValueError("Could not read value: {}".format(msg))
|
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._traces = {}
|
||||||
self._next_trace_id = 1
|
self._next_trace_id = 1
|
||||||
self.tlock = Lock()
|
self.tlock = Lock()
|
||||||
|
@ -732,7 +732,7 @@ class Client(object):
|
||||||
self.slock = Lock()
|
self.slock = Lock()
|
||||||
self.receiver.start()
|
self.receiver.start()
|
||||||
self._method_registry = method_registry
|
self._method_registry = method_registry
|
||||||
self._negotiate()
|
self.description = self._negotiate(description)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.s.close()
|
self.s.close()
|
||||||
|
@ -1083,15 +1083,16 @@ class Client(object):
|
||||||
return reply.length
|
return reply.length
|
||||||
return self._batch_or_now(root, 'reply_disassemble', _handle)
|
return self._batch_or_now(root, 'reply_disassemble', _handle)
|
||||||
|
|
||||||
def _negotiate(self):
|
def _negotiate(self, description: str):
|
||||||
root = bufs.RootMessage()
|
root = bufs.RootMessage()
|
||||||
root.request_negotiate.version = VERSION
|
root.request_negotiate.version = VERSION
|
||||||
|
root.request_negotiate.description = description
|
||||||
self._write_methods(root.request_negotiate.methods,
|
self._write_methods(root.request_negotiate.methods,
|
||||||
self._method_registry._methods.values())
|
self._method_registry._methods.values())
|
||||||
|
|
||||||
def _handle(reply):
|
def _handle(reply):
|
||||||
pass
|
return reply.description
|
||||||
self._now(root, 'reply_negotiate', _handle)
|
return self._now(root, 'reply_negotiate', _handle)
|
||||||
|
|
||||||
def _handle_invoke_method(self, request):
|
def _handle_invoke_method(self, request):
|
||||||
if request.HasField('oid'):
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
@ -28,6 +28,7 @@ import ghidra.async.AsyncPairingQueue;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.debug.api.target.ActionName;
|
import ghidra.debug.api.target.ActionName;
|
||||||
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.debug.api.tracermi.*;
|
import ghidra.debug.api.tracermi.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
@ -89,6 +90,11 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Test Trace RMI connnection";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocketAddress getRemoteAddress() {
|
public SocketAddress getRemoteAddress() {
|
||||||
return new InetSocketAddress("localhost", 0);
|
return new InetSocketAddress("localhost", 0);
|
||||||
|
@ -185,4 +191,9 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
||||||
throw new AssertionError(e);
|
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-enable-all.png||GHIDRA||||END|
|
||||||
src/main/resources/images/breakpoints-make-effective.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/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/connect.png||GHIDRA||||END|
|
||||||
src/main/resources/images/console.png||GHIDRA||||END|
|
src/main/resources/images/console.png||GHIDRA||||END|
|
||||||
src/main/resources/images/debugger.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-disable-all.svg||GHIDRA||||END|
|
||||||
src/main/svg/breakpoints-enable-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/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/connect.svg||GHIDRA||||END|
|
||||||
src/main/svg/console.svg||GHIDRA||||END|
|
src/main/svg/console.svg||GHIDRA||||END|
|
||||||
src/main/svg/debugger.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 = debugger.png
|
||||||
icon.debugger.connect = connect.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.disconnect = disconnect.png
|
||||||
icon.debugger.process = process.png
|
icon.debugger.process = process.png
|
||||||
icon.debugger.thread = thread.png
|
icon.debugger.thread = thread.png
|
||||||
|
|
|
@ -68,6 +68,8 @@ public interface DebuggerResources {
|
||||||
Icon ICON_DEBUGGER = new GIcon("icon.debugger");
|
Icon ICON_DEBUGGER = new GIcon("icon.debugger");
|
||||||
|
|
||||||
Icon ICON_CONNECTION = new GIcon("icon.debugger.connect");
|
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_DISCONNECT = new GIcon("icon.debugger.disconnect");
|
||||||
|
|
||||||
Icon ICON_PROCESS = new GIcon("icon.debugger.process");
|
Icon ICON_PROCESS = new GIcon("icon.debugger.process");
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.console;
|
package ghidra.app.plugin.core.debug.gui.console;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
@ -22,8 +23,10 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
import javax.swing.table.TableCellEditor;
|
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.ActionList;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
||||||
|
|
||||||
|
@ -36,8 +39,13 @@ public class ConsoleActionsCellEditor extends AbstractCellEditor
|
||||||
|
|
||||||
protected ActionList value;
|
protected ActionList value;
|
||||||
|
|
||||||
|
protected Color bg = new Color(0); // Initial cached value
|
||||||
|
|
||||||
public ConsoleActionsCellEditor() {
|
public ConsoleActionsCellEditor() {
|
||||||
ConsoleActionsCellRenderer.configureBox(box);
|
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
|
@Override
|
||||||
|
@ -49,7 +57,11 @@ public class ConsoleActionsCellEditor extends AbstractCellEditor
|
||||||
public Component getTableCellEditorComponent(JTable table, Object v, boolean isSelected,
|
public Component getTableCellEditorComponent(JTable table, Object v, boolean isSelected,
|
||||||
int row, int column) {
|
int row, int column) {
|
||||||
// I can't think of when you'd be "editing" a non-selected cell.
|
// 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;
|
value = (ActionList) v;
|
||||||
ConsoleActionsCellRenderer.populateBox(box, buttonCache, value,
|
ConsoleActionsCellRenderer.populateBox(box, buttonCache, value,
|
||||||
|
|
|
@ -84,6 +84,7 @@ public class ConsoleActionsCellRenderer extends AbstractGhidraColumnRenderer<Act
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
super.getTableCellRendererComponent(data); // A bit of a waste, but sets the background
|
super.getTableCellRendererComponent(data); // A bit of a waste, but sets the background
|
||||||
box.setBackground(getBackground());
|
box.setBackground(getBackground());
|
||||||
|
box.setBorder(getBorder());
|
||||||
|
|
||||||
ActionList value = (ActionList) data.getValue();
|
ActionList value = (ActionList) data.getValue();
|
||||||
populateBox(box, buttonCache, value, button -> {
|
populateBox(box, buttonCache, value, button -> {
|
||||||
|
|
|
@ -152,7 +152,7 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
|
||||||
* @param ctx the context
|
* @param ctx the context
|
||||||
* @return the the log entry
|
* @return the the log entry
|
||||||
*/
|
*/
|
||||||
public LogRow getLogRow(ActionContext ctx) {
|
public LogRow<?> getLogRow(ActionContext ctx) {
|
||||||
return provider.getLogRow(ctx);
|
return provider.getLogRow(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,23 +32,29 @@ import org.apache.logging.log4j.Level;
|
||||||
import org.apache.logging.log4j.core.LogEvent;
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.action.DockingAction;
|
import docking.action.*;
|
||||||
import docking.action.DockingActionIf;
|
|
||||||
import docking.actions.PopupActionProvider;
|
import docking.actions.PopupActionProvider;
|
||||||
import docking.widgets.table.ColumnSortState.SortDirection;
|
import docking.widgets.table.ColumnSortState.SortDirection;
|
||||||
import docking.widgets.table.CustomToStringCellRenderer;
|
import docking.widgets.table.CustomToStringCellRenderer;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
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.ClearAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
|
||||||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
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.AutoOptions;
|
||||||
import ghidra.framework.options.annotation.*;
|
import ghidra.framework.options.annotation.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
import resources.Icons;
|
||||||
|
|
||||||
public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
implements PopupActionProvider {
|
implements PopupActionProvider {
|
||||||
|
@ -57,19 +63,37 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE);
|
new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE);
|
||||||
static final int MIN_ROW_HEIGHT = 16;
|
static final int MIN_ROW_HEIGHT = 16;
|
||||||
|
|
||||||
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow> {
|
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow<?>> {
|
||||||
LEVEL("Level", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false),
|
ICON("Icon", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false),
|
||||||
MESSAGE("Message", String.class, LogRow::getMessage, SortDirection.ASCENDING, false),
|
MESSAGE("Message", Object.class, LogRow::getMessage, SortDirection.ASCENDING, false) {
|
||||||
ACTIONS("Actions", ActionList.class, LogRow::getActions, SortDirection.DESCENDING, true),
|
@Override
|
||||||
TIME("Time", Date.class, LogRow::getDate, SortDirection.DESCENDING, false);
|
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 String header;
|
||||||
private final Function<LogRow, ?> getter;
|
private final Function<LogRow<?>, ?> getter;
|
||||||
private final Class<?> cls;
|
private final Class<?> cls;
|
||||||
private final SortDirection defaultSortDirection;
|
private final SortDirection defaultSortDirection;
|
||||||
private final boolean editable;
|
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) {
|
SortDirection defaultSortDirection, boolean editable) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
this.cls = cls;
|
this.cls = cls;
|
||||||
|
@ -89,17 +113,17 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueOf(LogRow row) {
|
public Object getValueOf(LogRow<?> row) {
|
||||||
return getter.apply(row);
|
return getter.apply(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEditable(LogRow row) {
|
public boolean isEditable(LogRow<?> row) {
|
||||||
return editable;
|
return editable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setValueOf(LogRow row, Object value) {
|
public void setValueOf(LogRow<?> row, Object value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -164,14 +188,26 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
* <p>
|
* <p>
|
||||||
* This class is public for access by test cases only.
|
* 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 Icon icon;
|
||||||
private final String message;
|
private final String message;
|
||||||
private final Date date;
|
private final Date date;
|
||||||
private final ActionContext context;
|
private final ActionContext context;
|
||||||
private final ActionList actions;
|
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) {
|
ActionList actions) {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
@ -180,32 +216,154 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
this.actions = Objects.requireNonNull(actions);
|
this.actions = Objects.requireNonNull(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Icon getIcon() {
|
public Icon getIcon() {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Date getDate() {
|
public Date getDate() {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public ActionContext getActionContext() {
|
public ActionContext getActionContext() {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public ActionList getActions() {
|
public ActionList getActions() {
|
||||||
return actions;
|
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< //
|
protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||||
LogTableColumns, ActionContext, LogRow, LogRow> {
|
LogTableColumns, ActionContext, LogRow<?>, LogRow<?>> {
|
||||||
|
|
||||||
public LogTableModel(PluginTool tool) {
|
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
|
@Override
|
||||||
|
@ -215,7 +373,6 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class LogTable extends GhidraTable {
|
protected static class LogTable extends GhidraTable {
|
||||||
|
|
||||||
public LogTable(LogTableModel model) {
|
public LogTable(LogTableModel model) {
|
||||||
super(model);
|
super(model);
|
||||||
}
|
}
|
||||||
|
@ -255,12 +412,11 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
ActionList actions =
|
ActionList actions =
|
||||||
(ActionList) getModel().getValueAt(r, convertColumnIndexToModel(c));
|
(ActionList) getModel().getValueAt(r, convertColumnIndexToModel(c));
|
||||||
if (actions != null && !actions.isEmpty()) {
|
if (actions != null && !actions.isEmpty()) {
|
||||||
return ACTION_BUTTON_SIZE;
|
return ACTION_BUTTON_SIZE + 2;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (renderer instanceof CustomToStringCellRenderer<?>) {
|
if (renderer instanceof HtmlOrProgressCellRenderer custom) {
|
||||||
CustomToStringCellRenderer<?> custom = (CustomToStringCellRenderer<?>) renderer;
|
|
||||||
int colWidth = getColumnModel().getColumn(c).getWidth();
|
int colWidth = getColumnModel().getColumn(c).getWidth();
|
||||||
prepareRenderer(renderer, r, c);
|
prepareRenderer(renderer, r, c);
|
||||||
return custom.getRowHeight(colWidth);
|
return custom.getRowHeight(colWidth);
|
||||||
|
@ -271,6 +427,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
private final DebuggerConsolePlugin plugin;
|
private final DebuggerConsolePlugin plugin;
|
||||||
|
|
||||||
|
// @AutoServiceConsumed via method
|
||||||
|
private ProgressService progressService;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
@ -287,18 +445,21 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
protected final LogTableModel logTableModel;
|
protected final LogTableModel logTableModel;
|
||||||
protected GhidraTable logTable;
|
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 JPanel mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
private final ListenerForProgress progressListener;
|
||||||
|
|
||||||
DockingAction actionClear;
|
DockingAction actionClear;
|
||||||
DockingAction actionSelectNone;
|
DockingAction actionSelectNone;
|
||||||
|
|
||||||
public DebuggerConsoleProvider(DebuggerConsolePlugin plugin) {
|
public DebuggerConsoleProvider(DebuggerConsolePlugin plugin) {
|
||||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
|
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.progressListener = new ListenerForProgress();
|
||||||
|
|
||||||
logTableModel = new LogTableModel(tool);
|
logTableModel = new LogTableModel(tool);
|
||||||
|
|
||||||
|
@ -329,24 +490,21 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel);
|
logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel);
|
||||||
mainPanel.add(logFilterPanel, BorderLayout.NORTH);
|
mainPanel.add(logFilterPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
logTable.setRowHeight(ACTION_BUTTON_SIZE);
|
logTable.setRowHeight(ACTION_BUTTON_SIZE + 2);
|
||||||
TableColumnModel columnModel = logTable.getColumnModel();
|
TableColumnModel columnModel = logTable.getColumnModel();
|
||||||
|
|
||||||
TableColumn levelCol = columnModel.getColumn(LogTableColumns.LEVEL.ordinal());
|
TableColumn iconCol = columnModel.getColumn(LogTableColumns.ICON.ordinal());
|
||||||
levelCol.setMaxWidth(24);
|
iconCol.setMaxWidth(24);
|
||||||
levelCol.setMinWidth(24);
|
iconCol.setMinWidth(24);
|
||||||
|
|
||||||
TableColumn msgCol = columnModel.getColumn(LogTableColumns.MESSAGE.ordinal());
|
TableColumn msgCol = columnModel.getColumn(LogTableColumns.MESSAGE.ordinal());
|
||||||
msgCol.setPreferredWidth(150);
|
msgCol.setPreferredWidth(150);
|
||||||
msgCol.setCellRenderer(CustomToStringCellRenderer.HTML);
|
|
||||||
|
|
||||||
TableColumn actCol = columnModel.getColumn(LogTableColumns.ACTIONS.ordinal());
|
TableColumn actCol = columnModel.getColumn(LogTableColumns.ACTIONS.ordinal());
|
||||||
actCol.setPreferredWidth(50);
|
actCol.setPreferredWidth(50);
|
||||||
actCol.setCellRenderer(new ConsoleActionsCellRenderer());
|
|
||||||
actCol.setCellEditor(new ConsoleActionsCellEditor());
|
actCol.setCellEditor(new ConsoleActionsCellEditor());
|
||||||
|
|
||||||
TableColumn timeCol = columnModel.getColumn(LogTableColumns.TIME.ordinal());
|
TableColumn timeCol = columnModel.getColumn(LogTableColumns.TIME.ordinal());
|
||||||
timeCol.setCellRenderer(CustomToStringCellRenderer.TIME_24HMSms);
|
|
||||||
timeCol.setPreferredWidth(15);
|
timeCol.setPreferredWidth(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,8 +520,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
private void activatedClear(ActionContext ctx) {
|
private void activatedClear(ActionContext ctx) {
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
logTableModel.clear();
|
logTableModel.deleteItemsWith(r -> !(r instanceof MonitorLogRow));
|
||||||
buffer.clear();
|
buffer.removeIf(r -> !(r instanceof MonitorLogRow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +534,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
if (logTable.getSelectedRowCount() != 1) {
|
if (logTable.getSelectedRowCount() != 1) {
|
||||||
return super.getActionContext(event);
|
return super.getActionContext(event);
|
||||||
}
|
}
|
||||||
LogRow sel = logFilterPanel.getSelectedItem();
|
LogRow<?> sel = logFilterPanel.getSelectedItem();
|
||||||
if (sel == null) {
|
if (sel == null) {
|
||||||
// I guess this can happen because of timing?
|
// I guess this can happen because of timing?
|
||||||
return super.getActionContext(event);
|
return super.getActionContext(event);
|
||||||
|
@ -407,12 +565,13 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void log(Icon icon, String message, ActionContext context) {
|
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) {
|
synchronized (buffer) {
|
||||||
LogRow old = logTableModel.deleteKey(row.getActionContext());
|
LogRow<?> old = logTableModel.deleteKey(row.getActionContext());
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
buffer.remove(old);
|
buffer.remove(old);
|
||||||
}
|
}
|
||||||
|
@ -438,14 +597,14 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
protected void logEvent(LogEvent event) {
|
protected void logEvent(LogEvent event) {
|
||||||
ActionContext context = new LogRowConsoleActionContext();
|
ActionContext context = new LogRowConsoleActionContext();
|
||||||
logRow(new LogRow(iconForLevel(event.getLevel()),
|
logRow(new MessageLogRow(iconForLevel(event.getLevel()),
|
||||||
"<html>" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()),
|
"<html>" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()),
|
||||||
new Date(event.getTimeMillis()), context, computeToolbarActions(context)));
|
new Date(event.getTimeMillis()), context, computeToolbarActions(context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeFromLog(ActionContext context) {
|
protected void removeFromLog(ActionContext context) {
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
LogRow r = logTableModel.deleteKey(context);
|
LogRow<?> r = logTableModel.deleteKey(context);
|
||||||
buffer.remove(r);
|
buffer.remove(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -508,7 +667,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public java.util.List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
|
public List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
|
||||||
return streamActions(context)
|
return streamActions(context)
|
||||||
.filter(a -> a.isAddToPopup(context))
|
.filter(a -> a.isAddToPopup(context))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -518,14 +677,41 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
return logTableModel.getModelData()
|
return logTableModel.getModelData()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(r -> ctxCls.isInstance(r.context))
|
.filter(r -> ctxCls.isInstance(r.getActionContext()))
|
||||||
.count();
|
.count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogRow getLogRow(ActionContext ctx) {
|
public LogRow<?> getLogRow(ActionContext ctx) {
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
return logTableModel.getMap().get(ctx);
|
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;
|
protected boolean resetRequested;
|
||||||
|
|
||||||
private final PluginTool tool;
|
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.
|
// TODO: Not sure this is the best keying, but I think it works.
|
||||||
private Map<NameTypePair, Object> memorized = new HashMap<>();
|
private Map<NameTypePair, Object> memorized = new HashMap<>();
|
||||||
|
@ -275,13 +275,13 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invoke(ActionEvent evt) {
|
void invoke(ActionEvent evt) {
|
||||||
this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false);
|
this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false);
|
||||||
this.resetRequested = false;
|
this.resetRequested = false;
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset(ActionEvent evt) {
|
void reset(ActionEvent evt) {
|
||||||
this.arguments = new LinkedHashMap<>();
|
this.arguments = new LinkedHashMap<>();
|
||||||
this.resetRequested = true;
|
this.resetRequested = true;
|
||||||
close();
|
close();
|
||||||
|
@ -343,6 +343,10 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||||
return type.cast(memorized.get(new NameTypePair(name, type)));
|
return type.cast(memorized.get(new NameTypePair(name, type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void forgetMemorizedArguments() {
|
||||||
|
memorized.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
PropertyEditor editor = (PropertyEditor) evt.getSource();
|
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;
|
return address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (context.getContextObject() instanceof MarkerLocation ml) {
|
if (context != null && context.getContextObject() instanceof MarkerLocation ml) {
|
||||||
Address address = findAddress(ml);
|
Address address = findAddress(ml);
|
||||||
if (address != null) {
|
if (address != null) {
|
||||||
return address;
|
return address;
|
||||||
|
|
|
@ -1084,6 +1084,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return getCurrentFor(trace).trace(trace);
|
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
|
@Override
|
||||||
public DebuggerCoordinates resolvePlatform(TracePlatform platform) {
|
public DebuggerCoordinates resolvePlatform(TracePlatform platform) {
|
||||||
Trace trace = platform == null ? null : platform.getTrace();
|
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;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -24,7 +24,13 @@ import docking.DefaultActionContext;
|
||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
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.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.Task;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||||
DebuggerConsolePlugin consolePlugin;
|
DebuggerConsolePlugin consolePlugin;
|
||||||
|
@ -43,18 +49,18 @@ public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
@Test
|
@Test
|
||||||
public void testActions() throws Exception {
|
public void testActions() throws Exception {
|
||||||
consolePlugin.addResolutionAction(new ActionBuilder("Add", name.getMethodName())
|
consolePlugin.addResolutionAction(new ActionBuilder("Add", name.getMethodName())
|
||||||
.toolBarIcon(DebuggerResources.ICON_ADD)
|
.toolBarIcon(DebuggerResources.ICON_ADD)
|
||||||
.description("Add")
|
.description("Add")
|
||||||
.withContext(TestConsoleActionContext.class)
|
.withContext(TestConsoleActionContext.class)
|
||||||
.onAction(ctx -> Msg.info(this, "Add clicked"))
|
.onAction(ctx -> Msg.info(this, "Add clicked"))
|
||||||
.build());
|
.build());
|
||||||
consolePlugin.addResolutionAction(new ActionBuilder("Delete", name.getMethodName())
|
consolePlugin.addResolutionAction(new ActionBuilder("Delete", name.getMethodName())
|
||||||
.popupMenuIcon(DebuggerResources.ICON_DELETE)
|
.popupMenuIcon(DebuggerResources.ICON_DELETE)
|
||||||
.popupMenuPath("Delete")
|
.popupMenuPath("Delete")
|
||||||
.description("Delete")
|
.description("Delete")
|
||||||
.withContext(TestConsoleActionContext.class)
|
.withContext(TestConsoleActionContext.class)
|
||||||
.onAction(ctx -> Msg.info(this, "Delete clicked"))
|
.onAction(ctx -> Msg.info(this, "Delete clicked"))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "<html><b>Test message</b></html>",
|
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "<html><b>Test message</b></html>",
|
||||||
new TestConsoleActionContext());
|
new TestConsoleActionContext());
|
||||||
|
@ -75,4 +81,40 @@ public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
|
|
||||||
waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount()));
|
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);
|
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
||||||
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
||||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage().contains("recovery"));
|
assertTrue(consolePlugin.getLogRow(ctx).getMessage() instanceof String message &&
|
||||||
|
message.contains("recovery"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1626,7 +1627,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
|
|
||||||
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
DebuggerOpenProgramActionContext ctx = new DebuggerOpenProgramActionContext(df);
|
||||||
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
waitForPass(() -> assertTrue(consolePlugin.logContains(ctx)));
|
||||||
assertTrue(consolePlugin.getLogRow(ctx).getMessage().contains("version"));
|
assertTrue(consolePlugin.getLogRow(ctx).getMessage() instanceof String message &&
|
||||||
|
message.contains("version"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1643,7 +1645,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||||
consolePlugin.log(DebuggerResources.ICON_MODULES, "Test resolution", ctx);
|
consolePlugin.log(DebuggerResources.ICON_MODULES, "Test resolution", ctx);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
LogRow row = consolePlugin.getLogRow(ctx);
|
LogRow<?> row = consolePlugin.getLogRow(ctx);
|
||||||
assertEquals(1, row.getActions().size());
|
assertEquals(1, row.getActions().size());
|
||||||
BoundAction boundAction = row.getActions().get(0);
|
BoundAction boundAction = row.getActions().get(0);
|
||||||
assertEquals(listingProvider.actionOpenProgram, boundAction.action);
|
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 db.Transaction;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
import generic.test.rule.Repeated;
|
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||||
import ghidra.app.services.DebuggerControlService;
|
import ghidra.app.services.DebuggerControlService;
|
||||||
|
|
|
@ -150,11 +150,19 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
return names.computeIfAbsent(name, SchemaName::new);
|
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) {
|
public TargetObjectSchema schemaFromXml(Element schemaElem) {
|
||||||
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue("name", "")));
|
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue("name", "")));
|
||||||
|
|
||||||
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, "interface")) {
|
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);
|
Class<? extends TargetObject> iface = TargetObject.INTERFACES_BY_NAME.get(ifaceName);
|
||||||
if (iface == null) {
|
if (iface == null) {
|
||||||
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
|
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
|
||||||
|
@ -166,18 +174,18 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
|
|
||||||
builder.setCanonicalContainer(parseBoolean(schemaElem, "canonical"));
|
builder.setCanonicalContainer(parseBoolean(schemaElem, "canonical"));
|
||||||
builder.setElementResyncMode(
|
builder.setElementResyncMode(
|
||||||
ResyncMode.valueOf(schemaElem.getAttributeValue("elementResync")));
|
ResyncMode.valueOf(requireAttributeValue(schemaElem, "elementResync")));
|
||||||
builder.setAttributeResyncMode(
|
builder.setAttributeResyncMode(
|
||||||
ResyncMode.valueOf(schemaElem.getAttributeValue("attributeResync")));
|
ResyncMode.valueOf(requireAttributeValue(schemaElem, "attributeResync")));
|
||||||
|
|
||||||
for (Element elemElem : XmlUtilities.getChildren(schemaElem, "element")) {
|
for (Element elemElem : XmlUtilities.getChildren(schemaElem, "element")) {
|
||||||
SchemaName schema = name(elemElem.getAttributeValue("schema"));
|
SchemaName schema = name(requireAttributeValue(elemElem, "schema"));
|
||||||
String index = elemElem.getAttributeValue("index", "");
|
String index = elemElem.getAttributeValue("index", "");
|
||||||
builder.addElementSchema(index, schema, elemElem);
|
builder.addElementSchema(index, schema, elemElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Element attrElem : XmlUtilities.getChildren(schemaElem, "attribute")) {
|
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 required = parseBoolean(attrElem, "required");
|
||||||
boolean fixed = parseBoolean(attrElem, "fixed");
|
boolean fixed = parseBoolean(attrElem, "fixed");
|
||||||
boolean hidden = parseBoolean(attrElem, "hidden");
|
boolean hidden = parseBoolean(attrElem, "hidden");
|
||||||
|
|
|
@ -63,6 +63,7 @@ icon.run = play.png
|
||||||
icon.spreadsheet = application-vnd.oasis.opendocument.spreadsheet-template.png
|
icon.spreadsheet = application-vnd.oasis.opendocument.spreadsheet-template.png
|
||||||
icon.pulldown = menu16.gif
|
icon.pulldown = menu16.gif
|
||||||
icon.window = application_xp.png
|
icon.window = application_xp.png
|
||||||
|
icon.pending = hourglass.png
|
||||||
icon.zoom.in = zoom_in.png
|
icon.zoom.in = zoom_in.png
|
||||||
icon.zoom.out = zoom_out.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 = info_small.png
|
||||||
icon.widget.table.header.help.hovered = info_small_hover.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.report = icon.spreadsheet
|
||||||
icon.dialog.error.expandable.exception = program_obj.png
|
icon.dialog.error.expandable.exception = program_obj.png
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
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.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
import ghidra.app.services.TraceRmiService;
|
import ghidra.app.services.TraceRmiService;
|
||||||
import ghidra.dbg.testutil.DummyProc;
|
import ghidra.dbg.testutil.DummyProc;
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
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.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
import ghidra.app.services.TraceRmiService;
|
import ghidra.app.services.TraceRmiService;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
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.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
import ghidra.app.services.TraceRmiService;
|
import ghidra.app.services.TraceRmiService;
|
||||||
import ghidra.dbg.testutil.DummyProc;
|
import ghidra.dbg.testutil.DummyProc;
|
||||||
|
|
|
@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.gui;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
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.rmi.trace.TestTraceRmiConnection.*;
|
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.*;
|
||||||
import ghidra.dbg.target.schema.*;
|
import ghidra.dbg.target.schema.*;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.debug.api.target.ActionName;
|
import ghidra.debug.api.target.ActionName;
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
import generic.test.category.NightlyCategory;
|
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.database.target.DBTraceObjectManager;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import java.util.*;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import db.Transaction;
|
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.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
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.mapping.ObjectBasedDebuggerTargetTraceMapper;
|
||||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
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.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.DebuggerControlService;
|
||||||
import ghidra.app.services.DebuggerEmulationService;
|
import ghidra.app.services.DebuggerEmulationService;
|
||||||
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
|
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.junit.Before;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
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.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue