GP-4858: Change to track regions w/out catchpoint.

This commit is contained in:
Dan 2024-08-21 13:02:20 -04:00
parent d33af2b972
commit b66968f815
3 changed files with 53 additions and 123 deletions

View File

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

View File

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

View File

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