mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-3857: Port most Debugger components to TraceRmi.
This commit is contained in:
parent
7e4d2bcfaa
commit
fd4380c07a
222 changed files with 7241 additions and 3752 deletions
|
@ -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"
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue