From b66968f81514c1838bf1dbf8550783487d8ff78c Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:02:20 -0400 Subject: [PATCH] GP-4858: Change to track regions w/out catchpoint. --- .../src/main/py/src/ghidragdb/commands.py | 38 +++++----- .../src/main/py/src/ghidragdb/hooks.py | 66 +++-------------- .../src/main/py/src/ghidragdb/util.py | 72 ++++++------------- 3 files changed, 53 insertions(+), 123 deletions(-) diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py index 146c7073b9..019f1038ab 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.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 contextlib import contextmanager import inspect @@ -1200,12 +1200,13 @@ def ghidra_trace_put_environment(*, is_mi, **kwargs): put_environment() -def put_regions(): +def put_regions(regions=None): inf = gdb.selected_inferior() - try: - regions = util.REGION_INFO_READER.get_regions() - except Exception: - regions = [] + if regions is None: + try: + regions = util.REGION_INFO_READER.get_regions() + except Exception: + regions = [] if len(regions) == 0 and gdb.selected_thread() is not None: regions = [util.REGION_INFO_READER.full_mem()] mapper = STATE.trace.memory_mapper @@ -1229,6 +1230,7 @@ def put_regions(): regobj.insert() STATE.trace.proxy_object_path( MEMORY_PATTERN.format(infnum=inf.num)).retain_values(keys) + return regions @cmd('ghidra trace put-regions', '-ghidra-trace-put-regions', gdb.COMMAND_DATA, diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py index 921f98de7f..a9cf007b83 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py @@ -5,7 +5,7 @@ # 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 +# 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, @@ -33,11 +33,10 @@ GhidraHookPrefix() class HookState(object): - __slots__ = ('installed', 'mem_catchpoint', 'batch', 'skip_continue', 'in_break_w_cont') + __slots__ = ('installed', 'batch', 'skip_continue', 'in_break_w_cont') def __init__(self): self.installed = False - self.mem_catchpoint = None self.batch = None self.skip_continue = False self.in_break_w_cont = False @@ -64,7 +63,7 @@ class InferiorState(object): def __init__(self): self.first = True # For things we can detect changes to between stops - self.regions = False + self.regions = [] self.modules = False self.threads = False self.breaks = False @@ -107,13 +106,10 @@ class InferiorState(object): print(f"Couldn't record page with SP: {e}") self.visited.add(hashable_frame) # NB: These commands (put_modules/put_regions) will fail if the process is running - if first or self.regions or self.threads or self.modules: - # Sections, memory syscalls, or stack allocations - commands.put_modules() - self.modules = False - commands.put_regions() - self.regions = False - elif first or self.modules: + regions_changed, regions = util.REGION_INFO_READER.have_changed(self.regions) + if regions_changed: + self.regions = commands.put_regions(regions) + if first or self.modules: commands.put_modules() self.modules = False if first or self.breaks: @@ -252,14 +248,6 @@ def on_frame_selected(): commands.activate() -@log_errors -def on_syscall_memory(): - inf = gdb.selected_inferior() - if inf.num not in INF_STATES: - return - INF_STATES[inf.num].regions = True - - @log_errors def on_memory_changed(event): inf = gdb.selected_inferior() @@ -287,7 +275,8 @@ def on_register_changed(event): # For now, just record the lot HOOK_STATE.ensure_batch() with trace.open_tx("Register {} changed".format(event.regnum)): - commands.putreg(event.frame, util.get_register_descs(event.frame.architecture())) + commands.putreg(event.frame, util.get_register_descs( + event.frame.architecture())) @log_errors @@ -319,12 +308,9 @@ def check_for_continue(event): HOOK_STATE.in_break_w_cont = False return False - + @log_errors def on_stop(event): - if hasattr(event, 'breakpoints') and HOOK_STATE.mem_catchpoint in event.breakpoints: - HOOK_STATE.skip_continue = True - return if check_for_continue(event): HOOK_STATE.skip_continue = True return @@ -416,8 +402,6 @@ def on_breakpoint_created(b): @log_errors def on_breakpoint_modified(b): - if b == HOOK_STATE.mem_catchpoint: - return inf = gdb.selected_inferior() notify_others_breaks(inf) if inf.num not in INF_STATES: @@ -466,20 +450,6 @@ def on_before_prompt(): HOOK_STATE.end_batch() -# This will be called by a catchpoint -class GhidraTraceEventMemoryCommand(gdb.Command): - - def __init__(self): - super().__init__('hooks-ghidra event-memory', gdb.COMMAND_NONE) - - def invoke(self, argument, from_tty): - self.dont_repeat() - on_syscall_memory() - - -GhidraTraceEventMemoryCommand() - - def cmd_hook(name): def _cmd_hook(func): @@ -556,21 +526,6 @@ def install_hooks(): # Respond to user-driven state changes: (Not target-driven) gdb.events.memory_changed.connect(on_memory_changed) gdb.events.register_changed.connect(on_register_changed) - # Respond to target-driven memory map changes: - # group:memory is actually a bit broad, but will probably port better - # One alternative is to name all syscalls that cause a change.... - # Ones we could probably omit: - # msync, - # (Deals in syncing file-backed pages to disk.) - # mlock, munlock, mlockall, munlockall, mincore, madvise, - # (Deal in paging. Doesn't affect valid addresses.) - # mbind, get_mempolicy, set_mempolicy, migrate_pages, move_pages - # (All NUMA stuff) - # - if HOOK_STATE.mem_catchpoint is not None: - HOOK_STATE.mem_catchpoint.enabled = True - else: - HOOK_STATE.mem_catchpoint = util.MEM_CATCHPOINT_SETTER.install_catchpoint() gdb.events.cont.connect(on_cont) gdb.events.stop.connect(on_stop) @@ -605,7 +560,6 @@ def remove_hooks(): gdb.events.memory_changed.disconnect(on_memory_changed) gdb.events.register_changed.disconnect(on_register_changed) - HOOK_STATE.mem_catchpoint.enabled = False gdb.events.cont.disconnect(on_cont) gdb.events.stop.disconnect(on_stop) diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py index 5c1654e7dc..206b255c5b 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.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 +# IP: GHIDRA # -# http://www.apache.org/licenses/LICENSE-2.0 +# 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 # -# 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. +# 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 collections import namedtuple import bisect @@ -245,6 +245,14 @@ class RegionInfoReader(object): sizeptr = int(gdb.parse_and_eval('sizeof(void*)')) * 8 return Region(0, 1 << sizeptr, 0, None, 'full memory') + def have_changed(self, regions): + if len(regions) == 1 and regions[0].objfile == 'full memory': + return False, None + new_regions = self.get_regions() + if new_regions == regions: + return False, None + return True, new_regions + class RegionInfoReaderV8(RegionInfoReader): cmd = REGIONS_CMD @@ -344,41 +352,6 @@ def _choose_breakpoint_location_info_reader(): BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader() -class MemCatchpointSetterV8(object): - def install_catchpoint(self): - return object() - - -class MemCatchpointSetterV11(object): - def install_catchpoint(self): - breaks_before = set(gdb.breakpoints()) - try: - gdb.execute(""" - catch syscall group:memory - commands - silent - hooks-ghidra event-memory - cont - end - """) - return (set(gdb.breakpoints()) - breaks_before).pop() - except Exception as e: - print(f"Error setting memory catchpoint: {e}") - return object() - - -def _choose_mem_catchpoint_setter(): - if GDB_VERSION.major >= 11: - return MemCatchpointSetterV11() - if GDB_VERSION.major >= 8: - return MemCatchpointSetterV8() - else: - raise gdb.GdbError( - "GDB version not recognized by ghidragdb: " + GDB_VERSION.full) - - -MEM_CATCHPOINT_SETTER = _choose_mem_catchpoint_setter() - def set_bool_param_by_api(name, value): gdb.set_parameter(name, value) @@ -418,11 +391,11 @@ def get_register_descs(arch, group='all'): if hasattr(arch, "registers"): try: return arch.registers(group) - except ValueError: # No such group, or version too old + except ValueError: # No such group, or version too old return arch.registers() else: descs = [] - try: + try: regset = gdb.execute( f"info registers {group}", to_string=True).strip().split('\n') except Exception as e: @@ -433,7 +406,8 @@ def get_register_descs(arch, group='all'): tokens = line.strip().split() descs.append(RegisterDesc(tokens[0])) return descs - + + def selected_frame(): try: return gdb.selected_frame()