mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-5271: Fix break/watchpoints with lldb. Many framework changes.
This commit is contained in:
parent
3bfcb8695f
commit
2a41c8fe6b
16 changed files with 335 additions and 260 deletions
|
@ -42,20 +42,16 @@ DEFAULT_REGISTER_BANK = "General Purpose Registers"
|
||||||
AVAILABLES_PATH = 'Available'
|
AVAILABLES_PATH = 'Available'
|
||||||
AVAILABLE_KEY_PATTERN = '[{pid}]'
|
AVAILABLE_KEY_PATTERN = '[{pid}]'
|
||||||
AVAILABLE_PATTERN = AVAILABLES_PATH + AVAILABLE_KEY_PATTERN
|
AVAILABLE_PATTERN = AVAILABLES_PATH + AVAILABLE_KEY_PATTERN
|
||||||
BREAKPOINTS_PATH = 'Breakpoints'
|
|
||||||
BREAKPOINT_KEY_PATTERN = '[{breaknum}]'
|
BREAKPOINT_KEY_PATTERN = '[{breaknum}]'
|
||||||
BREAKPOINT_PATTERN = BREAKPOINTS_PATH + BREAKPOINT_KEY_PATTERN
|
|
||||||
WATCHPOINTS_PATH = 'Watchpoints'
|
|
||||||
WATCHPOINT_KEY_PATTERN = '[{watchnum}]'
|
WATCHPOINT_KEY_PATTERN = '[{watchnum}]'
|
||||||
WATCHPOINT_PATTERN = WATCHPOINTS_PATH + WATCHPOINT_KEY_PATTERN
|
|
||||||
BREAK_LOC_KEY_PATTERN = '[{locnum}]'
|
BREAK_LOC_KEY_PATTERN = '[{locnum}]'
|
||||||
PROCESSES_PATH = 'Processes'
|
PROCESSES_PATH = 'Processes'
|
||||||
PROCESS_KEY_PATTERN = '[{procnum}]'
|
PROCESS_KEY_PATTERN = '[{procnum}]'
|
||||||
PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN
|
PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN
|
||||||
PROC_WATCHES_PATTERN = PROCESS_PATTERN + '.Watchpoints'
|
PROC_WATCHES_PATTERN = PROCESS_PATTERN + '.Watchpoints'
|
||||||
PROC_WATCH_KEY_PATTERN = PROC_WATCHES_PATTERN + '[{watchnum}]'
|
PROC_WATCH_PATTERN = PROC_WATCHES_PATTERN + WATCHPOINT_KEY_PATTERN
|
||||||
PROC_BREAKS_PATTERN = PROCESS_PATTERN + '.Breakpoints'
|
PROC_BREAKS_PATTERN = PROCESS_PATTERN + '.Breakpoints'
|
||||||
PROC_BREAK_KEY_PATTERN = '[{breaknum}.{locnum}]'
|
PROC_BREAK_PATTERN = PROC_BREAKS_PATTERN + BREAKPOINT_KEY_PATTERN
|
||||||
ENV_PATTERN = PROCESS_PATTERN + '.Environment'
|
ENV_PATTERN = PROCESS_PATTERN + '.Environment'
|
||||||
THREADS_PATTERN = PROCESS_PATTERN + '.Threads'
|
THREADS_PATTERN = PROCESS_PATTERN + '.Threads'
|
||||||
THREAD_KEY_PATTERN = '[{tnum}]'
|
THREAD_KEY_PATTERN = '[{tnum}]'
|
||||||
|
@ -1474,108 +1470,102 @@ def ghidra_trace_put_available(debugger, command, result, internal_dict):
|
||||||
put_available()
|
put_available()
|
||||||
|
|
||||||
|
|
||||||
def put_single_breakpoint(b, ibobj, proc, ikeys):
|
def put_single_breakpoint(b, proc):
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
bpath = BREAKPOINT_PATTERN.format(breaknum=b.GetID())
|
bpt_path = PROC_BREAK_PATTERN.format(
|
||||||
brkobj = STATE.trace.create_object(bpath)
|
procnum=proc.GetProcessID(), breaknum=b.GetID())
|
||||||
|
bpt_obj = STATE.trace.create_object(bpt_path)
|
||||||
if b.IsHardware():
|
if b.IsHardware():
|
||||||
brkobj.set_value('Expression', util.get_description(b))
|
bpt_obj.set_value('Expression', util.get_description(b))
|
||||||
brkobj.set_value('Kinds', 'HW_EXECUTE')
|
bpt_obj.set_value('Kinds', 'HW_EXECUTE')
|
||||||
else:
|
else:
|
||||||
brkobj.set_value('Expression', util.get_description(b))
|
bpt_obj.set_value('Expression', util.get_description(b))
|
||||||
brkobj.set_value('Kinds', 'SW_EXECUTE')
|
bpt_obj.set_value('Kinds', 'SW_EXECUTE')
|
||||||
cmdList = lldb.SBStringList()
|
cmdList = lldb.SBStringList()
|
||||||
if b.GetCommandLineCommands(cmdList):
|
if b.GetCommandLineCommands(cmdList):
|
||||||
list = []
|
list = []
|
||||||
for i in range(0, cmdList.GetSize()):
|
for i in range(0, cmdList.GetSize()):
|
||||||
list.append(cmdList.GetStringAtIndex(i))
|
list.append(cmdList.GetStringAtIndex(i))
|
||||||
brkobj.set_value('Commands', list)
|
bpt_obj.set_value('Commands', list)
|
||||||
if b.GetCondition():
|
if b.GetCondition():
|
||||||
brkobj.set_value('Condition', b.GetCondition())
|
bpt_obj.set_value('Condition', b.GetCondition())
|
||||||
brkobj.set_value('Hit Count', b.GetHitCount())
|
bpt_obj.set_value('Hit Count', b.GetHitCount())
|
||||||
brkobj.set_value('Ignore Count', b.GetIgnoreCount())
|
bpt_obj.set_value('Ignore Count', b.GetIgnoreCount())
|
||||||
brkobj.set_value('Temporary', b.IsOneShot())
|
bpt_obj.set_value('Temporary', b.IsOneShot())
|
||||||
brkobj.set_value('Enabled', b.IsEnabled())
|
bpt_obj.set_value('Enabled', b.IsEnabled())
|
||||||
keys = []
|
loc_keys = []
|
||||||
locs = util.BREAKPOINT_LOCATION_INFO_READER.get_locations(b)
|
locs = util.BREAKPOINT_LOCATION_INFO_READER.get_locations(b)
|
||||||
hooks.BRK_STATE.update_brkloc_count(b, len(locs))
|
|
||||||
for i, l in enumerate(locs):
|
for i, l in enumerate(locs):
|
||||||
# Retain the key, even if not for this process
|
# Retain the key, even if not for this process
|
||||||
k = BREAK_LOC_KEY_PATTERN.format(locnum=i+1)
|
k = BREAK_LOC_KEY_PATTERN.format(locnum=i+1)
|
||||||
keys.append(k)
|
loc_keys.append(k)
|
||||||
locobj = STATE.trace.create_object(bpath + k)
|
loc_obj = STATE.trace.create_object(bpt_path + k)
|
||||||
ik = PROC_BREAK_KEY_PATTERN.format(breaknum=b.GetID(), locnum=i+1)
|
|
||||||
ikeys.append(ik)
|
|
||||||
if b.location is not None: # Implies execution break
|
if b.location is not None: # Implies execution break
|
||||||
base, addr = mapper.map(proc, l.GetLoadAddress())
|
base, addr = mapper.map(proc, l.GetLoadAddress())
|
||||||
if base != addr.space:
|
if base != addr.space:
|
||||||
STATE.trace.create_overlay_space(base, addr.space)
|
STATE.trace.create_overlay_space(base, addr.space)
|
||||||
locobj.set_value('Range', addr.extend(1))
|
loc_obj.set_value('Range', addr.extend(1))
|
||||||
locobj.set_value('Enabled', l.IsEnabled())
|
loc_obj.set_value('Enabled', l.IsEnabled())
|
||||||
else: # I guess it's a catchpoint
|
else: # I guess it's a catchpoint
|
||||||
pass
|
pass
|
||||||
locobj.insert()
|
loc_obj.insert()
|
||||||
ibobj.set_value(ik, locobj)
|
bpt_obj.retain_values(loc_keys)
|
||||||
brkobj.retain_values(keys)
|
bpt_obj.insert()
|
||||||
brkobj.insert()
|
|
||||||
|
|
||||||
|
|
||||||
def put_single_watchpoint(b, ibobj, proc, ikeys):
|
def put_single_watchpoint(w, proc):
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
bpath = PROC_WATCH_KEY_PATTERN.format(
|
wpt_path = PROC_WATCH_PATTERN.format(
|
||||||
procnum=proc.GetProcessID(), watchnum=b.GetID())
|
procnum=proc.GetProcessID(), watchnum=w.GetID())
|
||||||
brkobj = STATE.trace.create_object(bpath)
|
wpt_obj = STATE.trace.create_object(wpt_path)
|
||||||
desc = util.get_description(b, level=0)
|
desc = util.get_description(w, level=0)
|
||||||
brkobj.set_value('Expression', desc)
|
wpt_obj.set_value('Expression', desc)
|
||||||
brkobj.set_value('Kinds', 'WRITE')
|
wpt_obj.set_value('Kinds', 'WRITE')
|
||||||
if "type = r" in desc:
|
if "type = r" in desc:
|
||||||
brkobj.set_value('Kinds', 'READ')
|
wpt_obj.set_value('Kinds', 'READ')
|
||||||
if "type = rw" in desc:
|
if "type = rw" in desc:
|
||||||
brkobj.set_value('Kinds', 'READ,WRITE')
|
wpt_obj.set_value('Kinds', 'READ,WRITE')
|
||||||
base, addr = mapper.map(proc, b.GetWatchAddress())
|
base, addr = mapper.map(proc, w.GetWatchAddress())
|
||||||
if base != addr.space:
|
if base != addr.space:
|
||||||
STATE.trace.create_overlay_space(base, addr.space)
|
STATE.trace.create_overlay_space(base, addr.space)
|
||||||
brkobj.set_value('Range', addr.extend(b.GetWatchSize()))
|
wpt_obj.set_value('Range', addr.extend(w.GetWatchSize()))
|
||||||
if b.GetCondition():
|
if w.GetCondition():
|
||||||
brkobj.set_value('Condition', b.GetCondition())
|
wpt_obj.set_value('Condition', w.GetCondition())
|
||||||
brkobj.set_value('Hit Count', b.GetHitCount())
|
wpt_obj.set_value('Hit Count', w.GetHitCount())
|
||||||
brkobj.set_value('Ignore Count', b.GetIgnoreCount())
|
wpt_obj.set_value('Ignore Count', w.GetIgnoreCount())
|
||||||
brkobj.set_value('Hardware Index', b.GetHardwareIndex())
|
wpt_obj.set_value('Hardware Index', w.GetHardwareIndex())
|
||||||
brkobj.set_value('Watch Address', hex(b.GetWatchAddress()))
|
wpt_obj.set_value('Watch Address', hex(w.GetWatchAddress()))
|
||||||
brkobj.set_value('Watch Size', b.GetWatchSize())
|
wpt_obj.set_value('Watch Size', w.GetWatchSize())
|
||||||
brkobj.set_value('Enabled', b.IsEnabled())
|
wpt_obj.set_value('Enabled', w.IsEnabled())
|
||||||
brkobj.insert()
|
wpt_obj.insert()
|
||||||
|
|
||||||
|
|
||||||
def put_breakpoints():
|
def put_breakpoints():
|
||||||
target = util.get_target()
|
target = util.get_target()
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
ibpath = PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID())
|
cont_path = PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID())
|
||||||
ibobj = STATE.trace.create_object(ibpath)
|
cont_obj = STATE.trace.create_object(cont_path)
|
||||||
keys = []
|
keys = []
|
||||||
ikeys = []
|
|
||||||
for i in range(0, target.GetNumBreakpoints()):
|
for i in range(0, target.GetNumBreakpoints()):
|
||||||
b = target.GetBreakpointAtIndex(i)
|
b = target.GetBreakpointAtIndex(i)
|
||||||
keys.append(BREAKPOINT_KEY_PATTERN.format(breaknum=b.GetID()))
|
keys.append(BREAKPOINT_KEY_PATTERN.format(breaknum=b.GetID()))
|
||||||
put_single_breakpoint(b, ibobj, proc, ikeys)
|
put_single_breakpoint(b, proc)
|
||||||
ibobj.insert()
|
cont_obj.insert()
|
||||||
STATE.trace.proxy_object_path(BREAKPOINTS_PATH).retain_values(keys)
|
cont_obj.retain_values(keys)
|
||||||
ibobj.retain_values(ikeys)
|
|
||||||
|
|
||||||
|
|
||||||
def put_watchpoints():
|
def put_watchpoints():
|
||||||
target = util.get_target()
|
target = util.get_target()
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
ibpath = PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID())
|
cont_path = PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID())
|
||||||
ibobj = STATE.trace.create_object(ibpath)
|
cont_obj = STATE.trace.create_object(cont_path)
|
||||||
keys = []
|
keys = []
|
||||||
ikeys = []
|
|
||||||
for i in range(0, target.GetNumWatchpoints()):
|
for i in range(0, target.GetNumWatchpoints()):
|
||||||
b = target.GetWatchpointAtIndex(i)
|
b = target.GetWatchpointAtIndex(i)
|
||||||
keys.append(WATCHPOINT_KEY_PATTERN.format(watchnum=b.GetID()))
|
keys.append(WATCHPOINT_KEY_PATTERN.format(watchnum=b.GetID()))
|
||||||
put_single_watchpoint(b, ibobj, proc, ikeys)
|
put_single_watchpoint(b, proc)
|
||||||
ibobj.insert()
|
cont_obj.insert()
|
||||||
STATE.trace.proxy_object_path(WATCHPOINTS_PATH).retain_values(keys)
|
cont_obj.retain_values(keys)
|
||||||
|
|
||||||
|
|
||||||
@convert_errors
|
@convert_errors
|
||||||
|
|
|
@ -110,28 +110,7 @@ class ProcessState(object):
|
||||||
procobj.set_value('State', 'TERMINATED')
|
procobj.set_value('State', 'TERMINATED')
|
||||||
|
|
||||||
|
|
||||||
class BrkState(object):
|
|
||||||
__slots__ = ('break_loc_counts',)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.break_loc_counts = {}
|
|
||||||
|
|
||||||
def update_brkloc_count(self, b, count):
|
|
||||||
self.break_loc_counts[b.GetID()] = count
|
|
||||||
|
|
||||||
def get_brkloc_count(self, b):
|
|
||||||
return self.break_loc_counts.get(b.GetID(), 0)
|
|
||||||
|
|
||||||
def del_brkloc_count(self, b):
|
|
||||||
if b.GetID() not in self.break_loc_counts:
|
|
||||||
return 0 # TODO: Print a warning?
|
|
||||||
count = self.break_loc_counts[b.GetID()]
|
|
||||||
del self.break_loc_counts[b.GetID()]
|
|
||||||
return count
|
|
||||||
|
|
||||||
|
|
||||||
HOOK_STATE = HookState()
|
HOOK_STATE = HookState()
|
||||||
BRK_STATE = BrkState()
|
|
||||||
PROC_STATE = {}
|
PROC_STATE = {}
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,9 +219,9 @@ def process_event(self, listener, event):
|
||||||
return True
|
return True
|
||||||
if lldb.SBWatchpoint.EventIsWatchpointEvent(event):
|
if lldb.SBWatchpoint.EventIsWatchpointEvent(event):
|
||||||
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event)
|
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event)
|
||||||
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt)
|
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(event)
|
||||||
if btype is lldb.eWatchpointEventTypeAdded:
|
if btype is lldb.eWatchpointEventTypeAdded:
|
||||||
return on_watchpoint_added(bpt)
|
return on_watchpoint_created(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeCommandChanged:
|
if btype is lldb.eWatchpointEventTypeCommandChanged:
|
||||||
return on_watchpoint_modified(bpt)
|
return on_watchpoint_modified(bpt)
|
||||||
if btype is lldb.eWatchpointEventTypeConditionChanged:
|
if btype is lldb.eWatchpointEventTypeConditionChanged:
|
||||||
|
@ -550,18 +529,6 @@ def on_exited(event):
|
||||||
commands.activate()
|
commands.activate()
|
||||||
|
|
||||||
|
|
||||||
def notify_others_breaks(proc):
|
|
||||||
for num, state in PROC_STATE.items():
|
|
||||||
if num != proc.GetProcessID():
|
|
||||||
state.breaks = True
|
|
||||||
|
|
||||||
|
|
||||||
def notify_others_watches(proc):
|
|
||||||
for num, state in PROC_STATE.items():
|
|
||||||
if num != proc.GetProcessID():
|
|
||||||
state.watches = True
|
|
||||||
|
|
||||||
|
|
||||||
def modules_changed():
|
def modules_changed():
|
||||||
# Assumption: affects the current process
|
# Assumption: affects the current process
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
|
@ -580,111 +547,80 @@ def on_free_objfile(event):
|
||||||
|
|
||||||
def on_breakpoint_created(b):
|
def on_breakpoint_created(b):
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
notify_others_breaks(proc)
|
|
||||||
if proc.GetProcessID() not in PROC_STATE:
|
if proc.GetProcessID() not in PROC_STATE:
|
||||||
return
|
return
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID())
|
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Breakpoint {} created".format(b.GetID())):
|
with trace.open_tx("Breakpoint {} created".format(b.GetID())):
|
||||||
ibobj = trace.create_object(ibpath)
|
commands.put_single_breakpoint(b, proc)
|
||||||
# Do not use retain_values or it'll remove other locs
|
|
||||||
commands.put_single_breakpoint(b, ibobj, proc, [])
|
|
||||||
ibobj.insert()
|
|
||||||
|
|
||||||
|
|
||||||
def on_breakpoint_modified(b):
|
def on_breakpoint_modified(b):
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
notify_others_breaks(proc)
|
|
||||||
if proc.GetProcessID() not in PROC_STATE:
|
if proc.GetProcessID() not in PROC_STATE:
|
||||||
return
|
return
|
||||||
old_count = BRK_STATE.get_brkloc_count(b)
|
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID())
|
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Breakpoint {} modified".format(b.GetID())):
|
with trace.open_tx("Breakpoint {} modified".format(b.GetID())):
|
||||||
ibobj = trace.create_object(ibpath)
|
commands.put_single_breakpoint(b, proc)
|
||||||
commands.put_single_breakpoint(b, ibobj, proc, [])
|
|
||||||
new_count = BRK_STATE.get_brkloc_count(b)
|
|
||||||
# NOTE: Location may not apply to process, but whatever.
|
|
||||||
for i in range(new_count, old_count):
|
|
||||||
ikey = commands.PROC_BREAK_KEY_PATTERN.format(
|
|
||||||
breaknum=b.GetID(), locnum=i+1)
|
|
||||||
ibobj.set_value(ikey, None)
|
|
||||||
|
|
||||||
|
|
||||||
def on_breakpoint_deleted(b):
|
def on_breakpoint_deleted(b):
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
notify_others_breaks(proc)
|
|
||||||
if proc.GetProcessID() not in PROC_STATE:
|
if proc.GetProcessID() not in PROC_STATE:
|
||||||
return
|
return
|
||||||
old_count = BRK_STATE.del_brkloc_count(b)
|
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
bpath = commands.BREAKPOINT_PATTERN.format(breaknum=b.GetID())
|
bpt_path = commands.PROC_BREAK_PATTERN.format(
|
||||||
ibobj = trace.proxy_object_path(
|
procnum=proc.GetProcessID(), breaknum=b.GetID())
|
||||||
commands.PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID()))
|
bpt_obj = trace.proxy_object_path(bpt_path)
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Breakpoint {} deleted".format(b.GetID())):
|
with trace.open_tx("Breakpoint {} deleted".format(b.GetID())):
|
||||||
trace.proxy_object_path(bpath).remove(tree=True)
|
bpt_obj.remove(tree=True)
|
||||||
for i in range(old_count):
|
|
||||||
ikey = commands.PROC_BREAK_KEY_PATTERN.format(
|
|
||||||
breaknum=b.GetID(), locnum=i+1)
|
|
||||||
ibobj.set_value(ikey, None)
|
|
||||||
|
|
||||||
|
|
||||||
def on_watchpoint_created(b):
|
def on_watchpoint_created(b):
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
notify_others_watches(proc)
|
|
||||||
if proc.GetProcessID() not in PROC_STATE:
|
if proc.GetProcessID() not in PROC_STATE:
|
||||||
return
|
return
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
ibpath = commands.PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID())
|
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Breakpoint {} created".format(b.GetID())):
|
with trace.open_tx("Breakpoint {} created".format(b.GetID())):
|
||||||
ibobj = trace.create_object(ibpath)
|
commands.put_single_watchpoint(b, proc)
|
||||||
# Do not use retain_values or it'll remove other locs
|
|
||||||
commands.put_single_watchpoint(b, ibobj, proc, [])
|
|
||||||
ibobj.insert()
|
|
||||||
|
|
||||||
|
|
||||||
def on_watchpoint_modified(b):
|
def on_watchpoint_modified(b):
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
notify_others_watches(proc)
|
|
||||||
if proc.GetProcessID() not in PROC_STATE:
|
if proc.GetProcessID() not in PROC_STATE:
|
||||||
return
|
return
|
||||||
old_count = BRK_STATE.get_brkloc_count(b)
|
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
ibpath = commands.PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID())
|
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Watchpoint {} modified".format(b.GetID())):
|
with trace.open_tx("Watchpoint {} modified".format(b.GetID())):
|
||||||
ibobj = trace.create_object(ibpath)
|
commands.put_single_watchpoint(b, proc)
|
||||||
commands.put_single_watchpoint(b, ibobj, proc, [])
|
|
||||||
|
|
||||||
|
|
||||||
def on_watchpoint_deleted(b):
|
def on_watchpoint_deleted(b):
|
||||||
proc = util.get_process()
|
proc = util.get_process()
|
||||||
notify_others_watches(proc)
|
|
||||||
if proc.GetProcessID() not in PROC_STATE:
|
if proc.GetProcessID() not in PROC_STATE:
|
||||||
return
|
return
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
bpath = commands.WATCHPOINT_PATTERN.format(watchnum=b.GetID())
|
wpt_path = commands.PROC_WATCH_PATTERN.format(
|
||||||
ibobj = trace.proxy_object_path(
|
procnum=proc.GetProcessID(), watchnum=b.GetID())
|
||||||
commands.PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID()))
|
wpt_obj = trace.proxy_object_path(wpt_path)
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("Watchpoint {} deleted".format(b.GetID())):
|
with trace.open_tx("Watchpoint {} deleted".format(b.GetID())):
|
||||||
trace.proxy_object_path(bpath).remove(tree=True)
|
wpt_obj.remove(tree=True)
|
||||||
|
|
||||||
|
|
||||||
def install_hooks():
|
def install_hooks():
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
## ###
|
## ###
|
||||||
# IP: GHIDRA
|
# IP: GHIDRA
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# 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.
|
||||||
##
|
##
|
||||||
from concurrent.futures import Future, ThreadPoolExecutor
|
from concurrent.futures import Future, ThreadPoolExecutor
|
||||||
import re
|
import re
|
||||||
|
@ -33,13 +33,12 @@ def extre(base, ext):
|
||||||
|
|
||||||
|
|
||||||
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
|
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
|
||||||
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
|
|
||||||
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
|
|
||||||
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
|
|
||||||
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
|
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
|
||||||
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
|
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
|
||||||
|
PROC_BREAK_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
|
||||||
|
PROC_BREAKLOC_PATTERN = extre(PROC_BREAK_PATTERN, '\[(?P<locnum>\\d*)\]')
|
||||||
PROC_WATCHES_PATTERN = extre(PROCESS_PATTERN, '\.Watchpoints')
|
PROC_WATCHES_PATTERN = extre(PROCESS_PATTERN, '\.Watchpoints')
|
||||||
PROC_WATCHLOC_PATTERN = extre(PROC_WATCHES_PATTERN, '\[(?P<watchnum>\\d*)\]')
|
PROC_WATCH_PATTERN = extre(PROC_WATCHES_PATTERN, '\[(?P<watchnum>\\d*)\]')
|
||||||
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
|
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
|
||||||
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
|
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
|
||||||
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
|
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
|
||||||
|
@ -183,7 +182,7 @@ def find_bpt_by_pattern(pattern, object, err_msg):
|
||||||
|
|
||||||
|
|
||||||
def find_bpt_by_obj(object):
|
def find_bpt_by_obj(object):
|
||||||
return find_bpt_by_pattern(BREAKPOINT_PATTERN, object, "a BreakpointSpec")
|
return find_bpt_by_pattern(PROC_BREAK_PATTERN, object, "a BreakpointSpec")
|
||||||
|
|
||||||
|
|
||||||
# Oof. no lldb/Python method to get breakpoint by number
|
# Oof. no lldb/Python method to get breakpoint by number
|
||||||
|
@ -206,7 +205,7 @@ def find_wpt_by_pattern(pattern, object, err_msg):
|
||||||
|
|
||||||
|
|
||||||
def find_wpt_by_obj(object):
|
def find_wpt_by_obj(object):
|
||||||
return find_wpt_by_pattern(PROC_WATCHLOC_PATTERN, object, "a WatchpointSpec")
|
return find_wpt_by_pattern(PROC_WATCH_PATTERN, object, "a WatchpointSpec")
|
||||||
|
|
||||||
|
|
||||||
def find_bptlocnum_by_pattern(pattern, object, err_msg):
|
def find_bptlocnum_by_pattern(pattern, object, err_msg):
|
||||||
|
@ -219,7 +218,7 @@ def find_bptlocnum_by_pattern(pattern, object, err_msg):
|
||||||
|
|
||||||
|
|
||||||
def find_bptlocnum_by_obj(object):
|
def find_bptlocnum_by_obj(object):
|
||||||
return find_bptlocnum_by_pattern(BREAK_LOC_PATTERN, object,
|
return find_bptlocnum_by_pattern(PROC_BREAKLOC_PATTERN, object,
|
||||||
"a BreakpointLocation")
|
"a BreakpointLocation")
|
||||||
|
|
||||||
|
|
||||||
|
@ -607,17 +606,20 @@ def break_exception(lang: str):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='toggle')
|
@REGISTRY.method(action='toggle')
|
||||||
def toggle_watchpoint(breakpoint: sch.Schema('WatchpointSpec'), enabled: bool):
|
def toggle_watchpoint(watchpoint: sch.Schema('WatchpointSpec'), enabled: bool):
|
||||||
"""Toggle a watchpoint."""
|
"""Toggle a watchpoint."""
|
||||||
wpt = find_wpt_by_obj(watchpoint)
|
wpt = find_wpt_by_obj(watchpoint)
|
||||||
wpt.enabled = enabled
|
wpt.enabled = enabled
|
||||||
|
cmd = 'enable' if enabled else 'disable'
|
||||||
|
exec_convert_errors(f'watchpoint {cmd} {wpt.GetID()}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='toggle')
|
@REGISTRY.method(action='toggle')
|
||||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||||
"""Toggle a breakpoint."""
|
"""Toggle a breakpoint."""
|
||||||
bpt = find_bpt_by_obj(breakpoint)
|
bpt = find_bpt_by_obj(breakpoint)
|
||||||
bpt.enabled = enabled
|
cmd = 'enable' if enabled else 'disable'
|
||||||
|
exec_convert_errors(f'breakpoint {cmd} {bpt.GetID()}')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='toggle')
|
@REGISTRY.method(action='toggle')
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
<element schema="VOID" />
|
<element schema="VOID" />
|
||||||
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
|
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
|
||||||
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
|
|
||||||
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
|
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
|
||||||
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
|
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
|
||||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||||
|
@ -62,6 +60,7 @@
|
||||||
</schema>
|
</schema>
|
||||||
<schema name="WatchpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
<schema name="WatchpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||||
<interface name="BreakpointSpec" />
|
<interface name="BreakpointSpec" />
|
||||||
|
<interface name="BreakpointLocation" />
|
||||||
<interface name="Togglable" />
|
<interface name="Togglable" />
|
||||||
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
||||||
<attribute-alias from="_expression" to="Expression" />
|
<attribute-alias from="_expression" to="Expression" />
|
||||||
|
@ -93,7 +92,7 @@
|
||||||
<interface name="ExecutionStateful" />
|
<interface name="ExecutionStateful" />
|
||||||
<element schema="VOID" />
|
<element schema="VOID" />
|
||||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" />
|
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
|
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
|
||||||
<attribute name="Exit Code" schema="LONG" />
|
<attribute name="Exit Code" schema="LONG" />
|
||||||
<attribute-alias from="_exit_code" to="Exit Code" />
|
<attribute-alias from="_exit_code" to="Exit Code" />
|
||||||
|
|
|
@ -60,12 +60,6 @@ import ghidra.util.Msg;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class TraceRmiTarget extends AbstractTarget {
|
public class TraceRmiTarget extends AbstractTarget {
|
||||||
private static final String BREAK_HW_EXEC = "breakHwExec";
|
|
||||||
private static final String BREAK_SW_EXEC = "breakSwExec";
|
|
||||||
private static final String BREAK_READ = "breakRead";
|
|
||||||
private static final String BREAK_WRITE = "breakWrite";
|
|
||||||
private static final String BREAK_ACCESS = "breakAccess";
|
|
||||||
|
|
||||||
private final TraceRmiConnection connection;
|
private final TraceRmiConnection connection;
|
||||||
private final Trace trace;
|
private final Trace trace;
|
||||||
|
|
||||||
|
@ -510,14 +504,15 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MethodMatcher {
|
interface MethodMatcher {
|
||||||
default MatchedMethod match(RemoteMethod method, SchemaContext ctx) {
|
default MatchedMethod match(RemoteMethod method, TraceObjectSchema rootSchema,
|
||||||
|
KeyPath path) {
|
||||||
List<ParamSpec> spec = spec();
|
List<ParamSpec> spec = spec();
|
||||||
if (spec.size() != method.parameters().size()) {
|
if (spec.size() != method.parameters().size()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Map<String, RemoteParameter> found = new HashMap<>();
|
Map<String, RemoteParameter> found = new HashMap<>();
|
||||||
for (ParamSpec ps : spec) {
|
for (ParamSpec ps : spec) {
|
||||||
RemoteParameter param = ps.find(method, ctx);
|
RemoteParameter param = ps.find(method, rootSchema, path);
|
||||||
if (param == null) {
|
if (param == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -530,10 +525,10 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
int score();
|
int score();
|
||||||
|
|
||||||
static MatchedMethod matchPreferredForm(RemoteMethod method, SchemaContext ctx,
|
static MatchedMethod matchPreferredForm(RemoteMethod method, TraceObjectSchema rootSchema,
|
||||||
List<? extends MethodMatcher> preferred) {
|
KeyPath path, List<? extends MethodMatcher> preferred) {
|
||||||
return preferred.stream()
|
return preferred.stream()
|
||||||
.map(m -> m.match(method, ctx))
|
.map(m -> m.match(method, rootSchema, path))
|
||||||
.filter(m -> m != null)
|
.filter(m -> m != null)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
@ -549,7 +544,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean typeMatches(RemoteMethod method, RemoteParameter param,
|
protected static boolean typeMatches(RemoteMethod method, RemoteParameter param,
|
||||||
SchemaContext ctx, Class<?> type) {
|
TraceObjectSchema rootSchema, KeyPath path, Class<?> type) {
|
||||||
|
SchemaContext ctx = rootSchema.getContext();
|
||||||
TraceObjectSchema sch = ctx.getSchemaOrNull(param.type());
|
TraceObjectSchema sch = ctx.getSchemaOrNull(param.type());
|
||||||
if (sch == null) {
|
if (sch == null) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
|
@ -561,7 +557,15 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
return sch == PrimitiveTraceObjectSchema.OBJECT;
|
return sch == PrimitiveTraceObjectSchema.OBJECT;
|
||||||
}
|
}
|
||||||
else if (TraceObjectInterface.class.isAssignableFrom(type)) {
|
else if (TraceObjectInterface.class.isAssignableFrom(type)) {
|
||||||
return sch.getInterfaces().contains(type);
|
if (path == null) {
|
||||||
|
return sch.getInterfaces().contains(type);
|
||||||
|
}
|
||||||
|
KeyPath found =
|
||||||
|
rootSchema.searchForSuitable(type.asSubclass(TraceObjectInterface.class), path);
|
||||||
|
if (found == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return sch == rootSchema.getSuccessorSchema(path);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return sch.getType() == type;
|
return sch.getType() == type;
|
||||||
|
@ -571,12 +575,13 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
interface ParamSpec {
|
interface ParamSpec {
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
RemoteParameter find(RemoteMethod method, SchemaContext ctx);
|
RemoteParameter find(RemoteMethod method, TraceObjectSchema rootSchema, KeyPath path);
|
||||||
}
|
}
|
||||||
|
|
||||||
record SchemaParamSpec(String name, SchemaName schema) implements ParamSpec {
|
record SchemaParamSpec(String name, SchemaName schema) implements ParamSpec {
|
||||||
@Override
|
@Override
|
||||||
public RemoteParameter find(RemoteMethod method, SchemaContext ctx) {
|
public RemoteParameter find(RemoteMethod method, TraceObjectSchema rootSchema,
|
||||||
|
KeyPath path) {
|
||||||
List<RemoteParameter> withType = method.parameters()
|
List<RemoteParameter> withType = method.parameters()
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -591,11 +596,12 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
record TypeParamSpec(String name, Class<?> type) implements ParamSpec {
|
record TypeParamSpec(String name, Class<?> type) implements ParamSpec {
|
||||||
@Override
|
@Override
|
||||||
public RemoteParameter find(RemoteMethod method, SchemaContext ctx) {
|
public RemoteParameter find(RemoteMethod method, TraceObjectSchema rootSchema,
|
||||||
|
KeyPath path) {
|
||||||
List<RemoteParameter> withType = method.parameters()
|
List<RemoteParameter> withType = method.parameters()
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(p -> typeMatches(method, p, ctx, type))
|
.filter(p -> typeMatches(method, p, rootSchema, path, type))
|
||||||
.toList();
|
.toList();
|
||||||
if (withType.size() != 1) {
|
if (withType.size() != 1) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -606,9 +612,10 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
record NameParamSpec(String name, Class<?> type) implements ParamSpec {
|
record NameParamSpec(String name, Class<?> type) implements ParamSpec {
|
||||||
@Override
|
@Override
|
||||||
public RemoteParameter find(RemoteMethod method, SchemaContext ctx) {
|
public RemoteParameter find(RemoteMethod method, TraceObjectSchema rootSchema,
|
||||||
|
KeyPath path) {
|
||||||
RemoteParameter param = method.parameters().get(name);
|
RemoteParameter param = method.parameters().get(name);
|
||||||
if (param != null && typeMatches(method, param, ctx, type)) {
|
if (param != null && typeMatches(method, param, rootSchema, path, type)) {
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -802,30 +809,63 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
static final List<ToggleBreakMatcher> SPEC = matchers(HAS_SPEC);
|
static final List<ToggleBreakMatcher> SPEC = matchers(HAS_SPEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class Matches {
|
record MatchKey(Class<? extends MethodMatcher> cls, ActionName action, TraceObjectSchema sch) {}
|
||||||
private final Map<String, MatchedMethod> map = new HashMap<>();
|
|
||||||
|
|
||||||
public MatchedMethod getBest(String name, ActionName action,
|
protected class Matches {
|
||||||
Supplier<List<? extends MethodMatcher>> preferredSupplier) {
|
private final Map<MatchKey, MatchedMethod> map = new HashMap<>();
|
||||||
return getBest(name, action, preferredSupplier.get());
|
|
||||||
|
public MatchKey makeKey(Class<? extends MethodMatcher> cls, ActionName action,
|
||||||
|
KeyPath path) {
|
||||||
|
TraceObjectSchema rootSchema = trace.getObjectManager().getRootSchema();
|
||||||
|
if (rootSchema == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new MatchKey(cls, action,
|
||||||
|
path == null ? null : rootSchema.getSuccessorSchema(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MatchedMethod getBest(String name, ActionName action,
|
public <T extends MethodMatcher> MatchedMethod getBest(Class<T> cls, KeyPath path,
|
||||||
List<? extends MethodMatcher> preferred) {
|
ActionName action, Supplier<List<T>> preferredSupplier) {
|
||||||
|
return getBest(cls, path, action, preferredSupplier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the most preferred method for a given operation, with respect to a given path
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* A given path should be given as a point of reference, usually the current object or the
|
||||||
|
* object from the UI action context. If given, parameters that require a certain
|
||||||
|
* {@link TraceObjectInterface} will seek a suitable schema from that path and require it.
|
||||||
|
* Otherwise, any parameter whose schema includes the interface will be accepted.
|
||||||
|
*
|
||||||
|
* @param <T> the matcher class representing the desired operation
|
||||||
|
* @param cls the matcher class representing the desired operation
|
||||||
|
* @param path a path as a point of reference, or null for "any" point of reference.
|
||||||
|
* @param action the required action name for a matching method
|
||||||
|
* @param preferred the list of matchers (signatures) in preferred order
|
||||||
|
* @return the best method, or null
|
||||||
|
*/
|
||||||
|
public <T extends MethodMatcher> MatchedMethod getBest(Class<T> cls, KeyPath path,
|
||||||
|
ActionName action, List<T> preferred) {
|
||||||
|
MatchKey key = makeKey(cls, action, path);
|
||||||
synchronized (map) {
|
synchronized (map) {
|
||||||
return map.computeIfAbsent(name, n -> chooseBest(action, preferred));
|
return map.computeIfAbsent(key, k -> chooseBest(action, path, preferred));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MatchedMethod chooseBest(ActionName name, List<? extends MethodMatcher> preferred) {
|
private MatchedMethod chooseBest(ActionName name, KeyPath path,
|
||||||
|
List<? extends MethodMatcher> preferred) {
|
||||||
if (preferred.isEmpty()) {
|
if (preferred.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
SchemaContext ctx = getSchemaContext();
|
TraceObjectSchema rootSchema = trace.getObjectManager().getRootSchema();
|
||||||
|
if (rootSchema == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
MatchedMethod best = connection.getMethods()
|
MatchedMethod best = connection.getMethods()
|
||||||
.getByAction(name)
|
.getByAction(name)
|
||||||
.stream()
|
.stream()
|
||||||
.map(m -> MethodMatcher.matchPreferredForm(m, ctx, preferred))
|
.map(m -> MethodMatcher.matchPreferredForm(m, rootSchema, path, preferred))
|
||||||
.filter(f -> f != null)
|
.filter(f -> f != null)
|
||||||
.max(MatchedMethod::compareTo)
|
.max(MatchedMethod::compareTo)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
@ -902,7 +942,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<String> executeAsync(String command, boolean toString) {
|
public CompletableFuture<String> executeAsync(String command, boolean toString) {
|
||||||
MatchedMethod execute = matches.getBest("execute", ActionName.EXECUTE, ExecuteMatcher.ALL);
|
MatchedMethod execute =
|
||||||
|
matches.getBest(ExecuteMatcher.class, null, ActionName.EXECUTE, ExecuteMatcher.ALL);
|
||||||
if (execute == null) {
|
if (execute == null) {
|
||||||
return CompletableFuture.failedFuture(new NoSuchElementException());
|
return CompletableFuture.failedFuture(new NoSuchElementException());
|
||||||
}
|
}
|
||||||
|
@ -923,10 +964,10 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
|
||||||
SchemaName name = object.getSchema().getName();
|
MatchedMethod activate =
|
||||||
MatchedMethod activate = matches.getBest("activate_" + name, ActionName.ACTIVATE,
|
matches.getBest(ActivateMatcher.class, object.getCanonicalPath(), ActionName.ACTIVATE,
|
||||||
() -> ActivateMatcher.makeBySpecificity(trace.getObjectManager().getRootSchema(),
|
() -> ActivateMatcher.makeBySpecificity(trace.getObjectManager().getRootSchema(),
|
||||||
object.getCanonicalPath()));
|
object.getCanonicalPath()));
|
||||||
if (activate == null) {
|
if (activate == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1004,7 +1045,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
// NOTE: I don't intend to warn about the number of requests.
|
// NOTE: I don't intend to warn about the number of requests.
|
||||||
// They're delivered in serial, and there's a cancel button that works
|
// They're delivered in serial, and there's a cancel button that works
|
||||||
|
|
||||||
MatchedMethod readMem = matches.getBest("readMem", ActionName.READ_MEM, ReadMemMatcher.ALL);
|
MatchedMethod readMem =
|
||||||
|
matches.getBest(ReadMemMatcher.class, null, ActionName.READ_MEM, ReadMemMatcher.ALL);
|
||||||
if (readMem == null) {
|
if (readMem == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1066,7 +1108,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> writeMemoryAsync(Address address, byte[] data) {
|
public CompletableFuture<Void> writeMemoryAsync(Address address, byte[] data) {
|
||||||
MatchedMethod writeMem =
|
MatchedMethod writeMem =
|
||||||
matches.getBest("writeMem", ActionName.WRITE_MEM, WriteMemMatcher.ALL);
|
matches.getBest(WriteMemMatcher.class, null, ActionName.WRITE_MEM, WriteMemMatcher.ALL);
|
||||||
if (writeMem == null) {
|
if (writeMem == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1088,7 +1130,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
public CompletableFuture<Void> readRegistersAsync(TracePlatform platform, TraceThread thread,
|
public CompletableFuture<Void> readRegistersAsync(TracePlatform platform, TraceThread thread,
|
||||||
int frame, Set<Register> registers) {
|
int frame, Set<Register> registers) {
|
||||||
MatchedMethod readRegs =
|
MatchedMethod readRegs =
|
||||||
matches.getBest("readRegs", ActionName.REFRESH, ReadRegsMatcher.ALL);
|
matches.getBest(ReadRegsMatcher.class, null, ActionName.REFRESH, ReadRegsMatcher.ALL);
|
||||||
if (readRegs == null) {
|
if (readRegs == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1218,7 +1260,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
public CompletableFuture<Void> writeRegisterAsync(TracePlatform platform, TraceThread thread,
|
public CompletableFuture<Void> writeRegisterAsync(TracePlatform platform, TraceThread thread,
|
||||||
int frameLevel, RegisterValue value) {
|
int frameLevel, RegisterValue value) {
|
||||||
MatchedMethod writeReg =
|
MatchedMethod writeReg =
|
||||||
matches.getBest("writeReg", ActionName.WRITE_REG, WriteRegMatcher.ALL);
|
matches.getBest(WriteRegMatcher.class, null, ActionName.WRITE_REG, WriteRegMatcher.ALL);
|
||||||
if (writeReg == null) {
|
if (writeReg == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1371,8 +1413,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected CompletableFuture<Void> placeHwExecBreakAsync(Address address, String condition,
|
protected CompletableFuture<Void> placeHwExecBreakAsync(Address address, String condition,
|
||||||
String commands) {
|
String commands) {
|
||||||
MatchedMethod breakHwExec =
|
MatchedMethod breakHwExec = matches.getBest(BreakExecMatcher.class, null,
|
||||||
matches.getBest(BREAK_HW_EXEC, ActionName.BREAK_HW_EXECUTE, BreakExecMatcher.ALL);
|
ActionName.BREAK_HW_EXECUTE, BreakExecMatcher.ALL);
|
||||||
if (breakHwExec == null) {
|
if (breakHwExec == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1381,8 +1423,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected CompletableFuture<Void> placeSwExecBreakAsync(Address address, String condition,
|
protected CompletableFuture<Void> placeSwExecBreakAsync(Address address, String condition,
|
||||||
String commands) {
|
String commands) {
|
||||||
MatchedMethod breakSwExec =
|
MatchedMethod breakSwExec = matches.getBest(BreakExecMatcher.class, null,
|
||||||
matches.getBest(BREAK_SW_EXEC, ActionName.BREAK_SW_EXECUTE, BreakExecMatcher.ALL);
|
ActionName.BREAK_SW_EXECUTE, BreakExecMatcher.ALL);
|
||||||
if (breakSwExec == null) {
|
if (breakSwExec == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1399,8 +1441,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected CompletableFuture<Void> placeReadBreakAsync(AddressRange range, String condition,
|
protected CompletableFuture<Void> placeReadBreakAsync(AddressRange range, String condition,
|
||||||
String commands) {
|
String commands) {
|
||||||
MatchedMethod breakRead =
|
MatchedMethod breakRead = matches.getBest(BreakAccMatcher.class, null,
|
||||||
matches.getBest(BREAK_READ, ActionName.BREAK_READ, BreakAccMatcher.ALL);
|
ActionName.BREAK_READ, BreakAccMatcher.ALL);
|
||||||
if (breakRead == null) {
|
if (breakRead == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1409,8 +1451,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected CompletableFuture<Void> placeWriteBreakAsync(AddressRange range, String condition,
|
protected CompletableFuture<Void> placeWriteBreakAsync(AddressRange range, String condition,
|
||||||
String commands) {
|
String commands) {
|
||||||
MatchedMethod breakWrite =
|
MatchedMethod breakWrite = matches.getBest(BreakAccMatcher.class, null,
|
||||||
matches.getBest(BREAK_WRITE, ActionName.BREAK_WRITE, BreakAccMatcher.ALL);
|
ActionName.BREAK_WRITE, BreakAccMatcher.ALL);
|
||||||
if (breakWrite == null) {
|
if (breakWrite == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1419,8 +1461,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected CompletableFuture<Void> placeAccessBreakAsync(AddressRange range, String condition,
|
protected CompletableFuture<Void> placeAccessBreakAsync(AddressRange range, String condition,
|
||||||
String commands) {
|
String commands) {
|
||||||
MatchedMethod breakAccess =
|
MatchedMethod breakAccess = matches.getBest(BreakAccMatcher.class, null,
|
||||||
matches.getBest(BREAK_ACCESS, ActionName.BREAK_ACCESS, BreakAccMatcher.ALL);
|
ActionName.BREAK_ACCESS, BreakAccMatcher.ALL);
|
||||||
if (breakAccess == null) {
|
if (breakAccess == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1484,15 +1526,16 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
if (breakpoint.getName().endsWith("emu-" + breakpoint.getMinAddress())) {
|
if (breakpoint.getName().endsWith("emu-" + breakpoint.getMinAddress())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!breakpoint.getLifespan().contains(getSnap())) {
|
if (!breakpoint.isAlive(getSnap())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompletableFuture<Void> deleteBreakpointSpecAsync(TraceObjectBreakpointSpec spec) {
|
protected CompletableFuture<Void> deleteBreakpointSpecAsync(TraceObjectBreakpointSpec spec) {
|
||||||
|
KeyPath path = spec.getObject().getCanonicalPath();
|
||||||
MatchedMethod delBreak =
|
MatchedMethod delBreak =
|
||||||
matches.getBest("delBreakSpec", ActionName.DELETE, DelBreakMatcher.SPEC);
|
matches.getBest(DelBreakMatcher.class, path, ActionName.DELETE, DelBreakMatcher.SPEC);
|
||||||
if (delBreak == null) {
|
if (delBreak == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
@ -1504,8 +1547,9 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
// TODO: Would this make sense for any debugger? To delete individual locations?
|
// TODO: Would this make sense for any debugger? To delete individual locations?
|
||||||
protected CompletableFuture<Void> deleteBreakpointLocAsync(TraceObjectBreakpointLocation loc) {
|
protected CompletableFuture<Void> deleteBreakpointLocAsync(TraceObjectBreakpointLocation loc) {
|
||||||
|
KeyPath path = loc.getObject().getCanonicalPath();
|
||||||
MatchedMethod delBreak =
|
MatchedMethod delBreak =
|
||||||
matches.getBest("delBreakLoc", ActionName.DELETE, DelBreakMatcher.ALL);
|
matches.getBest(DelBreakMatcher.class, path, ActionName.DELETE, DelBreakMatcher.ALL);
|
||||||
if (delBreak == null) {
|
if (delBreak == null) {
|
||||||
Msg.debug(this, "Falling back to delete spec");
|
Msg.debug(this, "Falling back to delete spec");
|
||||||
return deleteBreakpointSpecAsync(loc.getSpecification());
|
return deleteBreakpointSpecAsync(loc.getSpecification());
|
||||||
|
@ -1533,33 +1577,37 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
protected CompletableFuture<Void> toggleBreakpointSpecAsync(TraceObjectBreakpointSpec spec,
|
protected CompletableFuture<Void> toggleBreakpointSpecAsync(TraceObjectBreakpointSpec spec,
|
||||||
boolean enabled) {
|
boolean enabled) {
|
||||||
MatchedMethod delBreak =
|
KeyPath path = spec.getObject().getCanonicalPath();
|
||||||
matches.getBest("toggleBreakSpec", ActionName.TOGGLE, ToggleBreakMatcher.SPEC);
|
MatchedMethod toggleBreak =
|
||||||
if (delBreak == null) {
|
matches.getBest(ToggleBreakMatcher.class, path, ActionName.TOGGLE,
|
||||||
|
ToggleBreakMatcher.SPEC);
|
||||||
|
if (toggleBreak == null) {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
return delBreak.method
|
return toggleBreak.method
|
||||||
.invokeAsync(Map.ofEntries(
|
.invokeAsync(Map.ofEntries(
|
||||||
Map.entry(delBreak.params.get("specification").name(), spec.getObject()),
|
Map.entry(toggleBreak.params.get("specification").name(), spec.getObject()),
|
||||||
Map.entry(delBreak.params.get("enabled").name(), enabled)))
|
Map.entry(toggleBreak.params.get("enabled").name(), enabled)))
|
||||||
.toCompletableFuture()
|
.toCompletableFuture()
|
||||||
.thenApply(__ -> null);
|
.thenApply(__ -> null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompletableFuture<Void> toggleBreakpointLocAsync(TraceObjectBreakpointLocation loc,
|
protected CompletableFuture<Void> toggleBreakpointLocAsync(TraceObjectBreakpointLocation loc,
|
||||||
boolean enabled) {
|
boolean enabled) {
|
||||||
MatchedMethod delBreak =
|
KeyPath path = loc.getObject().getCanonicalPath();
|
||||||
matches.getBest("toggleBreakLoc", ActionName.TOGGLE, ToggleBreakMatcher.ALL);
|
MatchedMethod toggleBreak =
|
||||||
if (delBreak == null) {
|
matches.getBest(ToggleBreakMatcher.class, path, ActionName.TOGGLE,
|
||||||
|
ToggleBreakMatcher.ALL);
|
||||||
|
if (toggleBreak == null) {
|
||||||
Msg.debug(this, "Falling back to toggle spec");
|
Msg.debug(this, "Falling back to toggle spec");
|
||||||
return toggleBreakpointSpecAsync(loc.getSpecification(), enabled);
|
return toggleBreakpointSpecAsync(loc.getSpecification(), enabled);
|
||||||
}
|
}
|
||||||
RemoteParameter paramLocation = delBreak.params.get("location");
|
RemoteParameter paramLocation = toggleBreak.params.get("location");
|
||||||
if (paramLocation != null) {
|
if (paramLocation != null) {
|
||||||
return delBreak.method
|
return toggleBreak.method
|
||||||
.invokeAsync(Map.ofEntries(
|
.invokeAsync(Map.ofEntries(
|
||||||
Map.entry(paramLocation.name(), loc.getObject()),
|
Map.entry(paramLocation.name(), loc.getObject()),
|
||||||
Map.entry(delBreak.params.get("enabled").name(), enabled)))
|
Map.entry(toggleBreak.params.get("enabled").name(), enabled)))
|
||||||
.toCompletableFuture()
|
.toCompletableFuture()
|
||||||
.thenApply(__ -> null);
|
.thenApply(__ -> null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -623,8 +623,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
private boolean isVisible(TraceBreakpoint location) {
|
private boolean isVisible(TraceBreakpoint location) {
|
||||||
long snap = traceManager.getCurrentFor(trace).getSnap();
|
long snap = traceManager.getCurrentFor(trace).getSnap();
|
||||||
Lifespan span = location.getLifespan();
|
return location.isAlive(snap);
|
||||||
return span != null && span.contains(snap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void locationAdded(TraceBreakpoint location) {
|
private void locationAdded(TraceBreakpoint location) {
|
||||||
|
|
|
@ -709,7 +709,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
if (forType != null) {
|
if (forType != null) {
|
||||||
return forType;
|
return forType;
|
||||||
}
|
}
|
||||||
if (type.contains("Breakpoint")) {
|
if (type.contains("Breakpoint") || type.contains("Watchpoint")) {
|
||||||
TraceObject object = edge.getChild();
|
TraceObject object = edge.getChild();
|
||||||
TraceObjectValue en =
|
TraceObjectValue en =
|
||||||
object.getAttribute(snap, TraceObjectTogglable.KEY_ENABLED);
|
object.getAttribute(snap, TraceObjectTogglable.KEY_ENABLED);
|
||||||
|
|
|
@ -219,8 +219,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
private void breakpointAdded(TraceBreakpoint tb) {
|
private void breakpointAdded(TraceBreakpoint tb) {
|
||||||
Lifespan span = tb.getLifespan();
|
if (!tb.isAlive(info.snap)) {
|
||||||
if (span == null || !span.contains(info.snap)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -233,7 +232,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
private void breakpointChanged(TraceBreakpoint tb) {
|
private void breakpointChanged(TraceBreakpoint tb) {
|
||||||
if (!tb.getLifespan().contains(info.snap)) {
|
if (!tb.isAlive(info.snap)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -521,7 +520,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||||
forgetTraceBreakpoint(r, tb);
|
forgetTraceBreakpoint(r, tb);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!tb.getLifespan().contains(snap)) {
|
if (!tb.isAlive(snap)) {
|
||||||
forgetTraceBreakpoint(r, tb);
|
forgetTraceBreakpoint(r, tb);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -272,6 +272,13 @@ public class DBTraceBreakpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlive(long snap) {
|
||||||
|
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
|
||||||
|
return lifespan.contains(snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPlacedSnap() {
|
public long getPlacedSnap() {
|
||||||
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
|
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
|
||||||
|
|
|
@ -196,6 +196,13 @@ public class DBTraceObjectBreakpointLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlive(long snap) {
|
||||||
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
|
return object.isAlive(snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lifespan computeSpan() {
|
public Lifespan computeSpan() {
|
||||||
Lifespan span = TraceObjectBreakpointLocation.super.computeSpan();
|
Lifespan span = TraceObjectBreakpointLocation.super.computeSpan();
|
||||||
|
|
|
@ -106,6 +106,13 @@ public class DBTraceObjectBreakpointSpec
|
||||||
return computeSpan();
|
return computeSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlive(long snap) {
|
||||||
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
|
return object.isAlive(snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPlacedSnap() {
|
public long getPlacedSnap() {
|
||||||
return computeMinSnap();
|
return computeMinSnap();
|
||||||
|
|
|
@ -163,23 +163,36 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected LifeSet ensureCachedLife() {
|
||||||
|
if (cachedLife != null) {
|
||||||
|
return cachedLife;
|
||||||
|
}
|
||||||
|
MutableLifeSet result = new DefaultLifeSet();
|
||||||
|
getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan()));
|
||||||
|
cachedLife = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifeSet getLife() {
|
public LifeSet getLife() {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
if (cachedLife != null) {
|
LifeSet result = ensureCachedLife();
|
||||||
synchronized (cachedLife) {
|
|
||||||
return DefaultLifeSet.copyOf(cachedLife);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MutableLifeSet result = new DefaultLifeSet();
|
|
||||||
getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan()));
|
|
||||||
cachedLife = result;
|
|
||||||
synchronized (result) {
|
synchronized (result) {
|
||||||
return DefaultLifeSet.copyOf(result);
|
return DefaultLifeSet.copyOf(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlive(long snap) {
|
||||||
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
|
LifeSet result = ensureCachedLife();
|
||||||
|
synchronized (result) {
|
||||||
|
return result.contains(snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected DBTraceObject doCreateCanonicalParentObject() {
|
protected DBTraceObject doCreateCanonicalParentObject() {
|
||||||
return manager.doCreateObject(path.parent());
|
return manager.doCreateObject(path.parent());
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -23,6 +23,7 @@ import ghidra.pcode.exec.SleighUtils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
|
||||||
|
@ -105,9 +106,21 @@ public interface TraceBreakpoint extends TraceUniqueObject {
|
||||||
* Get the lifespan of this breakpoint
|
* Get the lifespan of this breakpoint
|
||||||
*
|
*
|
||||||
* @return the lifespan
|
* @return the lifespan
|
||||||
|
* @deprecated Either this method no longer makes sense, or we need to wrap a
|
||||||
|
* {@link TraceObjectValue} instead. Even then, the attribute values can vary over
|
||||||
|
* the lifespan.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "11.3", forRemoval = true)
|
||||||
Lifespan getLifespan();
|
Lifespan getLifespan();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the breakpoint is present at the given snap
|
||||||
|
*
|
||||||
|
* @param snap the snap
|
||||||
|
* @return true if alive, false if not
|
||||||
|
*/
|
||||||
|
boolean isAlive(long snap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the placed snap of this breakpoint
|
* Get the placed snap of this breakpoint
|
||||||
*
|
*
|
||||||
|
|
|
@ -180,6 +180,17 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
*/
|
*/
|
||||||
LifeSet getLife();
|
LifeSet getLife();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the object is alive at the given snap
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is preferable to {@link #getLife()}, when we only need to check one snap
|
||||||
|
*
|
||||||
|
* @param snap the snap
|
||||||
|
* @return true if alive, false if not
|
||||||
|
*/
|
||||||
|
boolean isAlive(long snap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts this object at its canonical path for the given lifespan
|
* Inserts this object at its canonical path for the given lifespan
|
||||||
*
|
*
|
||||||
|
|
|
@ -497,18 +497,23 @@ public interface TraceObjectSchema {
|
||||||
public void nextLevel() {
|
public void nextLevel() {
|
||||||
Set<T> nextLevel = new HashSet<>();
|
Set<T> nextLevel = new HashSet<>();
|
||||||
for (T ent : allOnLevel) {
|
for (T ent : allOnLevel) {
|
||||||
if (!descend(ent)) {
|
if (descendAttributes(ent)) {
|
||||||
continue;
|
expandAttributes(nextLevel, ent);
|
||||||
|
expandDefaultAttribute(nextLevel, ent);
|
||||||
|
}
|
||||||
|
if (descendElements(ent)) {
|
||||||
|
expandElements(nextLevel, ent);
|
||||||
|
expandDefaultElement(nextLevel, ent);
|
||||||
}
|
}
|
||||||
expandAttributes(nextLevel, ent);
|
|
||||||
expandDefaultAttribute(nextLevel, ent);
|
|
||||||
expandElements(nextLevel, ent);
|
|
||||||
expandDefaultElement(nextLevel, ent);
|
|
||||||
}
|
}
|
||||||
allOnLevel = nextLevel;
|
allOnLevel = nextLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean descend(T ent) {
|
public boolean descendAttributes(T ent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean descendElements(T ent) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,10 +554,15 @@ public interface TraceObjectSchema {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean descend(SearchEntry ent) {
|
public boolean descendAttributes(SearchEntry ent) {
|
||||||
return ent.schema.getInterfaces().contains(TraceObjectAggregate.class);
|
return ent.schema.getInterfaces().contains(TraceObjectAggregate.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean descendElements(SearchEntry ent) {
|
||||||
|
return ent.schema.isCanonicalContainer();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expandAttribute(Set<SearchEntry> nextLevel, SearchEntry ent,
|
public void expandAttribute(Set<SearchEntry> nextLevel, SearchEntry ent,
|
||||||
TraceObjectSchema schema, KeyPath path) {
|
TraceObjectSchema schema, KeyPath path) {
|
||||||
|
|
|
@ -16,12 +16,15 @@
|
||||||
package ghidra.dbg.target.schema;
|
package ghidra.dbg.target.schema;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.jdom.JDOMException;
|
import org.jdom.JDOMException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation;
|
||||||
|
import ghidra.trace.model.target.path.KeyPath;
|
||||||
import ghidra.trace.model.target.schema.*;
|
import ghidra.trace.model.target.schema.*;
|
||||||
import ghidra.trace.model.target.schema.DefaultTraceObjectSchema.DefaultAttributeSchema;
|
import ghidra.trace.model.target.schema.DefaultTraceObjectSchema.DefaultAttributeSchema;
|
||||||
import ghidra.trace.model.target.schema.TraceObjectSchema.*;
|
import ghidra.trace.model.target.schema.TraceObjectSchema.*;
|
||||||
|
@ -75,4 +78,35 @@ public class XmlTargetObjectSchemaTest {
|
||||||
SchemaContext result = XmlSchemaContext.deserialize(SCHEMA_XML);
|
SchemaContext result = XmlSchemaContext.deserialize(SCHEMA_XML);
|
||||||
assertEquals(CTX, result);
|
assertEquals(CTX, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithMultipleImpls() throws Exception {
|
||||||
|
SchemaContext ctx = XmlSchemaContext.deserialize("""
|
||||||
|
<context>
|
||||||
|
<schema name="root">
|
||||||
|
<interface name="Aggregate" />
|
||||||
|
<attribute name="Watches" schema="WatchContainer" />
|
||||||
|
<attribute name="Breaks" schema="BreakContainer" />
|
||||||
|
</schema>
|
||||||
|
<schema name="WatchContainer" canonical="yes">
|
||||||
|
<element schema="Watch" />
|
||||||
|
</schema>
|
||||||
|
<schema name="Watch">
|
||||||
|
<interface name="BreakpointSpec" />
|
||||||
|
<interface name="BreakpointLocation" />
|
||||||
|
</schema>
|
||||||
|
<schema name="BreakContainer" canonical="yes">
|
||||||
|
<element schema="Break" />
|
||||||
|
</schema>
|
||||||
|
<schema name="Break">
|
||||||
|
<interface name="BreakpointSpec" />
|
||||||
|
<interface name="BreakpointLocation" />
|
||||||
|
</schema>
|
||||||
|
</context>
|
||||||
|
""");
|
||||||
|
|
||||||
|
KeyPath found = ctx.getSchema(new SchemaName("root"))
|
||||||
|
.searchForSuitable(TraceObjectBreakpointLocation.class, KeyPath.ROOT);
|
||||||
|
assertNotNull(found);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue