GP-5271: Fix break/watchpoints with lldb. Many framework changes.

This commit is contained in:
Dan 2025-01-17 07:58:01 -05:00
parent 3bfcb8695f
commit 2a41c8fe6b
16 changed files with 335 additions and 260 deletions

View file

@ -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

View file

@ -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():

View file

@ -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')

View file

@ -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" />

View file

@ -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);
} }

View file

@ -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) {

View file

@ -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);

View file

@ -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;
} }

View file

@ -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())) {

View file

@ -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();

View file

@ -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();

View file

@ -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());
} }

View file

@ -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
* *

View file

@ -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
* *

View file

@ -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) {

View file

@ -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);
}
} }