GP-3857: Port most Debugger components to TraceRmi.

This commit is contained in:
Dan 2023-11-02 10:43:31 -04:00
parent 7e4d2bcfaa
commit fd4380c07a
222 changed files with 7241 additions and 3752 deletions

View file

@ -46,7 +46,10 @@ shift
target_args="$@"
"$OPT_GDB_PATH" \
-q \
-ex "set pagination off" \
-ex "set confirm off" \
-ex "show version" \
-ex "python import ghidragdb" \
-ex "file \"$target_image\"" \
-ex "set args $target_args" \
@ -55,4 +58,5 @@ target_args="$@"
-ex "ghidra trace start" \
-ex "ghidra trace sync-enable" \
-ex "$OPT_START_CMD" \
-ex "set confirm on" \
-ex "set pagination on"

View file

@ -239,11 +239,18 @@ class DefaultRegisterMapper(object):
.format(name, value, value.type))
return RegVal(self.map_name(inf, name), av)
def convert_value_back(self, value, size=None):
if size is not None:
value = value[-size:].rjust(size, b'\0')
if self.byte_order == 'little':
value = bytes(reversed(value))
return value
def map_name_back(self, inf, name):
return name
def map_value_back(self, inf, name, value):
return RegVal(self.map_name_back(inf, name), value)
return RegVal(self.map_name_back(inf, name), self.convert_value_back(value))
class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
@ -268,6 +275,7 @@ class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
def map_name_back(self, inf, name):
if name == 'rflags':
return 'eflags'
return name
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')

View file

@ -50,6 +50,8 @@ STACK_PATTERN = THREAD_PATTERN + '.Stack'
FRAME_KEY_PATTERN = '[{level}]'
FRAME_PATTERN = STACK_PATTERN + FRAME_KEY_PATTERN
REGS_PATTERN = FRAME_PATTERN + '.Registers'
REG_KEY_PATTERN = '[{regname}]'
REG_PATTERN = REGS_PATTERN + REG_KEY_PATTERN
MEMORY_PATTERN = INFERIOR_PATTERN + '.Memory'
REGION_KEY_PATTERN = '[{start:08x}]'
REGION_PATTERN = MEMORY_PATTERN + REGION_KEY_PATTERN
@ -564,15 +566,26 @@ def putreg(frame, reg_descs):
space = REGS_PATTERN.format(infnum=inf.num, tnum=gdb.selected_thread().num,
level=frame.level())
STATE.trace.create_overlay_space('register', space)
robj = STATE.trace.create_object(space)
robj.insert()
cobj = STATE.trace.create_object(space)
cobj.insert()
mapper = STATE.trace.register_mapper
keys = []
values = []
for desc in reg_descs:
v = frame.read_register(desc)
values.append(mapper.map_value(inf, desc.name, v))
rv = mapper.map_value(inf, desc.name, v)
values.append(rv)
# TODO: Key by gdb's name or mapped name? I think gdb's.
rpath = REG_PATTERN.format(infnum=inf.num, tnum=gdb.selected_thread(
).num, level=frame.level(), regname=desc.name)
keys.append(REG_KEY_PATTERN.format(regname=desc.name))
robj = STATE.trace.create_object(rpath)
robj.set_value('_value', rv.value)
robj.insert()
cobj.retain_values(keys)
# TODO: Memorize registers that failed for this arch, and omit later.
return {'missing': STATE.trace.put_registers(space, values)}
missing = STATE.trace.put_registers(space, values)
return {'missing': missing}
@cmd('ghidra trace putreg', '-ghidra-trace-putreg', gdb.COMMAND_DATA, True)
@ -585,7 +598,8 @@ def ghidra_trace_putreg(group='all', *, is_mi, **kwargs):
STATE.require_tx()
frame = gdb.selected_frame()
return putreg(frame, frame.architecture().registers(group))
with STATE.client.batch() as b:
return putreg(frame, frame.architecture().registers(group))
@cmd('ghidra trace delreg', '-ghidra-trace-delreg', gdb.COMMAND_DATA, True)
@ -977,6 +991,17 @@ def compute_inf_state(inf):
return 'STOPPED'
def put_inferior_state(inf):
ipath = INFERIOR_PATTERN.format(infnum=inf.num)
infobj = STATE.trace.proxy_object_path(ipath)
istate = compute_inf_state(inf)
infobj.set_value('_state', istate)
for t in inf.threads():
tpath = THREAD_PATTERN.format(infnum=inf.num, tnum=t.num)
tobj = STATE.trace.proxy_object_path(tpath)
tobj.set_value('_state', convert_state(t))
def put_inferiors():
# TODO: Attributes like _exit_code, _state?
# _state would be derived from threads
@ -1034,6 +1059,7 @@ def put_single_breakpoint(b, ibobj, inf, ikeys):
mapper = STATE.trace.memory_mapper
bpath = BREAKPOINT_PATTERN.format(breaknum=b.number)
brkobj = STATE.trace.create_object(bpath)
brkobj.set_value('_enabled', b.enabled)
if b.type == gdb.BP_BREAKPOINT:
brkobj.set_value('_expression', b.location)
brkobj.set_value('_kinds', 'SW_EXECUTE')
@ -1073,6 +1099,7 @@ def put_single_breakpoint(b, ibobj, inf, ikeys):
if inf.num not in l.thread_groups:
continue
locobj = STATE.trace.create_object(bpath + k)
locobj.set_value('_enabled', l.enabled)
ik = INF_BREAK_KEY_PATTERN.format(breaknum=b.number, locnum=i+1)
ikeys.append(ik)
if b.location is not None: # Implies execution break

View file

@ -31,12 +31,13 @@ GhidraHookPrefix()
class HookState(object):
__slots__ = ('installed', 'mem_catchpoint', 'batch')
__slots__ = ('installed', 'mem_catchpoint', 'batch', 'skip_continue')
def __init__(self):
self.installed = False
self.mem_catchpoint = None
self.batch = None
self.skip_continue = False
def ensure_batch(self):
if self.batch is None:
@ -48,6 +49,11 @@ class HookState(object):
commands.STATE.client.end_batch()
self.batch = None
def check_skip_continue(self):
skip = self.skip_continue
self.skip_continue = False
return skip
class InferiorState(object):
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'visited')
@ -70,6 +76,8 @@ class InferiorState(object):
if first:
commands.put_inferiors()
commands.put_environment()
else:
commands.put_inferior_state(gdb.selected_inferior())
if self.threads:
commands.put_threads()
self.threads = False
@ -81,7 +89,8 @@ class InferiorState(object):
frame = gdb.selected_frame()
hashable_frame = (thread, frame.level())
if first or hashable_frame not in self.visited:
commands.putreg(frame, frame.architecture().registers())
commands.putreg(
frame, frame.architecture().registers('general'))
commands.putmem("$pc", "1", from_tty=False)
commands.putmem("$sp", "1", from_tty=False)
self.visited.add(hashable_frame)
@ -224,7 +233,6 @@ def on_memory_changed(event):
def on_register_changed(event):
gdb.write("Register changed: {}".format(dir(event)))
inf = gdb.selected_inferior()
if inf.num not in INF_STATES:
return
@ -240,6 +248,8 @@ def on_register_changed(event):
def on_cont(event):
if (HOOK_STATE.check_skip_continue()):
return
inf = gdb.selected_inferior()
if inf.num not in INF_STATES:
return
@ -254,6 +264,7 @@ def on_cont(event):
def on_stop(event):
if hasattr(event, 'breakpoints') and HOOK_STATE.mem_catchpoint in event.breakpoints:
HOOK_STATE.skip_continue = True
return
inf = gdb.selected_inferior()
if inf.num not in INF_STATES:
@ -337,6 +348,8 @@ def on_breakpoint_created(b):
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:
@ -438,6 +451,16 @@ def hook_frame():
on_frame_selected()
@cmd_hook('hookpost-up')
def hook_frame_up():
on_frame_selected()
@cmd_hook('hookpost-down')
def hook_frame_down():
on_frame_selected()
# TODO: Checks and workarounds for events missing in gdb 8
def install_hooks():
if HOOK_STATE.installed:
@ -451,6 +474,8 @@ def install_hooks():
gdb.events.new_thread.connect(on_new_thread)
hook_thread.hook()
hook_frame.hook()
hook_frame_up.hook()
hook_frame_down.hook()
# Respond to user-driven state changes: (Not target-driven)
gdb.events.memory_changed.connect(on_memory_changed)
@ -508,6 +533,8 @@ def remove_hooks():
gdb.events.new_thread.disconnect(on_new_thread)
hook_thread.unhook()
hook_frame.unhook()
hook_frame_up.unhook()
hook_frame_down.unhook()
gdb.events.memory_changed.disconnect(on_memory_changed)
gdb.events.register_changed.disconnect(on_register_changed)

View file

@ -14,6 +14,7 @@
# limitations under the License.
##
from concurrent.futures import Future, Executor
from contextlib import contextmanager
import re
from ghidratrace import sch
@ -24,13 +25,30 @@ import gdb
from . import commands, hooks, util
@contextmanager
def no_pagination():
before = gdb.parameter('pagination')
gdb.set_parameter('pagination', False)
yield
gdb.set_parameter('pagination', before)
@contextmanager
def no_confirm():
before = gdb.parameter('confirm')
gdb.set_parameter('confirm', False)
yield
gdb.set_parameter('confirm', before)
class GdbExecutor(Executor):
def submit(self, fn, *args, **kwargs):
fut = Future()
def _exec():
try:
result = fn(*args, **kwargs)
with no_pagination():
result = fn(*args, **kwargs)
hooks.HOOK_STATE.end_batch()
fut.set_result(result)
except Exception as e:
@ -186,7 +204,9 @@ def find_frame_by_regs_obj(object):
# Because there's no method to get a register by name....
def find_reg_by_name(f, name):
for reg in f.architecture().registers():
if reg.name == name:
# TODO: gdb appears to be case sensitive, but until we encounter a
# situation where case matters, we'll be insensitive
if reg.name.lower() == name.lower():
return reg
raise KeyError(f"No such register: {name}")
@ -453,7 +473,8 @@ def launch_run(inferior: sch.Schema('Inferior'),
def kill(inferior: sch.Schema('Inferior')):
"""Kill execution of the inferior."""
switch_inferior(find_inf_by_obj(inferior))
gdb.execute('kill')
with no_confirm():
gdb.execute('kill')
@REGISTRY.method
@ -463,8 +484,11 @@ def resume(inferior: sch.Schema('Inferior')):
gdb.execute('continue')
# Technically, inferior is not required, but it hints that the affected object
# is the current inferior. This in turn queues the UI to enable or disable the
# button appropriately
@REGISTRY.method
def interrupt():
def interrupt(inferior: sch.Schema('Inferior')):
"""Interrupt the execution of the debugged program."""
gdb.execute('interrupt')
@ -490,7 +514,7 @@ def step_out(thread: sch.Schema('Thread')):
gdb.execute('finish')
@REGISTRY.method(action='step_ext')
@REGISTRY.method(action='step_ext', name='Advance')
def step_advance(thread: sch.Schema('Thread'), address: Address):
"""Continue execution up to the given address (advance)."""
t = find_thread_by_obj(thread)
@ -499,7 +523,7 @@ def step_advance(thread: sch.Schema('Thread'), address: Address):
gdb.execute(f'advance *0x{offset:x}')
@REGISTRY.method(action='step_ext')
@REGISTRY.method(action='step_ext', name='Return')
def step_return(thread: sch.Schema('Thread'), value: int=None):
"""Skip the remainder of the current function (return)."""
find_thread_by_obj(thread).switch()
@ -641,13 +665,13 @@ def write_mem(inferior: sch.Schema('Inferior'), address: Address, data: bytes):
@REGISTRY.method
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
"""Write a register."""
f = find_frame_by_obj(frame)
f.select()
inf = gdb.selected_inferior()
mname, mval = frame.trace.register_mapper.map_value_back(inf, name, value)
reg = find_reg_by_name(f, mname)
size = int(gdb.parse_and_eval(f'sizeof(${mname})'))
size = int(gdb.parse_and_eval(f'sizeof(${reg.name})'))
arr = '{' + ','.join(str(b) for b in mval) + '}'
gdb.execute(f'set ((unsigned char[{size}])${mname}) = {arr}')
gdb.execute(f'set ((unsigned char[{size}])${reg.name}) = {arr}')