From 2a41c8fe6b328e6cbeeaa830c2c8e6c2cb30f8be Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 17 Jan 2025 07:58:01 -0500 Subject: [PATCH] GP-5271: Fix break/watchpoints with lldb. Many framework changes. --- .../src/main/py/src/ghidralldb/commands.py | 118 ++++++------ .../src/main/py/src/ghidralldb/hooks.py | 92 ++------- .../src/main/py/src/ghidralldb/methods.py | 46 ++--- .../src/main/py/src/ghidralldb/schema.xml | 5 +- .../service/tracermi/TraceRmiTarget.java | 176 +++++++++++------- .../DebuggerBreakpointsProvider.java | 7 +- .../core/debug/gui/model/ObjectTreeModel.java | 2 +- ...ebuggerLogicalBreakpointServicePlugin.java | 7 +- .../breakpoint/DBTraceBreakpoint.java | 11 +- .../DBTraceObjectBreakpointLocation.java | 7 + .../DBTraceObjectBreakpointSpec.java | 7 + .../trace/database/target/DBTraceObject.java | 29 ++- .../model/breakpoint/TraceBreakpoint.java | 17 +- .../trace/model/target/TraceObject.java | 11 ++ .../target/schema/TraceObjectSchema.java | 26 ++- .../schema/XmlTargetObjectSchemaTest.java | 34 ++++ 16 files changed, 335 insertions(+), 260 deletions(-) diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py index bebc60f387..dc2c207c0a 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/commands.py @@ -42,20 +42,16 @@ DEFAULT_REGISTER_BANK = "General Purpose Registers" AVAILABLES_PATH = 'Available' AVAILABLE_KEY_PATTERN = '[{pid}]' AVAILABLE_PATTERN = AVAILABLES_PATH + AVAILABLE_KEY_PATTERN -BREAKPOINTS_PATH = 'Breakpoints' BREAKPOINT_KEY_PATTERN = '[{breaknum}]' -BREAKPOINT_PATTERN = BREAKPOINTS_PATH + BREAKPOINT_KEY_PATTERN -WATCHPOINTS_PATH = 'Watchpoints' WATCHPOINT_KEY_PATTERN = '[{watchnum}]' -WATCHPOINT_PATTERN = WATCHPOINTS_PATH + WATCHPOINT_KEY_PATTERN BREAK_LOC_KEY_PATTERN = '[{locnum}]' PROCESSES_PATH = 'Processes' PROCESS_KEY_PATTERN = '[{procnum}]' PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN 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_BREAK_KEY_PATTERN = '[{breaknum}.{locnum}]' +PROC_BREAK_PATTERN = PROC_BREAKS_PATTERN + BREAKPOINT_KEY_PATTERN ENV_PATTERN = PROCESS_PATTERN + '.Environment' THREADS_PATTERN = PROCESS_PATTERN + '.Threads' THREAD_KEY_PATTERN = '[{tnum}]' @@ -1474,108 +1470,102 @@ def ghidra_trace_put_available(debugger, command, result, internal_dict): put_available() -def put_single_breakpoint(b, ibobj, proc, ikeys): +def put_single_breakpoint(b, proc): mapper = STATE.trace.memory_mapper - bpath = BREAKPOINT_PATTERN.format(breaknum=b.GetID()) - brkobj = STATE.trace.create_object(bpath) + bpt_path = PROC_BREAK_PATTERN.format( + procnum=proc.GetProcessID(), breaknum=b.GetID()) + bpt_obj = STATE.trace.create_object(bpt_path) if b.IsHardware(): - brkobj.set_value('Expression', util.get_description(b)) - brkobj.set_value('Kinds', 'HW_EXECUTE') + bpt_obj.set_value('Expression', util.get_description(b)) + bpt_obj.set_value('Kinds', 'HW_EXECUTE') else: - brkobj.set_value('Expression', util.get_description(b)) - brkobj.set_value('Kinds', 'SW_EXECUTE') + bpt_obj.set_value('Expression', util.get_description(b)) + bpt_obj.set_value('Kinds', 'SW_EXECUTE') cmdList = lldb.SBStringList() if b.GetCommandLineCommands(cmdList): list = [] for i in range(0, cmdList.GetSize()): list.append(cmdList.GetStringAtIndex(i)) - brkobj.set_value('Commands', list) + bpt_obj.set_value('Commands', list) if b.GetCondition(): - brkobj.set_value('Condition', b.GetCondition()) - brkobj.set_value('Hit Count', b.GetHitCount()) - brkobj.set_value('Ignore Count', b.GetIgnoreCount()) - brkobj.set_value('Temporary', b.IsOneShot()) - brkobj.set_value('Enabled', b.IsEnabled()) - keys = [] + bpt_obj.set_value('Condition', b.GetCondition()) + bpt_obj.set_value('Hit Count', b.GetHitCount()) + bpt_obj.set_value('Ignore Count', b.GetIgnoreCount()) + bpt_obj.set_value('Temporary', b.IsOneShot()) + bpt_obj.set_value('Enabled', b.IsEnabled()) + loc_keys = [] locs = util.BREAKPOINT_LOCATION_INFO_READER.get_locations(b) - hooks.BRK_STATE.update_brkloc_count(b, len(locs)) for i, l in enumerate(locs): # Retain the key, even if not for this process k = BREAK_LOC_KEY_PATTERN.format(locnum=i+1) - keys.append(k) - locobj = STATE.trace.create_object(bpath + k) - ik = PROC_BREAK_KEY_PATTERN.format(breaknum=b.GetID(), locnum=i+1) - ikeys.append(ik) + loc_keys.append(k) + loc_obj = STATE.trace.create_object(bpt_path + k) if b.location is not None: # Implies execution break base, addr = mapper.map(proc, l.GetLoadAddress()) if base != addr.space: STATE.trace.create_overlay_space(base, addr.space) - locobj.set_value('Range', addr.extend(1)) - locobj.set_value('Enabled', l.IsEnabled()) + loc_obj.set_value('Range', addr.extend(1)) + loc_obj.set_value('Enabled', l.IsEnabled()) else: # I guess it's a catchpoint pass - locobj.insert() - ibobj.set_value(ik, locobj) - brkobj.retain_values(keys) - brkobj.insert() + loc_obj.insert() + bpt_obj.retain_values(loc_keys) + bpt_obj.insert() -def put_single_watchpoint(b, ibobj, proc, ikeys): +def put_single_watchpoint(w, proc): mapper = STATE.trace.memory_mapper - bpath = PROC_WATCH_KEY_PATTERN.format( - procnum=proc.GetProcessID(), watchnum=b.GetID()) - brkobj = STATE.trace.create_object(bpath) - desc = util.get_description(b, level=0) - brkobj.set_value('Expression', desc) - brkobj.set_value('Kinds', 'WRITE') + wpt_path = PROC_WATCH_PATTERN.format( + procnum=proc.GetProcessID(), watchnum=w.GetID()) + wpt_obj = STATE.trace.create_object(wpt_path) + desc = util.get_description(w, level=0) + wpt_obj.set_value('Expression', desc) + wpt_obj.set_value('Kinds', 'WRITE') if "type = r" in desc: - brkobj.set_value('Kinds', 'READ') + wpt_obj.set_value('Kinds', 'READ') if "type = rw" in desc: - brkobj.set_value('Kinds', 'READ,WRITE') - base, addr = mapper.map(proc, b.GetWatchAddress()) + wpt_obj.set_value('Kinds', 'READ,WRITE') + base, addr = mapper.map(proc, w.GetWatchAddress()) if base != addr.space: STATE.trace.create_overlay_space(base, addr.space) - brkobj.set_value('Range', addr.extend(b.GetWatchSize())) - if b.GetCondition(): - brkobj.set_value('Condition', b.GetCondition()) - brkobj.set_value('Hit Count', b.GetHitCount()) - brkobj.set_value('Ignore Count', b.GetIgnoreCount()) - brkobj.set_value('Hardware Index', b.GetHardwareIndex()) - brkobj.set_value('Watch Address', hex(b.GetWatchAddress())) - brkobj.set_value('Watch Size', b.GetWatchSize()) - brkobj.set_value('Enabled', b.IsEnabled()) - brkobj.insert() + wpt_obj.set_value('Range', addr.extend(w.GetWatchSize())) + if w.GetCondition(): + wpt_obj.set_value('Condition', w.GetCondition()) + wpt_obj.set_value('Hit Count', w.GetHitCount()) + wpt_obj.set_value('Ignore Count', w.GetIgnoreCount()) + wpt_obj.set_value('Hardware Index', w.GetHardwareIndex()) + wpt_obj.set_value('Watch Address', hex(w.GetWatchAddress())) + wpt_obj.set_value('Watch Size', w.GetWatchSize()) + wpt_obj.set_value('Enabled', w.IsEnabled()) + wpt_obj.insert() def put_breakpoints(): target = util.get_target() proc = util.get_process() - ibpath = PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID()) - ibobj = STATE.trace.create_object(ibpath) + cont_path = PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID()) + cont_obj = STATE.trace.create_object(cont_path) keys = [] - ikeys = [] for i in range(0, target.GetNumBreakpoints()): b = target.GetBreakpointAtIndex(i) keys.append(BREAKPOINT_KEY_PATTERN.format(breaknum=b.GetID())) - put_single_breakpoint(b, ibobj, proc, ikeys) - ibobj.insert() - STATE.trace.proxy_object_path(BREAKPOINTS_PATH).retain_values(keys) - ibobj.retain_values(ikeys) + put_single_breakpoint(b, proc) + cont_obj.insert() + cont_obj.retain_values(keys) def put_watchpoints(): target = util.get_target() proc = util.get_process() - ibpath = PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID()) - ibobj = STATE.trace.create_object(ibpath) + cont_path = PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID()) + cont_obj = STATE.trace.create_object(cont_path) keys = [] - ikeys = [] for i in range(0, target.GetNumWatchpoints()): b = target.GetWatchpointAtIndex(i) keys.append(WATCHPOINT_KEY_PATTERN.format(watchnum=b.GetID())) - put_single_watchpoint(b, ibobj, proc, ikeys) - ibobj.insert() - STATE.trace.proxy_object_path(WATCHPOINTS_PATH).retain_values(keys) + put_single_watchpoint(b, proc) + cont_obj.insert() + cont_obj.retain_values(keys) @convert_errors diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py index 13a507643c..8f6f8341ae 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py @@ -110,28 +110,7 @@ class ProcessState(object): 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() -BRK_STATE = BrkState() PROC_STATE = {} @@ -240,9 +219,9 @@ def process_event(self, listener, event): return True if lldb.SBWatchpoint.EventIsWatchpointEvent(event): btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event) - bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt) + bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(event) if btype is lldb.eWatchpointEventTypeAdded: - return on_watchpoint_added(bpt) + return on_watchpoint_created(bpt) if btype is lldb.eWatchpointEventTypeCommandChanged: return on_watchpoint_modified(bpt) if btype is lldb.eWatchpointEventTypeConditionChanged: @@ -550,18 +529,6 @@ def on_exited(event): 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(): # Assumption: affects the current process proc = util.get_process() @@ -580,111 +547,80 @@ def on_free_objfile(event): def on_breakpoint_created(b): proc = util.get_process() - notify_others_breaks(proc) if proc.GetProcessID() not in PROC_STATE: return trace = commands.STATE.trace if trace is None: return - ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID()) with commands.STATE.client.batch(): with trace.open_tx("Breakpoint {} created".format(b.GetID())): - ibobj = trace.create_object(ibpath) - # Do not use retain_values or it'll remove other locs - commands.put_single_breakpoint(b, ibobj, proc, []) - ibobj.insert() + commands.put_single_breakpoint(b, proc) def on_breakpoint_modified(b): proc = util.get_process() - notify_others_breaks(proc) if proc.GetProcessID() not in PROC_STATE: return - old_count = BRK_STATE.get_brkloc_count(b) trace = commands.STATE.trace if trace is None: return - ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID()) with commands.STATE.client.batch(): with trace.open_tx("Breakpoint {} modified".format(b.GetID())): - ibobj = trace.create_object(ibpath) - 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) + commands.put_single_breakpoint(b, proc) def on_breakpoint_deleted(b): proc = util.get_process() - notify_others_breaks(proc) if proc.GetProcessID() not in PROC_STATE: return - old_count = BRK_STATE.del_brkloc_count(b) trace = commands.STATE.trace if trace is None: return - bpath = commands.BREAKPOINT_PATTERN.format(breaknum=b.GetID()) - ibobj = trace.proxy_object_path( - commands.PROC_BREAKS_PATTERN.format(procnum=proc.GetProcessID())) + bpt_path = commands.PROC_BREAK_PATTERN.format( + procnum=proc.GetProcessID(), breaknum=b.GetID()) + bpt_obj = trace.proxy_object_path(bpt_path) with commands.STATE.client.batch(): with trace.open_tx("Breakpoint {} deleted".format(b.GetID())): - trace.proxy_object_path(bpath).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) + bpt_obj.remove(tree=True) def on_watchpoint_created(b): proc = util.get_process() - notify_others_watches(proc) if proc.GetProcessID() not in PROC_STATE: return trace = commands.STATE.trace if trace is None: return - ibpath = commands.PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID()) with commands.STATE.client.batch(): with trace.open_tx("Breakpoint {} created".format(b.GetID())): - ibobj = trace.create_object(ibpath) - # Do not use retain_values or it'll remove other locs - commands.put_single_watchpoint(b, ibobj, proc, []) - ibobj.insert() + commands.put_single_watchpoint(b, proc) def on_watchpoint_modified(b): proc = util.get_process() - notify_others_watches(proc) if proc.GetProcessID() not in PROC_STATE: return - old_count = BRK_STATE.get_brkloc_count(b) trace = commands.STATE.trace if trace is None: return - ibpath = commands.PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID()) with commands.STATE.client.batch(): with trace.open_tx("Watchpoint {} modified".format(b.GetID())): - ibobj = trace.create_object(ibpath) - commands.put_single_watchpoint(b, ibobj, proc, []) + commands.put_single_watchpoint(b, proc) def on_watchpoint_deleted(b): proc = util.get_process() - notify_others_watches(proc) if proc.GetProcessID() not in PROC_STATE: return trace = commands.STATE.trace if trace is None: return - bpath = commands.WATCHPOINT_PATTERN.format(watchnum=b.GetID()) - ibobj = trace.proxy_object_path( - commands.PROC_WATCHES_PATTERN.format(procnum=proc.GetProcessID())) + wpt_path = commands.PROC_WATCH_PATTERN.format( + procnum=proc.GetProcessID(), watchnum=b.GetID()) + wpt_obj = trace.proxy_object_path(wpt_path) with commands.STATE.client.batch(): 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(): diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py index 7489164fc7..1761b51e66 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py @@ -1,17 +1,17 @@ ## ### -# IP: GHIDRA -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# IP: GHIDRA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. ## from concurrent.futures import Future, ThreadPoolExecutor import re @@ -33,13 +33,12 @@ def extre(base, ext): AVAILABLE_PATTERN = re.compile('Available\[(?P\\d*)\]') -WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P\\d*)\]') -BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P\\d*)\]') -BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P\\d*)\]') PROCESS_PATTERN = re.compile('Processes\[(?P\\d*)\]') PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints') +PROC_BREAK_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P\\d*)\]') +PROC_BREAKLOC_PATTERN = extre(PROC_BREAK_PATTERN, '\[(?P\\d*)\]') PROC_WATCHES_PATTERN = extre(PROCESS_PATTERN, '\.Watchpoints') -PROC_WATCHLOC_PATTERN = extre(PROC_WATCHES_PATTERN, '\[(?P\\d*)\]') +PROC_WATCH_PATTERN = extre(PROC_WATCHES_PATTERN, '\[(?P\\d*)\]') ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment') THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads') THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P\\d*)\]') @@ -183,7 +182,7 @@ def find_bpt_by_pattern(pattern, object, err_msg): 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 @@ -206,7 +205,7 @@ def find_wpt_by_pattern(pattern, object, err_msg): 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): @@ -219,7 +218,7 @@ def find_bptlocnum_by_pattern(pattern, object, err_msg): 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") @@ -607,17 +606,20 @@ def break_exception(lang: str): @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.""" wpt = find_wpt_by_obj(watchpoint) wpt.enabled = enabled + cmd = 'enable' if enabled else 'disable' + exec_convert_errors(f'watchpoint {cmd} {wpt.GetID()}') @REGISTRY.method(action='toggle') def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): """Toggle a 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') diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml index 01f25e6314..1c37118bf7 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/schema.xml @@ -6,8 +6,6 @@ - -