mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-26 06:02:29 +00:00
Merge remote-tracking branch 'origin/GP-4415_Dan_moreLldbTraceRmiFixes--SQUASHED'
This commit is contained in:
commit
3c1982b501
@ -1506,7 +1506,7 @@ def ghidra_trace_sync_disable(*, is_mi, **kwargs):
|
||||
"""
|
||||
Cease synchronizing the current inferior with the Ghidra trace.
|
||||
|
||||
This is the opposite of 'ghidra trace sync-disable', except it will not
|
||||
This is the opposite of 'ghidra trace sync-enable', except it will not
|
||||
automatically remove hooks.
|
||||
"""
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
#@menu-group local
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#lldb
|
||||
#@enum StartCmd:str run "process launch" "process launch --stop-at-entry"
|
||||
#@enum StartCmd:str "process launch" "process launch --stop-at-entry"
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_LLDB_PATH:str="lldb" "Path to lldb" "The path to lldb. Omit the full path to resolve using the system PATH."
|
||||
|
@ -50,8 +50,8 @@ language_map = {
|
||||
'thumbv7em': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
|
||||
'armv8': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'armv8l': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'arm64': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'arm64e': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'arm64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'arm64e': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'mips': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr2': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
@ -141,10 +141,24 @@ compiler_map = {
|
||||
}
|
||||
|
||||
|
||||
def get_arch():
|
||||
def find_host_triple():
|
||||
dbg = util.get_debugger()
|
||||
for i in range(dbg.GetNumPlatforms()):
|
||||
platform = dbg.GetPlatformAtIndex(i)
|
||||
if platform.GetName() == 'host':
|
||||
return platform.GetTriple()
|
||||
return 'unrecognized'
|
||||
|
||||
|
||||
def find_triple():
|
||||
triple = util.get_target().triple
|
||||
if triple is None:
|
||||
return "x86_64"
|
||||
if triple is not None:
|
||||
return triple
|
||||
return find_host_triple()
|
||||
|
||||
|
||||
def get_arch():
|
||||
triple = find_triple()
|
||||
return triple.split('-')[0]
|
||||
|
||||
|
||||
@ -152,7 +166,6 @@ def get_endian():
|
||||
parm = util.get_convenience_variable('endian')
|
||||
if parm != 'auto':
|
||||
return parm
|
||||
# Once again, we have to hack using the human-readable 'show'
|
||||
order = util.get_target().GetByteOrder()
|
||||
if order is lldb.eByteOrderLittle:
|
||||
return 'little'
|
||||
@ -167,15 +180,11 @@ def get_osabi():
|
||||
parm = util.get_convenience_variable('osabi')
|
||||
if not parm in ['auto', 'default']:
|
||||
return parm
|
||||
# We have to hack around the fact the LLDB won't give us the current OS ABI
|
||||
# via the API if it is "auto" or "default". Using "show", we can get it, but
|
||||
# we have to parse output meant for a human. The current value will be on
|
||||
# the top line, delimited by double quotes. It will be the last delimited
|
||||
# thing on that line. ("auto" may appear earlier on the line.)
|
||||
triple = util.get_target().triple
|
||||
triple = find_triple()
|
||||
# this is an unfortunate feature of the tests
|
||||
if triple is None or '-' not in triple:
|
||||
return "default"
|
||||
triple = find_triple()
|
||||
return triple.split('-')[2]
|
||||
|
||||
|
||||
@ -274,29 +283,8 @@ class DefaultRegisterMapper(object):
|
||||
def map_name(self, proc, name):
|
||||
return name
|
||||
|
||||
"""
|
||||
def convert_value(self, value, type=None):
|
||||
if type is None:
|
||||
type = value.dynamic_type.strip_typedefs()
|
||||
l = type.sizeof
|
||||
# l - 1 because array() takes the max index, inclusive
|
||||
# NOTE: Might like to pre-lookup 'unsigned char', but it depends on the
|
||||
# architecture *at the time of lookup*.
|
||||
cv = value.cast(lldb.lookup_type('unsigned char').array(l - 1))
|
||||
rng = range(l)
|
||||
if self.byte_order == 'little':
|
||||
rng = reversed(rng)
|
||||
return bytes(cv[i] for i in rng)
|
||||
"""
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
try:
|
||||
# TODO: this seems half-baked
|
||||
av = value.to_bytes(8, "big")
|
||||
except e:
|
||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||
.format(name, value, value.type))
|
||||
return RegVal(self.map_name(proc, name), av)
|
||||
return RegVal(self.map_name(proc, name), value)
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
return name
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -67,13 +67,22 @@ class ProcessState(object):
|
||||
hashable_frame = (thread.GetThreadID(), frame.GetFrameID())
|
||||
if first or hashable_frame not in self.visited:
|
||||
banks = frame.GetRegisters()
|
||||
primary = banks.GetFirstValueByName(commands.DEFAULT_REGISTER_BANK)
|
||||
primary = banks.GetFirstValueByName(
|
||||
commands.DEFAULT_REGISTER_BANK)
|
||||
if primary.value is None:
|
||||
primary = banks[0]
|
||||
commands.DEFAULT_REGISTER_BANK = primary.name
|
||||
commands.putreg(frame, primary)
|
||||
commands.putmem("$pc", "1", result=None)
|
||||
commands.putmem("$sp", "1", result=None)
|
||||
if primary is not None:
|
||||
commands.DEFAULT_REGISTER_BANK = primary.name
|
||||
if primary is not None:
|
||||
commands.putreg(frame, primary)
|
||||
try:
|
||||
commands.putmem("$pc", "1", result=None)
|
||||
except BaseException as e:
|
||||
print(f"Couldn't record page with PC: {e}")
|
||||
try:
|
||||
commands.putmem("$sp", "1", result=None)
|
||||
except BaseException as e:
|
||||
print(f"Couldn't record page with SP: {e}")
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
# Sections, memory syscalls, or stack allocations
|
||||
@ -113,7 +122,7 @@ class BrkState(object):
|
||||
return self.break_loc_counts.get(b.GetID(), 0)
|
||||
|
||||
def del_brkloc_count(self, b):
|
||||
if b not in self.break_loc_counts:
|
||||
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()]
|
||||
@ -125,19 +134,32 @@ BRK_STATE = BrkState()
|
||||
PROC_STATE = {}
|
||||
|
||||
|
||||
class QuitSentinel(object):
|
||||
pass
|
||||
|
||||
|
||||
QUIT = QuitSentinel()
|
||||
|
||||
|
||||
def process_event(self, listener, event):
|
||||
try:
|
||||
desc = util.get_description(event)
|
||||
# print('Event:', desc)
|
||||
# print(f"Event: {desc}")
|
||||
target = util.get_target()
|
||||
if not target.IsValid():
|
||||
# LLDB may crash on event.GetBroadcasterClass, otherwise
|
||||
# All the checks below, e.g. SBTarget.EventIsTargetEvent, call this
|
||||
print(f"Ignoring {desc} because target is invalid")
|
||||
return
|
||||
event_process = util.get_process()
|
||||
if event_process not in PROC_STATE:
|
||||
if event_process.IsValid() and event_process not in PROC_STATE:
|
||||
PROC_STATE[event_process.GetProcessID()] = ProcessState()
|
||||
rc = event_process.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for process failed")
|
||||
|
||||
# NB: Calling put_state on running leaves an open transaction
|
||||
if event_process.is_running is False:
|
||||
if not event_process.is_running:
|
||||
commands.put_state(event_process)
|
||||
type = event.GetType()
|
||||
if lldb.SBTarget.EventIsTargetEvent(event):
|
||||
@ -157,6 +179,8 @@ def process_event(self, listener, event):
|
||||
return on_exited(event)
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
if event_process.is_running:
|
||||
return on_cont(event)
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0:
|
||||
if event_process.is_stopped:
|
||||
@ -244,6 +268,9 @@ def process_event(self, listener, event):
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0:
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived) != 0:
|
||||
# DO NOT return QUIT here.
|
||||
# For some reason, this event comes just after launch.
|
||||
# Maybe need to figure out *which* interpreter?
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0:
|
||||
return True
|
||||
@ -251,7 +278,7 @@ def process_event(self, listener, event):
|
||||
return True
|
||||
print("UNKNOWN EVENT")
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
|
||||
|
||||
@ -267,50 +294,57 @@ class EventThread(threading.Thread):
|
||||
target = util.get_target()
|
||||
proc = util.get_process()
|
||||
rc = cli.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for cli failed")
|
||||
return
|
||||
# return
|
||||
rc = target.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for target failed")
|
||||
return
|
||||
# return
|
||||
rc = proc.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for process failed")
|
||||
return
|
||||
# return
|
||||
|
||||
# Not sure what effect this logic has
|
||||
rc = cli.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
print("add listener for cli failed")
|
||||
return
|
||||
if not rc:
|
||||
print("add initial events for cli failed")
|
||||
# return
|
||||
rc = target.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
print("add listener for target failed")
|
||||
return
|
||||
if not rc:
|
||||
print("add initial events for target failed")
|
||||
# return
|
||||
rc = proc.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
print("add listener for process failed")
|
||||
return
|
||||
if not rc:
|
||||
print("add initial events for process failed")
|
||||
# return
|
||||
|
||||
rc = listener.StartListeningForEventClass(
|
||||
util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for threads failed")
|
||||
return
|
||||
# return
|
||||
# THIS WILL NOT WORK: listener = util.get_debugger().GetListener()
|
||||
|
||||
while True:
|
||||
event_recvd = False
|
||||
while event_recvd is False:
|
||||
while not event_recvd:
|
||||
if listener.WaitForEvent(lldb.UINT32_MAX, self.event):
|
||||
try:
|
||||
self.func(listener, self.event)
|
||||
while listener.GetNextEvent(self.event):
|
||||
self.func(listener, self.event)
|
||||
event_recvd = True
|
||||
result = self.func(listener, self.event)
|
||||
if result is QUIT:
|
||||
return
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
while listener.GetNextEvent(self.event):
|
||||
try:
|
||||
result = self.func(listener, self.event)
|
||||
if result is QUIT:
|
||||
return
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
event_recvd = True
|
||||
proc = util.get_process()
|
||||
if proc is not None and not proc.is_alive:
|
||||
break
|
||||
@ -445,7 +479,7 @@ def on_memory_changed(event):
|
||||
|
||||
|
||||
def on_register_changed(event):
|
||||
print("Register changed: {}".format(dir(event)))
|
||||
# print("Register changed: {}".format(dir(event)))
|
||||
proc = util.get_process()
|
||||
if proc.GetProcessID() not in PROC_STATE:
|
||||
return
|
||||
@ -476,7 +510,8 @@ def on_cont(event):
|
||||
|
||||
|
||||
def on_stop(event):
|
||||
proc = lldb.SBProcess.GetProcessFromEvent(event) if event is not None else util.get_process()
|
||||
proc = lldb.SBProcess.GetProcessFromEvent(
|
||||
event) if event is not None else util.get_process()
|
||||
if proc.GetProcessID() not in PROC_STATE:
|
||||
print("not in state")
|
||||
return
|
||||
@ -586,7 +621,7 @@ def on_breakpoint_deleted(b):
|
||||
notify_others_breaks(proc)
|
||||
if proc.GetProcessID() not in PROC_STATE:
|
||||
return
|
||||
old_count = BRK_STATE.del_brkloc_count(b.GetID())
|
||||
old_count = BRK_STATE.del_brkloc_count(b)
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
|
@ -15,9 +15,11 @@
|
||||
##
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
import lldb
|
||||
|
||||
from . import commands, util
|
||||
@ -228,107 +230,122 @@ def find_bpt_loc_by_obj(object):
|
||||
return bpt.locations[locnum - 1] # Display is 1-up
|
||||
|
||||
|
||||
def exec_convert_errors(cmd, to_string=False):
|
||||
res = lldb.SBCommandReturnObject()
|
||||
util.get_debugger().GetCommandInterpreter().HandleCommand(cmd, res)
|
||||
if not res.Succeeded():
|
||||
if not to_string:
|
||||
print(res.GetError(), file=sys.stderr)
|
||||
raise RuntimeError(res.GetError())
|
||||
if to_string:
|
||||
return res.GetOutput()
|
||||
print(res.GetOutput(), end="")
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def execute(cmd: str, to_string: bool=False):
|
||||
"""Execute a CLI command."""
|
||||
res = lldb.SBCommandReturnObject()
|
||||
util.get_debugger().GetCommandInterpreter().HandleCommand(cmd, res)
|
||||
if to_string:
|
||||
if res.Succeeded():
|
||||
return res.GetOutput()
|
||||
else:
|
||||
return res.GetError()
|
||||
# TODO: Check for eCommandInterpreterResultQuitRequested?
|
||||
return exec_convert_errors(cmd, to_string)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method
|
||||
def evaluate(expr: str):
|
||||
"""Evaluate an expression."""
|
||||
value = util.get_target().EvaluateExpression(expr)
|
||||
if value.GetError().Fail():
|
||||
raise RuntimeError(value.GetError().GetCString())
|
||||
return commands.convert_value(value)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def pyeval(expr: str):
|
||||
return eval(expr)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh', display="Refresh Available")
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on lldb's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-available')
|
||||
exec_convert_errors('ghidra trace put-available')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Breakpoints")
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
process).
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoints'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-breakpoints')
|
||||
exec_convert_errors('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Processes")
|
||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||
"""Refresh the list of processes."""
|
||||
with commands.open_tracked_tx('Refresh Processes'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-threads')
|
||||
exec_convert_errors('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Breakpoints")
|
||||
def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the process.
|
||||
|
||||
In the course of refreshing the locations, the breakpoint list will also be
|
||||
refreshed.
|
||||
Refresh the breakpoints for the process.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-breakpoints')
|
||||
exec_convert_errors('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Watchpoints")
|
||||
def refresh_proc_watchpoints(node: sch.Schema('WatchpointContainer')):
|
||||
"""
|
||||
Refresh the watchpoint locations for the process.
|
||||
|
||||
In the course of refreshing the locations, the watchpoint list will also be
|
||||
refreshed.
|
||||
Refresh the watchpoints for the process.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Watchpoint Locations'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-watchpoints')
|
||||
exec_convert_errors('ghidra trace put-watchpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Environment")
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
with commands.open_tracked_tx('Refresh Environment'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-environment')
|
||||
exec_convert_errors('ghidra trace put-environment')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Threads")
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the process."""
|
||||
with commands.open_tracked_tx('Refresh Threads'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-threads')
|
||||
exec_convert_errors('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Stack")
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
t = find_thread_by_stack_obj(node)
|
||||
t.process.SetSelectedThread(t)
|
||||
with commands.open_tracked_tx('Refresh Stack'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-frames')
|
||||
exec_convert_errors('ghidra trace put-frames')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Registers")
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
f = find_frame_by_regs_obj(node)
|
||||
f.thread.SetSelectedFrame(f.GetFrameID())
|
||||
# TODO: Groups?
|
||||
with commands.open_tracked_tx('Refresh Registers'):
|
||||
util.get_debugger().HandleCommand('ghidra trace putreg')
|
||||
exec_convert_errors('ghidra trace putreg')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Memory")
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the process."""
|
||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-regions')
|
||||
exec_convert_errors('ghidra trace put-regions')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Modules")
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the process.
|
||||
@ -336,12 +353,13 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
util.get_debugger().HandleCommand('ghidra trace put-modules')
|
||||
exec_convert_errors('ghidra trace put-modules')
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_process(process: sch.Schema('Process')):
|
||||
"""Switch to the process."""
|
||||
# TODO
|
||||
return
|
||||
|
||||
|
||||
@ -363,41 +381,48 @@ def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
def remove_process(process: sch.Schema('Process')):
|
||||
"""Remove the process."""
|
||||
proc = find_proc_by_obj(process)
|
||||
util.get_debugger().HandleCommand(f'target delete 0')
|
||||
exec_convert_errors(f'target delete 0')
|
||||
|
||||
|
||||
@REGISTRY.method(action='connect')
|
||||
@REGISTRY.method(action='connect', display="Connect Target")
|
||||
def target(process: sch.Schema('Process'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
util.get_debugger().HandleCommand(f'target select {spec}')
|
||||
exec_convert_errors(f'target select {spec}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display="Attach by Attachable")
|
||||
def attach_obj(process: sch.Schema('Process'), target: sch.Schema('Attachable')):
|
||||
"""Attach the process to the given target."""
|
||||
pid = find_availpid_by_obj(target)
|
||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||
exec_convert_errors(f'process attach -p {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display="Attach by PID")
|
||||
def attach_pid(process: sch.Schema('Process'), pid: int):
|
||||
"""Attach the process to the given target."""
|
||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||
exec_convert_errors(f'process attach -p {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display="Attach by Name")
|
||||
def attach_name(process: sch.Schema('Process'), name: str):
|
||||
"""Attach the process to the given target."""
|
||||
util.get_debugger().HandleCommand(f'process attach -n {name}')
|
||||
exec_convert_errors(f'process attach -n {name}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display="Detach")
|
||||
def detach(process: sch.Schema('Process')):
|
||||
"""Detach the process's target."""
|
||||
util.get_debugger().HandleCommand(f'process detach')
|
||||
exec_convert_errors(f'process detach')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def do_launch(process, file, args, cmd):
|
||||
exec_convert_errors(f'file {file}')
|
||||
if args != '':
|
||||
exec_convert_errors(f'settings set target.run-args {args}')
|
||||
exec_convert_errors(cmd)
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch', display="Launch at Entry")
|
||||
def launch_loader(process: sch.Schema('Process'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
@ -406,14 +431,10 @@ def launch_loader(process: sch.Schema('Process'),
|
||||
|
||||
If 'main' is not defined in the file, this behaves like 'run'.
|
||||
"""
|
||||
util.get_debugger().HandleCommand(f'file {file}')
|
||||
if args != '':
|
||||
util.get_debugger().HandleCommand(
|
||||
f'settings set target.run-args {args}')
|
||||
util.get_debugger().HandleCommand(f'process launch --stop-at-entry')
|
||||
do_launch(process, file, args, 'process launch --stop-at-entry')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
@REGISTRY.method(action='launch', display="Launch and Run")
|
||||
def launch(process: sch.Schema('Process'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
@ -423,31 +444,27 @@ def launch(process: sch.Schema('Process'),
|
||||
The process will not stop until it hits one of your breakpoints, or it is
|
||||
signaled.
|
||||
"""
|
||||
util.get_debugger().HandleCommand(f'file {file}')
|
||||
if args != '':
|
||||
util.get_debugger().HandleCommand(
|
||||
f'settings set target.run-args {args}')
|
||||
util.get_debugger().HandleCommand(f'run')
|
||||
do_launch(process, file, args, 'run')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def kill(process: sch.Schema('Process')):
|
||||
"""Kill execution of the process."""
|
||||
util.get_debugger().HandleCommand('process kill')
|
||||
exec_convert_errors('process kill')
|
||||
|
||||
|
||||
@REGISTRY.method(name='continue', action='resume')
|
||||
def _continue(process: sch.Schema('Process')):
|
||||
"""Continue execution of the process."""
|
||||
util.get_debugger().HandleCommand('process continue')
|
||||
exec_convert_errors('process continue')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def interrupt():
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
util.get_debugger().HandleCommand('process interrupt')
|
||||
exec_convert_errors('process interrupt')
|
||||
# util.get_process().SendAsyncInterrupt()
|
||||
# util.get_debugger().HandleCommand('^c')
|
||||
# exec_convert_errors('^c')
|
||||
# util.get_process().Signal(2)
|
||||
|
||||
|
||||
@ -456,7 +473,7 @@ def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step on instruction exactly."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
util.get_debugger().HandleCommand('thread step-inst')
|
||||
exec_convert_errors('thread step-inst')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_over')
|
||||
@ -464,7 +481,7 @@ def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction, but proceed through subroutine calls."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
util.get_debugger().HandleCommand('thread step-inst-over')
|
||||
exec_convert_errors('thread step-inst-over')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_out')
|
||||
@ -473,27 +490,27 @@ def step_out(thread: sch.Schema('Thread')):
|
||||
if thread is not None:
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
util.get_debugger().HandleCommand('thread step-out')
|
||||
exec_convert_errors('thread step-out')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext')
|
||||
def step_ext(thread: sch.Schema('Thread'), address: Address):
|
||||
@REGISTRY.method(action='step_ext', display="Advance")
|
||||
def step_advance(thread: sch.Schema('Thread'), address: Address):
|
||||
"""Continue execution up to the given address."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
offset = thread.trace.memory_mapper.map_back(t.process, address)
|
||||
util.get_debugger().HandleCommand(f'thread until -a {offset}')
|
||||
exec_convert_errors(f'thread until -a {offset}')
|
||||
|
||||
|
||||
@REGISTRY.method(name='return', action='step_ext')
|
||||
def _return(thread: sch.Schema('Thread'), value: int=None):
|
||||
@REGISTRY.method(action='step_ext', display="Return")
|
||||
def step_return(thread: sch.Schema('Thread'), value: int=None):
|
||||
"""Skip the remainder of the current function."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
if value is None:
|
||||
util.get_debugger().HandleCommand('thread return')
|
||||
exec_convert_errors('thread return')
|
||||
else:
|
||||
util.get_debugger().HandleCommand(f'thread return {value}')
|
||||
exec_convert_errors(f'thread return {value}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
@ -501,14 +518,14 @@ def break_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a breakpoint."""
|
||||
proc = find_proc_by_obj(process)
|
||||
offset = process.trace.memory_mapper.map_back(proc, address)
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -a 0x{offset:x}')
|
||||
exec_convert_errors(f'breakpoint set -a 0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_expression(expression: str):
|
||||
"""Set a breakpoint."""
|
||||
# TODO: Escape?
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -r {expression}')
|
||||
exec_convert_errors(f'breakpoint set -r {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
@ -516,14 +533,14 @@ def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
proc = find_proc_by_obj(process)
|
||||
offset = process.trace.memory_mapper.map_back(proc, address)
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -H -a 0x{offset:x}')
|
||||
exec_convert_errors(f'breakpoint set -H -a 0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_expression(expression: str):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
# TODO: Escape?
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -H -name {expression}')
|
||||
exec_convert_errors(f'breakpoint set -H -name {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
@ -533,15 +550,16 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
sz = range.length()
|
||||
util.get_debugger().HandleCommand(
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression -s {sz} -w read -- {offset_start}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_expression(expression: str):
|
||||
def break_read_expression(expression: str, size=None):
|
||||
"""Set a read watchpoint."""
|
||||
util.get_debugger().HandleCommand(
|
||||
f'watchpoint set expression -w read -- {expression}')
|
||||
size_part = '' if size is None else f'-s {size}'
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression {size_part} -w read -- {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
@ -551,15 +569,16 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
sz = range.length()
|
||||
util.get_debugger().HandleCommand(
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression -s {sz} -- {offset_start}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_expression(expression: str):
|
||||
def break_write_expression(expression: str, size=None):
|
||||
"""Set a watchpoint."""
|
||||
util.get_debugger().HandleCommand(
|
||||
f'watchpoint set expression -- {expression}')
|
||||
size_part = '' if size is None else f'-s {size}'
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression {size_part} -- {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
@ -569,21 +588,22 @@ def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
sz = range.length()
|
||||
util.get_debugger().HandleCommand(
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression -s {sz} -w read_write -- {offset_start}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_expression(expression: str):
|
||||
def break_access_expression(expression: str, size=None):
|
||||
"""Set an access watchpoint."""
|
||||
util.get_debugger().HandleCommand(
|
||||
f'watchpoint set expression -w read_write -- {expression}')
|
||||
size_part = '' if size is None else f'-s {size}'
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression {size_part} -w read_write -- {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_ext')
|
||||
@REGISTRY.method(action='break_ext', display="Break on Exception")
|
||||
def break_exception(lang: str):
|
||||
"""Set a catchpoint."""
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -E {lang}')
|
||||
exec_convert_errors(f'breakpoint set -E {lang}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
@ -605,7 +625,7 @@ def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabl
|
||||
"""Toggle a breakpoint location."""
|
||||
bptnum, locnum = find_bptlocnum_by_obj(location)
|
||||
cmd = 'enable' if enabled else 'disable'
|
||||
util.get_debugger().HandleCommand(f'breakpoint {cmd} {bptnum}.{locnum}')
|
||||
exec_convert_errors(f'breakpoint {cmd} {bptnum}.{locnum}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
@ -613,7 +633,7 @@ def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')):
|
||||
"""Delete a watchpoint."""
|
||||
wpt = find_wpt_by_obj(watchpoint)
|
||||
wptnum = wpt.GetID()
|
||||
util.get_debugger().HandleCommand(f'watchpoint delete {wptnum}')
|
||||
exec_convert_errors(f'watchpoint delete {wptnum}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
@ -621,7 +641,7 @@ def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
bptnum = bpt.GetID()
|
||||
util.get_debugger().HandleCommand(f'breakpoint delete {bptnum}')
|
||||
exec_convert_errors(f'breakpoint delete {bptnum}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@ -638,7 +658,7 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||
if result.Succeeded():
|
||||
return
|
||||
print(f"Could not read 0x{offset_start:x}: {result}")
|
||||
util.get_debugger().HandleCommand(
|
||||
exec_convert_errors(
|
||||
f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error')
|
||||
|
||||
|
||||
@ -660,5 +680,5 @@ def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||
reg = find_reg_by_name(f, mname)
|
||||
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
|
||||
arr = '{' + ','.join(str(b) for b in mval) + '}'
|
||||
util.get_debugger().HandleCommand(
|
||||
exec_convert_errors(
|
||||
f'expr ((unsigned char[{size}])${mname}) = {arr};')
|
||||
|
@ -54,7 +54,6 @@
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="WatchpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="WatchpointSpecContainer" />
|
||||
<element schema="WatchpointSpec" />
|
||||
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
@ -107,7 +106,8 @@
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="Commands" schema="STRING" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
@ -131,7 +131,8 @@
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
@ -241,6 +242,8 @@
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
|
@ -21,7 +21,7 @@ import sys
|
||||
import lldb
|
||||
|
||||
|
||||
LldbVersion = namedtuple('LldbVersion', ['full', 'major', 'minor'])
|
||||
LldbVersion = namedtuple('LldbVersion', ['display', 'full', 'major', 'minor'])
|
||||
|
||||
|
||||
def _compute_lldb_ver():
|
||||
@ -32,7 +32,7 @@ def _compute_lldb_ver():
|
||||
else:
|
||||
full = top.split('-')[1] # "lldb-x.y.z"
|
||||
major, minor = full.split('.')[:2]
|
||||
return LldbVersion(full, int(major), int(minor))
|
||||
return LldbVersion(top, full, int(major), int(minor))
|
||||
|
||||
|
||||
LLDB_VERSION = _compute_lldb_ver()
|
||||
@ -150,8 +150,11 @@ class RegionInfoReader(object):
|
||||
|
||||
def full_mem(self):
|
||||
# TODO: This may not work for Harvard architectures
|
||||
sizeptr = int(parse_and_eval('sizeof(void*)')) * 8
|
||||
return Region(0, 1 << sizeptr, 0, None, 'full memory')
|
||||
try:
|
||||
sizeptr = int(parse_and_eval('sizeof(void*)')) * 8
|
||||
return Region(0, 1 << sizeptr, 0, None, 'full memory')
|
||||
except ValueError:
|
||||
return Region(0, 1 << 64, 0, None, 'full memory')
|
||||
|
||||
|
||||
def _choose_region_info_reader():
|
||||
|
@ -35,6 +35,12 @@ public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtil
|
||||
return DummyProc.which("expSpin");
|
||||
}
|
||||
},
|
||||
READ {
|
||||
@Override
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expRead");
|
||||
}
|
||||
},
|
||||
FORK_EXIT {
|
||||
@Override
|
||||
public String getCommandLine() {
|
||||
|
@ -19,8 +19,7 @@ import java.util.Objects;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
|
||||
public class ListenTraceRmiScript extends GhidraScript {
|
||||
|
||||
@ -42,8 +41,18 @@ public class ListenTraceRmiScript extends GhidraScript {
|
||||
TraceRmiConnection connection = acceptor.accept();
|
||||
println("Connection from " + connection.getRemoteAddress());
|
||||
|
||||
while (askYesNo("Execute?", "Execute 'echo test'?")) {
|
||||
connection.getMethods().get("execute").invoke(Map.of("cmd", "echo test"));
|
||||
RemoteMethod execute = connection.getMethods().get("execute");
|
||||
if (execute == null) {
|
||||
printerr("No execute method!");
|
||||
}
|
||||
while (true) {
|
||||
String cmd = askString("Execute", "command?");
|
||||
try {
|
||||
execute.invoke(Map.of("cmd", cmd));
|
||||
}
|
||||
catch (TraceRmiError e) {
|
||||
printerr(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,11 @@ class Receiver(Thread):
|
||||
dbg_seq = 0
|
||||
while not self._is_shutdown:
|
||||
#print("Receiving message")
|
||||
reply = recv_delimited(self.client.s, bufs.RootMessage(), dbg_seq)
|
||||
try:
|
||||
reply = recv_delimited(self.client.s, bufs.RootMessage(), dbg_seq)
|
||||
except BaseException as e:
|
||||
self._is_shutdown = True
|
||||
return
|
||||
#print(f"Got one: {reply.WhichOneof('msg')}")
|
||||
dbg_seq += 1
|
||||
try:
|
||||
@ -333,6 +337,14 @@ class Trace(object):
|
||||
return self.client._delete_bytes(self.id, snap, range)
|
||||
|
||||
def put_registers(self, space, values, snap=None):
|
||||
"""
|
||||
TODO
|
||||
|
||||
values is a dictionary, where each key is a a register name, and the
|
||||
value is a byte array. No matter the target architecture, the value is
|
||||
given in big-endian byte order.
|
||||
"""
|
||||
|
||||
if snap is None:
|
||||
snap = self.snap()
|
||||
return self.client._put_registers(self.id, snap, space, values)
|
||||
|
@ -160,6 +160,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
DebuggerObjectActionContext ctx) {
|
||||
Set<TraceModule> result = new HashSet<>();
|
||||
for (TraceObjectValue value : ctx.getObjectValues()) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
TraceObject child = value.getChild();
|
||||
TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
|
||||
if (module != null) {
|
||||
@ -179,6 +182,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
DebuggerObjectActionContext ctx) {
|
||||
Set<TraceSection> result = new HashSet<>();
|
||||
for (TraceObjectValue value : ctx.getObjectValues()) {
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
TraceObject child = value.getChild();
|
||||
TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
|
||||
if (module != null) {
|
||||
|
@ -49,10 +49,11 @@ task testSpecimenWin_x86_64 {
|
||||
|
||||
task testSpecimenLinux_x86_64 {
|
||||
dependsOn 'expCloneExecExecutable'//Linux_x86_64Executable'
|
||||
dependsOn 'expCloneExitExecutable'//Linux_x86_64Executable'
|
||||
dependsOn 'expCloneExitLinux_x86_64Executable'
|
||||
//dependsOn 'expCloneSpinExecutable'//Linux_x86_64Executable'
|
||||
dependsOn 'expForkExecutable'//Linux_x86_64Executable'
|
||||
dependsOn 'expPrintLinux_x86_64Executable'
|
||||
dependsOn 'expReadLinux_x86_64Executable'
|
||||
dependsOn 'expSpinLinux_x86_64Executable'
|
||||
//dependsOn 'expTypesExecutable'//Linux_x86_64Executable'
|
||||
dependsOn 'expRegistersLinux_x86_64Executable'
|
||||
@ -67,6 +68,12 @@ task testSpecimenLinux_x86_64 {
|
||||
}
|
||||
}
|
||||
|
||||
task testSpecimenMac_arm_64 {
|
||||
dependsOn 'expCloneExitMac_arm_64Executable'
|
||||
dependsOn 'expPrintMac_arm_64Executable'
|
||||
dependsOn 'expReadMac_arm_64Executable'
|
||||
}
|
||||
|
||||
// TODO: testSpecimenMac_x86_64 (Intel)
|
||||
// will likely need to codesign them to grant debugee-entitlement
|
||||
|
||||
@ -91,6 +98,7 @@ model {
|
||||
expCloneExit(NativeExecutableSpec) {
|
||||
targetPlatform "linux_x86_64"
|
||||
//targetPlatform "linux_x86_32" // TODO: Test on these
|
||||
targetPlatform "mac_arm_64"
|
||||
}
|
||||
expCloneSpin(NativeExecutableSpec) {
|
||||
targetPlatform "linux_x86_64"
|
||||
@ -105,6 +113,11 @@ model {
|
||||
//targetPlatform "linux_x86_32" // TODO: Test on these
|
||||
targetPlatform "win_x86_64"
|
||||
targetPlatform "win_x86_32" // TODO: Test on these
|
||||
targetPlatform "mac_arm_64"
|
||||
}
|
||||
expRead(NativeExecutableSpec) {
|
||||
targetPlatform "linux_x86_64"
|
||||
targetPlatform "mac_arm_64"
|
||||
}
|
||||
expSpin(NativeExecutableSpec) {
|
||||
targetPlatform "linux_x86_64"
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
pthread_t thread;
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
#define DLLEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLLEXPORT
|
||||
#define OutputDebugString(out) printf("%s\n", out)
|
||||
#define OutputDebugString(out) puts(out)
|
||||
#endif
|
||||
|
||||
DLLEXPORT volatile char overwrite[] = "Hello, World!";
|
||||
|
21
Ghidra/Debug/Framework-Debugging/src/expRead/c/expRead.c
Normal file
21
Ghidra/Debug/Framework-Debugging/src/expRead/c/expRead.c
Normal file
@ -0,0 +1,21 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
char c;
|
||||
read(0, &c, sizeof(c));
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
package ghidra.pty;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class StreamPumper extends Thread {
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
|
||||
public StreamPumper(InputStream in, OutputStream out) {
|
||||
setDaemon(true);
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] buf = new byte[1024];
|
||||
try {
|
||||
while (true) {
|
||||
int len = in.read(buf);
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
@ -24,9 +24,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.script.AskDialog;
|
||||
import ghidra.pty.Pty;
|
||||
import ghidra.pty.*;
|
||||
import ghidra.pty.PtyChild.Echo;
|
||||
import ghidra.pty.PtySession;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@ -49,33 +48,6 @@ public class SshPtyTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
return dialog.getValueAsString();
|
||||
}
|
||||
|
||||
public static class StreamPumper extends Thread {
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
|
||||
public StreamPumper(InputStream in, OutputStream out) {
|
||||
setDaemon(true);
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] buf = new byte[1024];
|
||||
try {
|
||||
while (true) {
|
||||
int len = in.read(buf);
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionBash() throws IOException, InterruptedException {
|
||||
try (Pty pty = factory.openpty()) {
|
||||
|
@ -20,7 +20,8 @@ import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
@ -28,8 +29,11 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.output.TeeOutputStream;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
@ -45,6 +49,7 @@ import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
import ghidra.framework.plugintool.util.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
import ghidra.pty.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
@ -54,56 +59,70 @@ import ghidra.util.Msg;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
|
||||
record PlatDep(String name, String endian, String lang, String cSpec, String callMne,
|
||||
String intReg, String floatReg) {
|
||||
static final PlatDep ARM64 =
|
||||
new PlatDep("arm64", "little", "AARCH64:LE:64:v8A", "default", "bl", "x0", "s0");
|
||||
static final PlatDep X8664 = // Note AT&T callq
|
||||
new PlatDep("x86_64", "little", "x86:LE:64:default", "gcc", "callq", "rax", "st0");
|
||||
}
|
||||
|
||||
public static final PlatDep PLAT = computePlat();
|
||||
|
||||
static PlatDep computePlat() {
|
||||
return switch (System.getProperty("os.arch")) {
|
||||
case "aarch64" -> PlatDep.ARM64;
|
||||
case "x86" -> PlatDep.X8664;
|
||||
case "amd64" -> PlatDep.X8664;
|
||||
default -> throw new AssertionError(
|
||||
"Unrecognized arch: " + System.getProperty("os.arch"));
|
||||
};
|
||||
}
|
||||
|
||||
static String getSpecimenClone() {
|
||||
return DummyProc.which("expCloneExit");
|
||||
}
|
||||
|
||||
static String getSpecimenPrint() {
|
||||
return DummyProc.which("expPrint");
|
||||
}
|
||||
|
||||
static String getSpecimenRead() {
|
||||
return DummyProc.which("expRead");
|
||||
}
|
||||
|
||||
/**
|
||||
* Some features have to be disabled to avoid permissions issues in the test container. Namely,
|
||||
* don't try to disable ASLR.
|
||||
*
|
||||
* Color codes mess up the address parsing.
|
||||
*/
|
||||
public static final String PREAMBLE = """
|
||||
script import ghidralldb
|
||||
settings set use-color false
|
||||
settings set target.disable-aslr false
|
||||
""";
|
||||
// Connecting should be the first thing the script does, so use a tight timeout.
|
||||
protected static final int CONNECT_TIMEOUT_MS = 3000;
|
||||
protected static final int TIMEOUT_SECONDS = 300;
|
||||
protected static final int QUIT_TIMEOUT_MS = 1000;
|
||||
public static final String INSTRUMENT_STOPPED =
|
||||
"""
|
||||
ghidra_trace_txopen "Fake" 'ghidra_trace_create_obj Processes[1]'
|
||||
define do-set-stopped
|
||||
ghidra_trace_set_value Processes[1] _state '"STOPPED"'
|
||||
end
|
||||
define set-stopped
|
||||
ghidra_trace_txopen Stopped do-set-stopped
|
||||
end
|
||||
#lldb.debugger.HandleCommand('target stop-hook add -P ghidralldb.hooks.StopHook')
|
||||
#python lldb.events.stop.connect(lambda e: lldb.execute("set-stopped"))""";
|
||||
public static final String INSTRUMENT_RUNNING =
|
||||
"""
|
||||
ghidra_trace_txopen "Fake" 'ghidra_trace_create_obj Processes[1]'
|
||||
define do-set-running
|
||||
ghidra_trace_set_value Processes[1] _state '"RUNNING"'
|
||||
end
|
||||
define set-running
|
||||
ghidra_trace_txopen Running do-set-running
|
||||
end
|
||||
#lldb.debugger.HandleCommand('target stop-hook add -P ghidralldb.hooks.StopHook')
|
||||
#python lldb.events.cont.connect(lambda e: lldb.execute("set-running"))""";
|
||||
|
||||
protected TraceRmiService traceRmi;
|
||||
private Path lldbPath;
|
||||
private Path outFile;
|
||||
private Path errFile;
|
||||
|
||||
// @BeforeClass
|
||||
public static void setupPython() throws Throwable {
|
||||
new ProcessBuilder("gradle", "Debugger-agent-lldb:assemblePyPackage")
|
||||
.directory(TestApplicationUtils.getInstallationDirectory())
|
||||
.inheritIO()
|
||||
.start()
|
||||
.waitFor();
|
||||
new ProcessBuilder("gradle",
|
||||
"Debugger-rmi-trace:assemblePyPackage",
|
||||
"Debugger-agent-lldb:assemblePyPackage")
|
||||
.directory(TestApplicationUtils.getInstallationDirectory())
|
||||
.inheritIO()
|
||||
.start()
|
||||
.waitFor();
|
||||
}
|
||||
|
||||
protected void setPythonPath(ProcessBuilder pb) throws IOException {
|
||||
protected void setPythonPath(Map<String, String> env) throws IOException {
|
||||
String sep =
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
|
||||
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
|
||||
@ -111,7 +130,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-lldb",
|
||||
"build/pypkg/src").getAbsolutePath();
|
||||
String add = rmiPyPkg + sep + gdbPyPkg;
|
||||
pb.environment().compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
|
||||
env.compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
|
||||
}
|
||||
|
||||
@Before
|
||||
@ -124,8 +143,6 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
catch (RuntimeException e) {
|
||||
lldbPath = Paths.get(DummyProc.which("lldb"));
|
||||
}
|
||||
outFile = Files.createTempFile("lldbout", null);
|
||||
errFile = Files.createTempFile("lldberr", null);
|
||||
}
|
||||
|
||||
protected void addAllDebuggerPlugins() throws PluginException {
|
||||
@ -156,71 +173,70 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
throw new AssertionError("Unhandled address type " + address);
|
||||
}
|
||||
|
||||
protected record LldbResult(boolean timedOut, int exitCode, String stdout, String stderr) {
|
||||
protected record LldbResult(boolean timedOut, int exitCode, String out) {
|
||||
protected String handle() {
|
||||
if (!"".equals(stderr) || (0 != exitCode && 143 != exitCode)) {
|
||||
throw new LldbError(exitCode, stdout, stderr);
|
||||
if (0 != exitCode && 143 != exitCode) {
|
||||
throw new LldbError(exitCode, out);
|
||||
}
|
||||
return stdout;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
protected record ExecInLldb(Process lldb, CompletableFuture<LldbResult> future) {
|
||||
}
|
||||
protected record ExecInLldb(Pty pty, PtySession lldb, CompletableFuture<LldbResult> future,
|
||||
Thread pumper) {}
|
||||
|
||||
@SuppressWarnings("resource") // Do not close stdin
|
||||
protected ExecInLldb execInLldb(String script) throws IOException {
|
||||
ProcessBuilder pb = new ProcessBuilder(lldbPath.toString());
|
||||
setPythonPath(pb);
|
||||
Pty pty = PtyFactory.local().openpty();
|
||||
Map<String, String> env = new HashMap<>(System.getenv());
|
||||
setPythonPath(env);
|
||||
env.put("TERM", "xterm-256color");
|
||||
ByteArrayOutputStream capture = new ByteArrayOutputStream();
|
||||
OutputStream tee = new TeeOutputStream(System.out, capture);
|
||||
Thread pumper = new StreamPumper(pty.getParent().getInputStream(), tee);
|
||||
pumper.start();
|
||||
PtySession lldbSession = pty.getChild().session(new String[] { lldbPath.toString() }, env);
|
||||
|
||||
// If commands come from file, LLDB will quit after EOF.
|
||||
Msg.info(this, "outFile: " + outFile);
|
||||
Msg.info(this, "errFile: " + errFile);
|
||||
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
|
||||
pb.redirectOutput(outFile.toFile());
|
||||
pb.redirectError(errFile.toFile());
|
||||
Process lldbProc = pb.start();
|
||||
OutputStream stdin = lldbProc.getOutputStream();
|
||||
OutputStream stdin = pty.getParent().getOutputStream();
|
||||
stdin.write(script.getBytes());
|
||||
stdin.flush();
|
||||
return new ExecInLldb(lldbProc, CompletableFuture.supplyAsync(() -> {
|
||||
return new ExecInLldb(pty, lldbSession, CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
if (!lldbProc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||
Msg.error(this, "Timed out waiting for LLDB");
|
||||
lldbProc.destroyForcibly();
|
||||
lldbProc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
return new LldbResult(true, -1, Files.readString(outFile),
|
||||
Files.readString(errFile));
|
||||
}
|
||||
Msg.info(this, "LLDB exited with code " + lldbProc.exitValue());
|
||||
return new LldbResult(false, lldbProc.exitValue(), Files.readString(outFile),
|
||||
Files.readString(errFile));
|
||||
int exitVal = lldbSession.waitExited(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
Msg.info(this, "LLDB exited with code " + exitVal);
|
||||
return new LldbResult(false, exitVal, capture.toString());
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
return new LldbResult(true, -1, capture.toString());
|
||||
}
|
||||
catch (Exception e) {
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
finally {
|
||||
lldbProc.destroyForcibly();
|
||||
try {
|
||||
pty.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.warn(this, "Couldn't close pty: " + e);
|
||||
}
|
||||
lldbSession.destroyForcibly();
|
||||
pumper.interrupt();
|
||||
}
|
||||
}));
|
||||
}), pumper);
|
||||
}
|
||||
|
||||
public static class LldbError extends RuntimeException {
|
||||
public final int exitCode;
|
||||
public final String stdout;
|
||||
public final String stderr;
|
||||
public final String out;
|
||||
|
||||
public LldbError(int exitCode, String stdout, String stderr) {
|
||||
public LldbError(int exitCode, String out) {
|
||||
super("""
|
||||
exitCode=%d:
|
||||
----stdout----
|
||||
----out----
|
||||
%s
|
||||
----stderr----
|
||||
%s
|
||||
""".formatted(exitCode, stdout, stderr));
|
||||
""".formatted(exitCode, out));
|
||||
this.exitCode = exitCode;
|
||||
this.stdout = stdout;
|
||||
this.stderr = stderr;
|
||||
this.out = out;
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,17 +266,32 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true));
|
||||
}
|
||||
|
||||
public Object evaluate(String expr) {
|
||||
RemoteMethod evaluate = getMethod("evaluate");
|
||||
return evaluate.invoke(Map.of("expr", expr));
|
||||
}
|
||||
|
||||
public Object pyeval(String expr) {
|
||||
RemoteMethod pyeval = getMethod("pyeval");
|
||||
return pyeval.invoke(Map.of("expr", expr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
Msg.info(this, "Cleaning up lldb");
|
||||
exec.lldb().destroy();
|
||||
execute("settings set auto-confirm true");
|
||||
exec.pty.getParent().getOutputStream().write("""
|
||||
quit
|
||||
""".getBytes());
|
||||
try {
|
||||
LldbResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
r.handle();
|
||||
waitForPass(() -> assertTrue(connection.isClosed()));
|
||||
}
|
||||
finally {
|
||||
exec.pty.close();
|
||||
exec.lldb.destroyForcibly();
|
||||
exec.pumper.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,8 +307,10 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
return new LldbAndConnection(exec, connection);
|
||||
}
|
||||
catch (SocketTimeoutException e) {
|
||||
exec.pty.close();
|
||||
exec.lldb.destroyForcibly();
|
||||
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||
exec.pumper.interrupt();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -285,7 +318,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
protected LldbAndConnection startAndConnectLldb() throws Exception {
|
||||
return startAndConnectLldb(addr -> """
|
||||
%s
|
||||
ghidra_trace_connect %s
|
||||
ghidra trace connect %s
|
||||
""".formatted(PREAMBLE, addr));
|
||||
}
|
||||
|
||||
@ -294,36 +327,56 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
throws Exception {
|
||||
LldbAndConnection conn = startAndConnectLldb(scriptSupplier);
|
||||
LldbResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
conn.exec.pty.close();
|
||||
conn.exec.pumper.interrupt();
|
||||
String stdout = r.handle();
|
||||
waitForPass(() -> assertTrue(conn.connection.isClosed()));
|
||||
return stdout;
|
||||
}
|
||||
|
||||
protected void waitStopped() {
|
||||
protected void waitState(String state) {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
|
||||
for (int i = 0; i < 5; i++) {
|
||||
try {
|
||||
waitForPass(() -> {
|
||||
Long snap = tb.trace.getTimeManager().getMaxSnap();
|
||||
assertEquals(state, tb.objValue(proc, snap != null ? snap : 0, "_state"));
|
||||
});
|
||||
break;
|
||||
}
|
||||
catch (AssertionError e) {
|
||||
if (i == 4) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
waitTxDone();
|
||||
}
|
||||
|
||||
protected void waitRunning() {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
|
||||
waitTxDone();
|
||||
protected void waitStopped(LldbAndConnection conn) {
|
||||
waitForPass(() -> assertEquals(Boolean.TRUE,
|
||||
conn.pyeval("util.get_debugger().GetTargetAtIndex(0).GetProcess().is_stopped")));
|
||||
// waitState("STOPPED");
|
||||
}
|
||||
|
||||
protected void waitRunning(LldbAndConnection conn) {
|
||||
waitForPass(() -> assertEquals(Boolean.TRUE,
|
||||
conn.pyeval("util.get_debugger().GetTargetAtIndex(0).GetProcess().is_running")));
|
||||
// waitState("RUNNING");
|
||||
}
|
||||
|
||||
protected String extractOutSection(String out, String head) {
|
||||
String[] split = out.split("\n");
|
||||
String[] split = out.replace("\r", "").split("\n");
|
||||
String xout = "";
|
||||
for (String s : split) {
|
||||
if (!s.startsWith("(lldb)") && !s.equals("")) {
|
||||
if (!s.startsWith("(lldb)") && !s.contains("script print(") && !s.equals("")) {
|
||||
xout += s + "\n";
|
||||
}
|
||||
}
|
||||
return xout.split(head)[1].split("---")[0].replace("(lldb)", "").trim();
|
||||
}
|
||||
|
||||
record MemDump(long address, byte[] data) {
|
||||
}
|
||||
record MemDump(long address, byte[] data) {}
|
||||
|
||||
protected MemDump parseHexDump(String dump) throws IOException {
|
||||
// First, get the address. Assume contiguous, so only need top line.
|
||||
@ -348,13 +401,6 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
return new MemDump(address, buf.toByteArray());
|
||||
}
|
||||
|
||||
record RegDump() {
|
||||
}
|
||||
|
||||
protected RegDump parseRegDump(String dump) {
|
||||
return new RegDump();
|
||||
}
|
||||
|
||||
protected ManagedDomainObject openDomainObject(String path) throws Exception {
|
||||
DomainFile df = env.getProject().getProjectData().getFile(path);
|
||||
assertNotNull(df);
|
||||
@ -376,6 +422,14 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertLocalOs(String actual) {
|
||||
assertThat(actual, Matchers.startsWith(switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
||||
case LINUX -> "linux";
|
||||
case MAC_OS_X -> "macos";
|
||||
default -> throw new AssertionError("What OS?");
|
||||
}));
|
||||
}
|
||||
|
||||
protected void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
|
||||
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
|
||||
assertEquals(key, locVal.getEntryKey());
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,8 @@
|
||||
*/
|
||||
package agent.lldb.rmi;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@ -41,7 +42,7 @@ import ghidra.trace.model.time.TraceSnapshot;
|
||||
|
||||
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
|
||||
public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
private static final long RUN_TIMEOUT_MS = 20000;
|
||||
private static final long RUN_TIMEOUT_MS = 5000;
|
||||
private static final long RETRY_MS = 500;
|
||||
|
||||
record LldbAndTrace(LldbAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable {
|
||||
@ -64,10 +65,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
protected LldbAndTrace startAndSyncLldb() throws Exception {
|
||||
LldbAndConnection conn = startAndConnectLldb();
|
||||
try {
|
||||
// TODO: Why does using 'set arch' cause a hang at quit?
|
||||
conn.execute(
|
||||
"ghidralldb.util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
ManagedDomainObject mdo = waitDomainObject("/New Traces/lldb/noname");
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
return new LldbAndTrace(conn, mdo);
|
||||
@ -102,7 +100,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("continue");
|
||||
waitStopped();
|
||||
waitStopped(conn.conn);
|
||||
txPut(conn, "threads");
|
||||
waitForPass(() -> assertEquals(2,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
||||
@ -131,7 +129,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("continue");
|
||||
waitStopped();
|
||||
waitStopped(conn.conn);
|
||||
waitForPass(() -> {
|
||||
TraceObject inf = tb.objAny("Processes[]");
|
||||
assertNotNull(inf);
|
||||
@ -207,11 +205,11 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
traceManager.openTrace(tb.trace);
|
||||
|
||||
start(conn, "bash");
|
||||
conn.execute("breakpoint set -n read");
|
||||
start(conn, getSpecimenPrint());
|
||||
conn.execute("breakpoint set -n puts");
|
||||
conn.execute("cont");
|
||||
|
||||
waitStopped();
|
||||
waitStopped(conn.conn);
|
||||
waitForPass(() -> assertThat(
|
||||
tb.objValues(lastSnap(conn), "Processes[].Threads[].Stack[]").size(),
|
||||
greaterThan(2)),
|
||||
@ -224,6 +222,8 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
conn.execute("frame select 0");
|
||||
waitForPass(() -> assertEquals("0", frameIndex(traceManager.getCurrentObject())),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("kill");
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,16 +234,16 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
// FWIW, I've already seen this getting exercised in other tests.
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test // LLDB does not provide the necessary events
|
||||
public void testOnMemoryChanged() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
conn.execute("expr *((char*)(void(*)())main) = 0x7f");
|
||||
conn.execute("ghidra_trace_txstart 'Tx'");
|
||||
conn.execute("ghidra_trace_putmem `(void(*)())main` 10");
|
||||
conn.execute("ghidra_trace_txcommit");
|
||||
//conn.execute("ghidra trace tx-start 'Tx'");
|
||||
//conn.execute("ghidra trace putmem '(void(*)())main' 10");
|
||||
//conn.execute("ghidra trace tx-commit");
|
||||
|
||||
waitForPass(() -> {
|
||||
ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
@ -253,15 +253,15 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test // LLDB does not provide the necessary events
|
||||
public void testOnRegisterChanged() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
|
||||
conn.execute("expr $rax = 0x1234");
|
||||
conn.execute("ghidra_trace_txstart 'Tx'");
|
||||
conn.execute("ghidra_trace_putreg");
|
||||
conn.execute("ghidra_trace_txcommit");
|
||||
conn.execute("expr $%s = 0x1234".formatted(PLAT.intReg()));
|
||||
//conn.execute("ghidra trace tx-start 'Tx'");
|
||||
//conn.execute("ghidra trace putreg");
|
||||
//conn.execute("ghidra trace tx-commit");
|
||||
|
||||
String path = "Processes[].Threads[].Stack[].Registers";
|
||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||
@ -269,29 +269,33 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
.getAddressSpace(registers.getCanonicalPath().toString());
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false);
|
||||
waitForPass(() -> assertEquals("1234",
|
||||
regs.getValue(lastSnap(conn), tb.reg("RAX")).getUnsignedValue().toString(16)));
|
||||
regs.getValue(lastSnap(conn), tb.reg(PLAT.intReg()))
|
||||
.getUnsignedValue()
|
||||
.toString(16)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCont() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenRead());
|
||||
|
||||
conn.execute("cont");
|
||||
waitRunning();
|
||||
waitRunning(conn.conn);
|
||||
|
||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
||||
waitForPass(() -> {
|
||||
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("process interrupt");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStop() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
|
||||
TraceObject inf = waitForValue(() -> tb.objAny("Processes[]"));
|
||||
waitForPass(() -> {
|
||||
@ -303,25 +307,22 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testOnExited() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_sync_enable");
|
||||
conn.execute("process launch --stop-at-entry -- -c 'exit 1'");
|
||||
txPut(conn, "processes");
|
||||
start(conn, getSpecimenPrint());
|
||||
|
||||
conn.execute("cont");
|
||||
waitRunning();
|
||||
waitRunning(conn.conn);
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceSnapshot snapshot =
|
||||
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
|
||||
assertNotNull(snapshot);
|
||||
assertEquals("Exited with code 1", snapshot.getDescription());
|
||||
assertEquals("Exited with code 72", snapshot.getDescription());
|
||||
|
||||
TraceObject proc = tb.objAny("Processes[]");
|
||||
assertNotNull(proc);
|
||||
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
||||
assertThat(val, instanceOf(Number.class));
|
||||
assertEquals(1, ((Number) val).longValue());
|
||||
assertEquals(72, ((Number) val).longValue());
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
@ -329,7 +330,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testOnBreakpointCreated() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("breakpoint set -n main");
|
||||
@ -346,9 +347,10 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testOnBreakpointModified() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Breakpoints[]").size());
|
||||
|
||||
//conn.execute("script lldb.debugger.SetAsync(False)");
|
||||
conn.execute("breakpoint set -n main");
|
||||
conn.execute("stepi");
|
||||
TraceObject brk = waitForPass(() -> {
|
||||
@ -357,6 +359,8 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
assertEquals(null, tb.objValue(brk, lastSnap(conn), "Condition"));
|
||||
|
||||
waitStopped(conn.conn);
|
||||
conn.execute("breakpoint modify -c 'x>3'");
|
||||
conn.execute("stepi");
|
||||
// NB: Testing "Commands" requires multi-line input - not clear how to do this
|
||||
@ -372,7 +376,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testOnBreakpointDeleted() throws Exception {
|
||||
try (LldbAndTrace conn = startAndSyncLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("breakpoint set -n main");
|
||||
@ -384,6 +388,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
|
||||
waitStopped(conn.conn);
|
||||
conn.execute("breakpoint delete %s".formatted(brk.getCanonicalPath().index()));
|
||||
conn.execute("stepi");
|
||||
|
||||
@ -395,15 +400,15 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
|
||||
|
||||
private void start(LldbAndTrace conn, String obj) {
|
||||
conn.execute("file " + obj);
|
||||
conn.execute("ghidra_trace_sync_enable");
|
||||
conn.execute("ghidra trace sync-enable");
|
||||
conn.execute("process launch --stop-at-entry");
|
||||
txPut(conn, "processes");
|
||||
}
|
||||
|
||||
private void txPut(LldbAndTrace conn, String obj) {
|
||||
conn.execute("ghidra_trace_txstart 'Tx" + obj + "'");
|
||||
conn.execute("ghidra_trace_put_" + obj);
|
||||
conn.execute("ghidra_trace_txcommit");
|
||||
conn.execute("ghidra trace tx-start 'Tx" + obj + "'");
|
||||
conn.execute("ghidra trace put-" + obj);
|
||||
conn.execute("ghidra trace tx-commit");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,9 +18,11 @@ package agent.lldb.rmi;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
@ -31,6 +33,7 @@ import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.debug.api.tracermi.RemoteMethod;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
@ -59,10 +62,10 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testExecute() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
conn.execute("kill");
|
||||
}
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
// Just confirm it's present
|
||||
}
|
||||
}
|
||||
@ -70,7 +73,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testRefreshAvailable() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txCreate(conn, "Available");
|
||||
|
||||
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
||||
@ -93,13 +96,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testRefreshBreakpoints() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
//waitStopped();
|
||||
//waitStopped(conn);
|
||||
|
||||
conn.execute("breakpoint set --name main");
|
||||
conn.execute("breakpoint set -H --name main");
|
||||
@ -132,14 +135,14 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testRefreshProcBreakpoints() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
txPut(conn, "breakpoints");
|
||||
|
||||
RemoteMethod refreshProcBreakpoints = conn.getMethod("refresh_proc_breakpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject locations =
|
||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||
@ -171,19 +174,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testRefreshProcWatchpoints() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "all");
|
||||
|
||||
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_proc_watchpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject locations =
|
||||
Objects.requireNonNull(tb.objAny("Processes[].Watchpoints"));
|
||||
conn.execute("watchpoint set expression -- `(void(*)())main`");
|
||||
conn.execute("watchpoint set expression -w read -- `(void(*)())main`+-0x20");
|
||||
conn.execute("watchpoint set expression -w read_write -- `(void(*)())main`+0x30");
|
||||
conn.execute("watchpoint set expression -s 1 -- `(void(*)())main`");
|
||||
conn.execute("watchpoint set expression -s 1 -w read -- `(void(*)())main`+-0x20");
|
||||
conn.execute(
|
||||
"watchpoint set expression -s 1 -w read_write -- `(void(*)())main`+0x30");
|
||||
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
||||
|
||||
List<TraceObjectValue> procWatchLocVals = tb.trace.getObjectManager()
|
||||
@ -219,7 +223,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testRefreshProcesses() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txCreate(conn, "Processes");
|
||||
txCreate(conn, "Processes[1]");
|
||||
|
||||
@ -244,21 +248,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
public void testRefreshEnvironment() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
String path = "Processes[].Environment";
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "all");
|
||||
|
||||
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject env = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshEnvironment.invoke(Map.of("node", env));
|
||||
|
||||
// Assumes LLDB on Linux amd64
|
||||
assertEquals("lldb", env.getValue(0, "_debugger").getValue());
|
||||
assertEquals("x86_64", env.getValue(0, "_arch").getValue());
|
||||
assertEquals("linux", env.getValue(0, "_os").getValue());
|
||||
assertEquals("little", env.getValue(0, "_endian").getValue());
|
||||
assertEquals(PLAT.name(), env.getValue(0, "_arch").getValue());
|
||||
assertLocalOs(env.getValue(0, "_os").castValue());
|
||||
assertEquals(PLAT.endian(), env.getValue(0, "_endian").getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,11 +270,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
public void testRefreshThreads() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
String path = "Processes[].Threads";
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
@ -287,15 +290,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
public void testRefreshStack() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
String path = "Processes[].Threads[].Stack";
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("file " + getSpecimenPrint());
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "read");
|
||||
breakAt(conn, "puts");
|
||||
|
||||
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
waitTxDone();
|
||||
|
||||
txPut(conn, "frames");
|
||||
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
|
||||
@ -316,16 +320,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
public void testRefreshRegisters() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
String path = "Processes[].Threads[].Stack[].Registers";
|
||||
start(conn, "bash");
|
||||
conn.execute("ghidra_trace_txstart 'Tx'");
|
||||
conn.execute("ghidra_trace_putreg");
|
||||
conn.execute("ghidra_trace_txcommit");
|
||||
start(conn, getSpecimenPrint());
|
||||
conn.execute("ghidra trace tx-start 'Tx'");
|
||||
conn.execute("ghidra trace putreg");
|
||||
conn.execute("ghidra trace tx-commit");
|
||||
|
||||
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("expr $rax = 0xdeadbeef");
|
||||
conn.execute("expr $%s = 0xdeadbeef".formatted(PLAT.intReg()));
|
||||
|
||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||
refreshRegisters.invoke(Map.of("node", registers));
|
||||
@ -334,9 +338,9 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
|
||||
.getAddressSpace(registers.getCanonicalPath().toString());
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
|
||||
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
||||
RegisterValue intRegVal = regs.getValue(snap, tb.reg(PLAT.intReg()));
|
||||
// LLDB treats registers in arch's endian
|
||||
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
|
||||
assertEquals("deadbeef", intRegVal.getUnsignedValue().toString(16));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -345,11 +349,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
public void testRefreshMappings() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
String path = "Processes[].Memory";
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
@ -367,11 +371,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
public void testRefreshModules() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
String path = "Processes[].Modules";
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
@ -379,27 +383,30 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
|
||||
TraceModule modBash =
|
||||
Unique.assertOne(all.stream().filter(m -> m.getName().contains("bash")));
|
||||
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
|
||||
TraceModule modExpPrint = Unique.assertOne(
|
||||
all.stream().filter(m -> m.getName().contains("expPrint")));
|
||||
assertNotEquals(tb.addr(0), Objects.requireNonNull(modExpPrint.getBase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThread() throws Exception {
|
||||
// This test crashes lldb-1500.0.404.7 on macOS arm64
|
||||
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
// TODO: need to find this file (same issue in LldbHookTests
|
||||
String dproc = DummyProc.which("expCloneExit");
|
||||
conn.execute("file " + dproc);
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "work");
|
||||
|
||||
RemoteMethod activateThread = conn.getMethod("activate_thread");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expCloneExit")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
waitTxDone();
|
||||
|
||||
txPut(conn, "threads");
|
||||
|
||||
@ -415,7 +422,10 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
activateThread.invoke(Map.of("thread", t));
|
||||
String out = conn.executeCapture("thread info");
|
||||
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
|
||||
assertThat(out, containsString("tid = %s".formatted(indices.get(1))));
|
||||
long index = Long.decode(indices.get(1));
|
||||
assertThat(out, Matchers
|
||||
.either(containsString("tid = %s".formatted(index)))
|
||||
.or(containsString("tid = 0x%x".formatted(index))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -424,15 +434,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testActivateFrame() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("file " + getSpecimenPrint());
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "read");
|
||||
breakAt(conn, "puts");
|
||||
|
||||
RemoteMethod activateFrame = conn.getMethod("activate_frame");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
waitTxDone();
|
||||
|
||||
txPut(conn, "frames");
|
||||
|
||||
@ -456,11 +467,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testRemoveProcess() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod removeProcess = conn.getMethod("remove_process");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
@ -474,10 +485,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
|
||||
@Test
|
||||
public void testAttachObj() throws Exception {
|
||||
// Missing specimen for macOS
|
||||
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
|
||||
String sleep = DummyProc.which("expTraceableSleep");
|
||||
try (DummyProc dproc = DummyProc.run(sleep)) {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "available");
|
||||
txPut(conn, "processes");
|
||||
|
||||
@ -499,10 +512,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
|
||||
@Test
|
||||
public void testAttachPid() throws Exception {
|
||||
// Missing specimen for macOS
|
||||
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
|
||||
String sleep = DummyProc.which("expTraceableSleep");
|
||||
try (DummyProc dproc = DummyProc.run(sleep)) {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod attachPid = conn.getMethod("attach_pid");
|
||||
@ -522,12 +537,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testDetach() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
//conn.execute("process attach -p %d".formatted(dproc.pid));
|
||||
|
||||
RemoteMethod detach = conn.getMethod("detach");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
@ -543,7 +558,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testLaunchEntry() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod launch = conn.getMethod("launch_loader");
|
||||
@ -553,11 +568,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
launch.invoke(Map.ofEntries(
|
||||
Map.entry("process", proc),
|
||||
Map.entry("file", "bash")));
|
||||
waitStopped();
|
||||
Map.entry("file", getSpecimenPrint())));
|
||||
waitStopped(conn);
|
||||
|
||||
String out = conn.executeCapture("target list");
|
||||
assertThat(out, containsString("bash"));
|
||||
assertThat(out, containsString(getSpecimenPrint()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -565,7 +580,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test //Not clear how to send interrupt
|
||||
public void testLaunch() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod launch = conn.getMethod("launch");
|
||||
@ -575,17 +590,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
launch.invoke(Map.ofEntries(
|
||||
Map.entry("process", proc),
|
||||
Map.entry("file", "bash")));
|
||||
Map.entry("file", getSpecimenRead())));
|
||||
|
||||
txPut(conn, "processes");
|
||||
|
||||
waitRunning();
|
||||
waitRunning(conn);
|
||||
Thread.sleep(100); // Give it plenty of time to block on read
|
||||
|
||||
conn.execute("process interrupt");
|
||||
txPut(conn, "processes");
|
||||
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
String out = conn.executeCapture("bt");
|
||||
assertThat(out, containsString("read"));
|
||||
@ -596,13 +611,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testKill() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod kill = conn.getMethod("kill");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
kill.invoke(Map.of("process", proc));
|
||||
@ -613,95 +628,123 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected void stepToCall(LldbAndConnection conn, RemoteMethod step, TraceObject thread)
|
||||
throws InterruptedException {
|
||||
while (true) {
|
||||
String dis = conn.executeCapture("dis -c1 -s '$pc'");
|
||||
if (dis.contains(PLAT.callMne())) {
|
||||
return;
|
||||
}
|
||||
step.invoke(Map.of("thread", thread));
|
||||
}
|
||||
}
|
||||
|
||||
record FoundHex(int idx, long value) {
|
||||
static FoundHex findHex(List<String> tokens, int start) {
|
||||
for (int i = start; i < tokens.size(); i++) {
|
||||
String tok = tokens.get(i);
|
||||
if (tok.startsWith("0x")) {
|
||||
return new FoundHex(i, Long.decode(tok));
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Could not find 0x");
|
||||
}
|
||||
}
|
||||
|
||||
record CallInstr(long next, long target) {
|
||||
static CallInstr parse(String dis2) {
|
||||
List<String> tokens = List.of(dis2.split("\\s+"));
|
||||
int mneIndex = tokens.indexOf(PLAT.callMne());
|
||||
assertNotEquals("Could not find " + PLAT.callMne(), -1, mneIndex);
|
||||
FoundHex target = FoundHex.findHex(tokens, mneIndex + 1);
|
||||
FoundHex next = FoundHex.findHex(tokens, target.idx + 1);
|
||||
return new CallInstr(next.value, target.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepInto() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
waitTxDone();
|
||||
|
||||
txPut(conn, "threads");
|
||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!conn.executeCapture("dis -c1 -s '$pc'").contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
stepToCall(conn, step_into, thread);
|
||||
|
||||
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
|
||||
// lab0:
|
||||
// -> addr0
|
||||
//
|
||||
// lab1:
|
||||
// addr1
|
||||
long pcNext = Long.decode(dis2.strip().split("\n")[4].strip().split("\\s+")[0]);
|
||||
CallInstr instr = CallInstr.parse(dis2);
|
||||
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
String disAt = conn.executeCapture("dis -c1 -s '$pc'");
|
||||
long pc = Long.decode(disAt.strip().split("\n")[1].strip().split("\\s+")[1]);
|
||||
assertNotEquals(pcNext, pc);
|
||||
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
|
||||
assertEquals(instr.target, pc.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test // Debug information required (at least on macOS arm64)
|
||||
public void testStepOver() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "read");
|
||||
|
||||
RemoteMethod step_over = conn.getMethod("step_over");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
txPut(conn, "threads");
|
||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!conn.executeCapture("dis -c1 -s '$pc'").contains("call")) {
|
||||
step_over.invoke(Map.of("thread", thread));
|
||||
}
|
||||
stepToCall(conn, step_over, thread);
|
||||
|
||||
String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
|
||||
// lab0:
|
||||
// -> addr0
|
||||
// addr1
|
||||
long pcNext = Long.decode(dis2.strip().split("\n")[2].strip().split("\\s+")[0]);
|
||||
System.err.println(dis2);
|
||||
CallInstr instr = CallInstr.parse(dis2);
|
||||
|
||||
// This winds up a step_into if lldb can't place its breakpoint
|
||||
step_over.invoke(Map.of("thread", thread));
|
||||
String disAt = conn.executeCapture("dis -c1 -s '$pc'");
|
||||
long pc = Long.decode(disAt.strip().split("\n")[1].strip().split("\\s+")[1]);
|
||||
assertEquals(pcNext, pc);
|
||||
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
|
||||
assertEquals(instr.next, pc.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//@Test Not obvious "thread until -a" works (and definitely requires debug info")
|
||||
public void testAdvance() throws Exception {
|
||||
//@Test // Debug information required
|
||||
public void testStepAdvance() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_ext = conn.getMethod("step_ext");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
RemoteMethod step_advance = conn.getMethod("step_advance");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
//waitStopped();
|
||||
waitStopped(conn);
|
||||
txPut(conn, "threads");
|
||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
String dis3 = conn.executeCapture("disassemble -c3 -s '$pc'");
|
||||
// TODO: Examine for control transfer?
|
||||
long pcTarget = Long.decode(dis3.strip().split("\n")[2].strip().split("\\s+")[0]);
|
||||
List<String> lines = List.of(dis3.split("\n"));
|
||||
String last = lines.get(lines.size() - 1);
|
||||
FoundHex addr = FoundHex.findHex(List.of(last.split("\\s+")), 0);
|
||||
|
||||
step_ext.invoke(Map.of("thread", thread, "address", tb.addr(pcTarget)));
|
||||
step_advance.invoke(Map.of("thread", thread, "address", tb.addr(addr.value)));
|
||||
|
||||
String dis1 = conn.executeCapture("disassemble -c1 -s '$pc'");
|
||||
long pc = Long.decode(dis1.strip().split("\n")[1].strip().split("\\s+")[1]);
|
||||
assertEquals(pcTarget, pc);
|
||||
String disAt = conn.executeCapture("disassemble -c1 -s '$pc'");
|
||||
FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
|
||||
assertEquals(addr.value, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -709,16 +752,18 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testFinish() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("file " + getSpecimenPrint());
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "read");
|
||||
breakAt(conn, "puts");
|
||||
|
||||
RemoteMethod activate = conn.getMethod("activate_thread");
|
||||
RemoteMethod step_out = conn.getMethod("step_out");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
waitTxDone();
|
||||
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
@ -735,18 +780,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturn() throws Exception {
|
||||
public void testStepReturn() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("file " + getSpecimenPrint());
|
||||
conn.execute("ghidra trace start");
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "read");
|
||||
breakAt(conn, "puts");
|
||||
|
||||
RemoteMethod activate = conn.getMethod("activate_thread");
|
||||
RemoteMethod ret = conn.getMethod("return");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
RemoteMethod ret = conn.getMethod("step_return");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
waitTxDone();
|
||||
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
@ -765,11 +812,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakAddress() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
@ -786,13 +833,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakExpression() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "main"));
|
||||
|
||||
@ -806,13 +853,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
// Are hardware breakpoints available on our VMs?
|
||||
public void testBreakHardwareAddress() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_hw_address");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
@ -827,13 +874,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
//@Test There appear to be issues with hardware register availability in our virtual environments
|
||||
public void testBreakHardwareExpression() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
|
||||
|
||||
@ -848,22 +895,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakReadRange() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_read_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
AddressRange range = tb.range(address, address + 0); // length 1
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
assertThat(out, containsString("0x%x".formatted(address)));
|
||||
assertThat(out, containsString("size = 4"));
|
||||
assertThat(out, containsString("size = 1"));
|
||||
assertThat(out, containsString("type = r"));
|
||||
}
|
||||
}
|
||||
@ -872,14 +919,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakReadExpression() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
|
||||
breakExpression.invoke(Map.of(
|
||||
"expression", "`(void(*)())main`",
|
||||
"size", 1));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
@ -892,22 +941,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakWriteRange() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_write_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
AddressRange range = tb.range(address, address + 0); // length 1
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
assertThat(out, containsString("0x%x".formatted(address)));
|
||||
assertThat(out, containsString("size = 4"));
|
||||
assertThat(out, containsString("size = 1"));
|
||||
assertThat(out, containsString("type = w"));
|
||||
}
|
||||
}
|
||||
@ -916,14 +965,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakWriteExpression() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_write_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
|
||||
breakExpression.invoke(Map.of(
|
||||
"expression", "`(void(*)())main`",
|
||||
"size", 1));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
@ -936,22 +987,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakAccessRange() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_access_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
AddressRange range = tb.range(address, address + 0); // length 1
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
assertThat(out, containsString("0x%x".formatted(address)));
|
||||
assertThat(out, containsString("size = 4"));
|
||||
assertThat(out, containsString("size = 1"));
|
||||
assertThat(out, containsString("type = rw"));
|
||||
}
|
||||
}
|
||||
@ -960,14 +1011,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakAccessExpression() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_access_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
|
||||
breakExpression.invoke(Map.of(
|
||||
"expression", "`(void(*)())main`",
|
||||
"size", 1));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
@ -981,11 +1034,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testBreakException() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExc = conn.getMethod("break_exception");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExc.invoke(Map.of("lang", "C++"));
|
||||
@ -1000,16 +1053,15 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testToggleBreakpoint() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "main");
|
||||
|
||||
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
conn.execute("breakpoint set -n main");
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
|
||||
|
||||
@ -1024,19 +1076,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testToggleBreakpointLocation() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "main");
|
||||
|
||||
RemoteMethod toggleBreakpointLocation = conn.getMethod("toggle_breakpoint_location");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
conn.execute("breakpoint set -n main");
|
||||
txPut(conn, "breakpoints");
|
||||
|
||||
// NB. Requires canonical path. Inf[].Brk[] is a link
|
||||
TraceObject loc = Objects.requireNonNull(tb.objAny("Breakpoints[][]"));
|
||||
|
||||
toggleBreakpointLocation.invoke(Map.of("location", loc, "enabled", false));
|
||||
@ -1050,16 +1100,15 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testDeleteBreakpoint() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
conn.execute("file bash");
|
||||
conn.execute("ghidra_trace_start");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
breakAt(conn, "main");
|
||||
|
||||
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
waitStopped(conn);
|
||||
|
||||
conn.execute("breakpoint set -n main");
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
|
||||
|
||||
@ -1074,15 +1123,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
@Test
|
||||
public void testDeleteWatchpoint() throws Exception {
|
||||
try (LldbAndConnection conn = startAndConnectLldb()) {
|
||||
start(conn, "bash");
|
||||
start(conn, getSpecimenPrint());
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
|
||||
RemoteMethod deleteWatchpoint = conn.getMethod("delete_watchpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) {
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
|
||||
breakExpression.invoke(Map.of(
|
||||
"expression", "`(void(*)())main`",
|
||||
"size", 1));
|
||||
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
|
||||
|
||||
String out = conn.executeCapture("watchpoint list");
|
||||
@ -1101,25 +1152,26 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
|
||||
|
||||
private void start(LldbAndConnection conn, String obj) {
|
||||
conn.execute("file " + obj);
|
||||
conn.execute("ghidra_trace_start");
|
||||
conn.execute("ghidra trace start");
|
||||
conn.execute("process launch --stop-at-entry");
|
||||
}
|
||||
|
||||
private void txPut(LldbAndConnection conn, String obj) {
|
||||
conn.execute("ghidra_trace_txstart 'Tx'");
|
||||
conn.execute("ghidra_trace_put_" + obj);
|
||||
conn.execute("ghidra_trace_txcommit");
|
||||
conn.execute("ghidra trace tx-start 'Tx'");
|
||||
conn.execute("ghidra trace put-" + obj);
|
||||
conn.execute("ghidra trace tx-commit");
|
||||
}
|
||||
|
||||
private void txCreate(LldbAndConnection conn, String path) {
|
||||
conn.execute("ghidra_trace_txstart 'Fake'");
|
||||
conn.execute("ghidra_trace_create_obj %s".formatted(path));
|
||||
conn.execute("ghidra_trace_txcommit");
|
||||
conn.execute("ghidra trace tx-start 'Fake'");
|
||||
conn.execute("ghidra trace create-obj %s".formatted(path));
|
||||
conn.execute("ghidra trace tx-commit");
|
||||
}
|
||||
|
||||
private void breakAt(LldbAndConnection conn, String fn) {
|
||||
conn.execute("ghidra_trace_sync_enable");
|
||||
conn.execute("ghidra trace sync-enable");
|
||||
conn.execute("breakpoint set -n " + fn);
|
||||
conn.execute("script lldb.debugger.SetAsync(False)");
|
||||
conn.execute("run");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user