GP-4415: Lots of lldb trace-rmi fixes

Breakpoint Enabled atribute.
Test fixes on macOS and Linux.
Re-work value conversion a bit.
shlexify commands.
Add method display names.
This commit is contained in:
Dan 2024-03-22 08:56:59 -04:00
parent 523f6e4cbe
commit eb5bf458a4
22 changed files with 1828 additions and 1222 deletions

View file

@ -1506,7 +1506,7 @@ def ghidra_trace_sync_disable(*, is_mi, **kwargs):
""" """
Cease synchronizing the current inferior with the Ghidra trace. 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. automatically remove hooks.
""" """

View file

@ -24,7 +24,7 @@
#@menu-group local #@menu-group local
#@icon icon.debugger #@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#lldb #@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" #@arg :str "Image" "The target binary executable image"
#@args "Arguments" "Command-line arguments to pass to the target" #@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." #@env OPT_LLDB_PATH:str="lldb" "Path to lldb" "The path to lldb. Omit the full path to resolve using the system PATH."

View file

@ -50,8 +50,8 @@ language_map = {
'thumbv7em': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'], 'thumbv7em': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
'armv8': ['ARM:BE:32:v8', 'ARM:LE:32:v8'], 'armv8': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'armv8l': ['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'], 'arm64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
'arm64e': ['ARM:BE:64:v8', 'ARM:LE:64:v8'], 'arm64e': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'], 'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'mips': ['MIPS:BE:32:default', 'MIPS:LE:32:default'], 'mips': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
'mipsr2': ['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 triple = util.get_target().triple
if triple is None: if triple is not None:
return "x86_64" return triple
return find_host_triple()
def get_arch():
triple = find_triple()
return triple.split('-')[0] return triple.split('-')[0]
@ -152,7 +166,6 @@ def get_endian():
parm = util.get_convenience_variable('endian') parm = util.get_convenience_variable('endian')
if parm != 'auto': if parm != 'auto':
return parm return parm
# Once again, we have to hack using the human-readable 'show'
order = util.get_target().GetByteOrder() order = util.get_target().GetByteOrder()
if order is lldb.eByteOrderLittle: if order is lldb.eByteOrderLittle:
return 'little' return 'little'
@ -167,15 +180,11 @@ def get_osabi():
parm = util.get_convenience_variable('osabi') parm = util.get_convenience_variable('osabi')
if not parm in ['auto', 'default']: if not parm in ['auto', 'default']:
return parm return parm
# We have to hack around the fact the LLDB won't give us the current OS ABI triple = find_triple()
# 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
# this is an unfortunate feature of the tests # this is an unfortunate feature of the tests
if triple is None or '-' not in triple: if triple is None or '-' not in triple:
return "default" return "default"
triple = find_triple()
return triple.split('-')[2] return triple.split('-')[2]
@ -274,29 +283,8 @@ class DefaultRegisterMapper(object):
def map_name(self, proc, name): def map_name(self, proc, name):
return 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): def map_value(self, proc, name, value):
try: return RegVal(self.map_name(proc, name), value)
# 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)
def map_name_back(self, proc, name): def map_name_back(self, proc, name):
return name return name

View file

@ -67,13 +67,22 @@ class ProcessState(object):
hashable_frame = (thread.GetThreadID(), frame.GetFrameID()) hashable_frame = (thread.GetThreadID(), frame.GetFrameID())
if first or hashable_frame not in self.visited: if first or hashable_frame not in self.visited:
banks = frame.GetRegisters() banks = frame.GetRegisters()
primary = banks.GetFirstValueByName(commands.DEFAULT_REGISTER_BANK) primary = banks.GetFirstValueByName(
commands.DEFAULT_REGISTER_BANK)
if primary.value is None: if primary.value is None:
primary = banks[0] primary = banks[0]
if primary is not None:
commands.DEFAULT_REGISTER_BANK = primary.name commands.DEFAULT_REGISTER_BANK = primary.name
if primary is not None:
commands.putreg(frame, primary) commands.putreg(frame, primary)
try:
commands.putmem("$pc", "1", result=None) 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) commands.putmem("$sp", "1", result=None)
except BaseException as e:
print(f"Couldn't record page with SP: {e}")
self.visited.add(hashable_frame) self.visited.add(hashable_frame)
if first or self.regions or self.threads or self.modules: if first or self.regions or self.threads or self.modules:
# Sections, memory syscalls, or stack allocations # Sections, memory syscalls, or stack allocations
@ -113,7 +122,7 @@ class BrkState(object):
return self.break_loc_counts.get(b.GetID(), 0) return self.break_loc_counts.get(b.GetID(), 0)
def del_brkloc_count(self, b): 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? return 0 # TODO: Print a warning?
count = self.break_loc_counts[b.GetID()] count = self.break_loc_counts[b.GetID()]
del self.break_loc_counts[b.GetID()] del self.break_loc_counts[b.GetID()]
@ -125,19 +134,32 @@ BRK_STATE = BrkState()
PROC_STATE = {} PROC_STATE = {}
class QuitSentinel(object):
pass
QUIT = QuitSentinel()
def process_event(self, listener, event): def process_event(self, listener, event):
try: try:
desc = util.get_description(event) 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() 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() PROC_STATE[event_process.GetProcessID()] = ProcessState()
rc = event_process.GetBroadcaster().AddListener(listener, ALL_EVENTS) rc = event_process.GetBroadcaster().AddListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for process failed") print("add listener for process failed")
# NB: Calling put_state on running leaves an open transaction # 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) commands.put_state(event_process)
type = event.GetType() type = event.GetType()
if lldb.SBTarget.EventIsTargetEvent(event): if lldb.SBTarget.EventIsTargetEvent(event):
@ -157,6 +179,8 @@ def process_event(self, listener, event):
return on_exited(event) return on_exited(event)
if event_process.is_stopped: if event_process.is_stopped:
return on_stop(event) return on_stop(event)
if event_process.is_running:
return on_cont(event)
return True return True
if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0: if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0:
if event_process.is_stopped: if event_process.is_stopped:
@ -244,6 +268,9 @@ def process_event(self, listener, event):
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0: if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0:
return True return True
if (type & lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived) != 0: 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 return True
if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0: if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0:
return True return True
@ -251,7 +278,7 @@ def process_event(self, listener, event):
return True return True
print("UNKNOWN EVENT") print("UNKNOWN EVENT")
return True return True
except RuntimeError as e: except BaseException as e:
print(e) print(e)
@ -267,50 +294,57 @@ class EventThread(threading.Thread):
target = util.get_target() target = util.get_target()
proc = util.get_process() proc = util.get_process()
rc = cli.GetBroadcaster().AddListener(listener, ALL_EVENTS) rc = cli.GetBroadcaster().AddListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for cli failed") print("add listener for cli failed")
return # return
rc = target.GetBroadcaster().AddListener(listener, ALL_EVENTS) rc = target.GetBroadcaster().AddListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for target failed") print("add listener for target failed")
return # return
rc = proc.GetBroadcaster().AddListener(listener, ALL_EVENTS) rc = proc.GetBroadcaster().AddListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for process failed") print("add listener for process failed")
return # return
# Not sure what effect this logic has # Not sure what effect this logic has
rc = cli.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS) rc = cli.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for cli failed") print("add initial events for cli failed")
return # return
rc = target.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS) rc = target.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for target failed") print("add initial events for target failed")
return # return
rc = proc.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS) rc = proc.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
if rc is False: if not rc:
print("add listener for process failed") print("add initial events for process failed")
return # return
rc = listener.StartListeningForEventClass( rc = listener.StartListeningForEventClass(
util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS) util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS)
if rc is False: if not rc:
print("add listener for threads failed") print("add listener for threads failed")
return # return
# THIS WILL NOT WORK: listener = util.get_debugger().GetListener() # THIS WILL NOT WORK: listener = util.get_debugger().GetListener()
while True: while True:
event_recvd = False event_recvd = False
while event_recvd is False: while not event_recvd:
if listener.WaitForEvent(lldb.UINT32_MAX, self.event): if listener.WaitForEvent(lldb.UINT32_MAX, self.event):
try: try:
self.func(listener, self.event) result = self.func(listener, self.event)
while listener.GetNextEvent(self.event): if result is QUIT:
self.func(listener, self.event) return
event_recvd = True
except BaseException as e: except BaseException as e:
print(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() proc = util.get_process()
if proc is not None and not proc.is_alive: if proc is not None and not proc.is_alive:
break break
@ -445,7 +479,7 @@ def on_memory_changed(event):
def on_register_changed(event): def on_register_changed(event):
print("Register changed: {}".format(dir(event))) # print("Register changed: {}".format(dir(event)))
proc = util.get_process() proc = util.get_process()
if proc.GetProcessID() not in PROC_STATE: if proc.GetProcessID() not in PROC_STATE:
return return
@ -476,7 +510,8 @@ def on_cont(event):
def on_stop(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: if proc.GetProcessID() not in PROC_STATE:
print("not in state") print("not in state")
return return
@ -586,7 +621,7 @@ def on_breakpoint_deleted(b):
notify_others_breaks(proc) notify_others_breaks(proc)
if proc.GetProcessID() not in PROC_STATE: if proc.GetProcessID() not in PROC_STATE:
return return
old_count = BRK_STATE.del_brkloc_count(b.GetID()) old_count = BRK_STATE.del_brkloc_count(b)
trace = commands.STATE.trace trace = commands.STATE.trace
if trace is None: if trace is None:
return return

View file

@ -15,9 +15,11 @@
## ##
from concurrent.futures import Future, ThreadPoolExecutor from concurrent.futures import Future, ThreadPoolExecutor
import re import re
import sys
from ghidratrace import sch from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
import lldb import lldb
from . import commands, util from . import commands, util
@ -228,107 +230,122 @@ def find_bpt_loc_by_obj(object):
return bpt.locations[locnum - 1] # Display is 1-up 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 @REGISTRY.method
def execute(cmd: str, to_string: bool=False): def execute(cmd: str, to_string: bool=False):
"""Execute a CLI command.""" """Execute a CLI command."""
res = lldb.SBCommandReturnObject() # TODO: Check for eCommandInterpreterResultQuitRequested?
util.get_debugger().GetCommandInterpreter().HandleCommand(cmd, res) return exec_convert_errors(cmd, to_string)
if to_string:
if res.Succeeded():
return res.GetOutput()
else:
return res.GetError()
@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')): def refresh_available(node: sch.Schema('AvailableContainer')):
"""List processes on lldb's host system.""" """List processes on lldb's host system."""
with commands.open_tracked_tx('Refresh Available'): 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')): def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
""" """
Refresh the list of breakpoints (including locations for the current Refresh the list of breakpoints (including locations for the current
process). process).
""" """
with commands.open_tracked_tx('Refresh Breakpoints'): 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')): def refresh_processes(node: sch.Schema('ProcessContainer')):
"""Refresh the list of processes.""" """Refresh the list of processes."""
with commands.open_tracked_tx('Refresh 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')): def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
""" """
Refresh the breakpoint locations for the process. Refresh the breakpoints for the process.
In the course of refreshing the locations, the breakpoint list will also be
refreshed.
""" """
with commands.open_tracked_tx('Refresh Breakpoint Locations'): 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')): def refresh_proc_watchpoints(node: sch.Schema('WatchpointContainer')):
""" """
Refresh the watchpoint locations for the process. Refresh the watchpoints for the process.
In the course of refreshing the locations, the watchpoint list will also be
refreshed.
""" """
with commands.open_tracked_tx('Refresh Watchpoint Locations'): 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')): def refresh_environment(node: sch.Schema('Environment')):
"""Refresh the environment descriptors (arch, os, endian).""" """Refresh the environment descriptors (arch, os, endian)."""
with commands.open_tracked_tx('Refresh Environment'): 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')): def refresh_threads(node: sch.Schema('ThreadContainer')):
"""Refresh the list of threads in the process.""" """Refresh the list of threads in the process."""
with commands.open_tracked_tx('Refresh Threads'): 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')): def refresh_stack(node: sch.Schema('Stack')):
"""Refresh the backtrace for the thread.""" """Refresh the backtrace for the thread."""
t = find_thread_by_stack_obj(node) t = find_thread_by_stack_obj(node)
t.process.SetSelectedThread(t) t.process.SetSelectedThread(t)
with commands.open_tracked_tx('Refresh Stack'): 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')): def refresh_registers(node: sch.Schema('RegisterValueContainer')):
"""Refresh the register values for the frame.""" """Refresh the register values for the frame."""
f = find_frame_by_regs_obj(node) f = find_frame_by_regs_obj(node)
f.thread.SetSelectedFrame(f.GetFrameID()) f.thread.SetSelectedFrame(f.GetFrameID())
# TODO: Groups? # TODO: Groups?
with commands.open_tracked_tx('Refresh Registers'): 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')): def refresh_mappings(node: sch.Schema('Memory')):
"""Refresh the list of memory regions for the process.""" """Refresh the list of memory regions for the process."""
with commands.open_tracked_tx('Refresh Memory Regions'): 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')): def refresh_modules(node: sch.Schema('ModuleContainer')):
""" """
Refresh the modules and sections list for the process. 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. This will refresh the sections for all modules, not just the selected one.
""" """
with commands.open_tracked_tx('Refresh Modules'): 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') @REGISTRY.method(action='activate')
def activate_process(process: sch.Schema('Process')): def activate_process(process: sch.Schema('Process')):
"""Switch to the process.""" """Switch to the process."""
# TODO
return return
@ -363,41 +381,48 @@ def activate_frame(frame: sch.Schema('StackFrame')):
def remove_process(process: sch.Schema('Process')): def remove_process(process: sch.Schema('Process')):
"""Remove the process.""" """Remove the process."""
proc = find_proc_by_obj(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): def target(process: sch.Schema('Process'), spec: str):
"""Connect to a target machine or process.""" """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')): def attach_obj(process: sch.Schema('Process'), target: sch.Schema('Attachable')):
"""Attach the process to the given target.""" """Attach the process to the given target."""
pid = find_availpid_by_obj(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): def attach_pid(process: sch.Schema('Process'), pid: int):
"""Attach the process to the given target.""" """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): def attach_name(process: sch.Schema('Process'), name: str):
"""Attach the process to the given target.""" """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')): def detach(process: sch.Schema('Process')):
"""Detach the process's target.""" """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'), def launch_loader(process: sch.Schema('Process'),
file: ParamDesc(str, display='File'), file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''): 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'. If 'main' is not defined in the file, this behaves like 'run'.
""" """
util.get_debugger().HandleCommand(f'file {file}') do_launch(process, file, args, 'process launch --stop-at-entry')
if args != '':
util.get_debugger().HandleCommand(
f'settings set target.run-args {args}')
util.get_debugger().HandleCommand(f'process launch --stop-at-entry')
@REGISTRY.method(action='launch') @REGISTRY.method(action='launch', display="Launch and Run")
def launch(process: sch.Schema('Process'), def launch(process: sch.Schema('Process'),
file: ParamDesc(str, display='File'), file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''): 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 The process will not stop until it hits one of your breakpoints, or it is
signaled. signaled.
""" """
util.get_debugger().HandleCommand(f'file {file}') do_launch(process, file, args, 'run')
if args != '':
util.get_debugger().HandleCommand(
f'settings set target.run-args {args}')
util.get_debugger().HandleCommand(f'run')
@REGISTRY.method @REGISTRY.method
def kill(process: sch.Schema('Process')): def kill(process: sch.Schema('Process')):
"""Kill execution of the process.""" """Kill execution of the process."""
util.get_debugger().HandleCommand('process kill') exec_convert_errors('process kill')
@REGISTRY.method(name='continue', action='resume') @REGISTRY.method(name='continue', action='resume')
def _continue(process: sch.Schema('Process')): def _continue(process: sch.Schema('Process')):
"""Continue execution of the process.""" """Continue execution of the process."""
util.get_debugger().HandleCommand('process continue') exec_convert_errors('process continue')
@REGISTRY.method @REGISTRY.method
def interrupt(): def interrupt():
"""Interrupt the execution of the debugged program.""" """Interrupt the execution of the debugged program."""
util.get_debugger().HandleCommand('process interrupt') exec_convert_errors('process interrupt')
# util.get_process().SendAsyncInterrupt() # util.get_process().SendAsyncInterrupt()
# util.get_debugger().HandleCommand('^c') # exec_convert_errors('^c')
# util.get_process().Signal(2) # 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.""" """Step on instruction exactly."""
t = find_thread_by_obj(thread) t = find_thread_by_obj(thread)
t.process.SetSelectedThread(t) t.process.SetSelectedThread(t)
util.get_debugger().HandleCommand('thread step-inst') exec_convert_errors('thread step-inst')
@REGISTRY.method(action='step_over') @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.""" """Step one instruction, but proceed through subroutine calls."""
t = find_thread_by_obj(thread) t = find_thread_by_obj(thread)
t.process.SetSelectedThread(t) t.process.SetSelectedThread(t)
util.get_debugger().HandleCommand('thread step-inst-over') exec_convert_errors('thread step-inst-over')
@REGISTRY.method(action='step_out') @REGISTRY.method(action='step_out')
@ -473,27 +490,27 @@ def step_out(thread: sch.Schema('Thread')):
if thread is not None: if thread is not None:
t = find_thread_by_obj(thread) t = find_thread_by_obj(thread)
t.process.SetSelectedThread(t) t.process.SetSelectedThread(t)
util.get_debugger().HandleCommand('thread step-out') exec_convert_errors('thread step-out')
@REGISTRY.method(action='step_ext') @REGISTRY.method(action='step_ext', display="Advance")
def step_ext(thread: sch.Schema('Thread'), address: Address): def step_advance(thread: sch.Schema('Thread'), address: Address):
"""Continue execution up to the given address.""" """Continue execution up to the given address."""
t = find_thread_by_obj(thread) t = find_thread_by_obj(thread)
t.process.SetSelectedThread(t) t.process.SetSelectedThread(t)
offset = thread.trace.memory_mapper.map_back(t.process, address) 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') @REGISTRY.method(action='step_ext', display="Return")
def _return(thread: sch.Schema('Thread'), value: int=None): def step_return(thread: sch.Schema('Thread'), value: int=None):
"""Skip the remainder of the current function.""" """Skip the remainder of the current function."""
t = find_thread_by_obj(thread) t = find_thread_by_obj(thread)
t.process.SetSelectedThread(t) t.process.SetSelectedThread(t)
if value is None: if value is None:
util.get_debugger().HandleCommand('thread return') exec_convert_errors('thread return')
else: else:
util.get_debugger().HandleCommand(f'thread return {value}') exec_convert_errors(f'thread return {value}')
@REGISTRY.method(action='break_sw_execute') @REGISTRY.method(action='break_sw_execute')
@ -501,14 +518,14 @@ def break_address(process: sch.Schema('Process'), address: Address):
"""Set a breakpoint.""" """Set a breakpoint."""
proc = find_proc_by_obj(process) proc = find_proc_by_obj(process)
offset = process.trace.memory_mapper.map_back(proc, address) 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') @REGISTRY.method(action='break_sw_execute')
def break_expression(expression: str): def break_expression(expression: str):
"""Set a breakpoint.""" """Set a breakpoint."""
# TODO: Escape? # 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') @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.""" """Set a hardware-assisted breakpoint."""
proc = find_proc_by_obj(process) proc = find_proc_by_obj(process)
offset = process.trace.memory_mapper.map_back(proc, address) 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') @REGISTRY.method(action='break_hw_execute')
def break_hw_expression(expression: str): def break_hw_expression(expression: str):
"""Set a hardware-assisted breakpoint.""" """Set a hardware-assisted breakpoint."""
# TODO: Escape? # 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') @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( offset_start = process.trace.memory_mapper.map_back(
proc, Address(range.space, range.min)) proc, Address(range.space, range.min))
sz = range.length() sz = range.length()
util.get_debugger().HandleCommand( exec_convert_errors(
f'watchpoint set expression -s {sz} -w read -- {offset_start}') f'watchpoint set expression -s {sz} -w read -- {offset_start}')
@REGISTRY.method(action='break_read') @REGISTRY.method(action='break_read')
def break_read_expression(expression: str): def break_read_expression(expression: str, size=None):
"""Set a read watchpoint.""" """Set a read watchpoint."""
util.get_debugger().HandleCommand( size_part = '' if size is None else f'-s {size}'
f'watchpoint set expression -w read -- {expression}') exec_convert_errors(
f'watchpoint set expression {size_part} -w read -- {expression}')
@REGISTRY.method(action='break_write') @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( offset_start = process.trace.memory_mapper.map_back(
proc, Address(range.space, range.min)) proc, Address(range.space, range.min))
sz = range.length() sz = range.length()
util.get_debugger().HandleCommand( exec_convert_errors(
f'watchpoint set expression -s {sz} -- {offset_start}') f'watchpoint set expression -s {sz} -- {offset_start}')
@REGISTRY.method(action='break_write') @REGISTRY.method(action='break_write')
def break_write_expression(expression: str): def break_write_expression(expression: str, size=None):
"""Set a watchpoint.""" """Set a watchpoint."""
util.get_debugger().HandleCommand( size_part = '' if size is None else f'-s {size}'
f'watchpoint set expression -- {expression}') exec_convert_errors(
f'watchpoint set expression {size_part} -- {expression}')
@REGISTRY.method(action='break_access') @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( offset_start = process.trace.memory_mapper.map_back(
proc, Address(range.space, range.min)) proc, Address(range.space, range.min))
sz = range.length() sz = range.length()
util.get_debugger().HandleCommand( exec_convert_errors(
f'watchpoint set expression -s {sz} -w read_write -- {offset_start}') f'watchpoint set expression -s {sz} -w read_write -- {offset_start}')
@REGISTRY.method(action='break_access') @REGISTRY.method(action='break_access')
def break_access_expression(expression: str): def break_access_expression(expression: str, size=None):
"""Set an access watchpoint.""" """Set an access watchpoint."""
util.get_debugger().HandleCommand( size_part = '' if size is None else f'-s {size}'
f'watchpoint set expression -w read_write -- {expression}') 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): def break_exception(lang: str):
"""Set a catchpoint.""" """Set a catchpoint."""
util.get_debugger().HandleCommand(f'breakpoint set -E {lang}') exec_convert_errors(f'breakpoint set -E {lang}')
@REGISTRY.method(action='toggle') @REGISTRY.method(action='toggle')
@ -605,7 +625,7 @@ def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabl
"""Toggle a breakpoint location.""" """Toggle a breakpoint location."""
bptnum, locnum = find_bptlocnum_by_obj(location) bptnum, locnum = find_bptlocnum_by_obj(location)
cmd = 'enable' if enabled else 'disable' 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') @REGISTRY.method(action='delete')
@ -613,7 +633,7 @@ def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')):
"""Delete a watchpoint.""" """Delete a watchpoint."""
wpt = find_wpt_by_obj(watchpoint) wpt = find_wpt_by_obj(watchpoint)
wptnum = wpt.GetID() wptnum = wpt.GetID()
util.get_debugger().HandleCommand(f'watchpoint delete {wptnum}') exec_convert_errors(f'watchpoint delete {wptnum}')
@REGISTRY.method(action='delete') @REGISTRY.method(action='delete')
@ -621,7 +641,7 @@ def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
"""Delete a breakpoint.""" """Delete a breakpoint."""
bpt = find_bpt_by_obj(breakpoint) bpt = find_bpt_by_obj(breakpoint)
bptnum = bpt.GetID() bptnum = bpt.GetID()
util.get_debugger().HandleCommand(f'breakpoint delete {bptnum}') exec_convert_errors(f'breakpoint delete {bptnum}')
@REGISTRY.method @REGISTRY.method
@ -638,7 +658,7 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange):
if result.Succeeded(): if result.Succeeded():
return return
print(f"Could not read 0x{offset_start:x}: {result}") 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') 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) reg = find_reg_by_name(f, mname)
size = int(lldb.parse_and_eval(f'sizeof(${mname})')) size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
arr = '{' + ','.join(str(b) for b in mval) + '}' arr = '{' + ','.join(str(b) for b in mval) + '}'
util.get_debugger().HandleCommand( exec_convert_errors(
f'expr ((unsigned char[{size}])${mname}) = {arr};') f'expr ((unsigned char[{size}])${mname}) = {arr};')

View file

@ -54,7 +54,6 @@
<attribute schema="VOID" /> <attribute schema="VOID" />
</schema> </schema>
<schema name="WatchpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER"> <schema name="WatchpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="WatchpointSpecContainer" />
<element schema="WatchpointSpec" /> <element schema="WatchpointSpec" />
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" /> <attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" /> <attribute name="_value" schema="ANY" hidden="yes" />
@ -107,7 +106,8 @@
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" /> <attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" 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="Commands" schema="STRING" />
<attribute name="Condition" schema="STRING" /> <attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" /> <attribute name="Hit Count" schema="INT" />
@ -131,7 +131,8 @@
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" /> <attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" 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="_range" schema="RANGE" hidden="yes" />
<attribute name="Condition" schema="STRING" /> <attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" /> <attribute name="Hit Count" schema="INT" />
@ -241,6 +242,8 @@
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" /> <attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" /> <attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" 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" /> <attribute schema="VOID" />
</schema> </schema>
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER"> <schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">

View file

@ -21,7 +21,7 @@ import sys
import lldb import lldb
LldbVersion = namedtuple('LldbVersion', ['full', 'major', 'minor']) LldbVersion = namedtuple('LldbVersion', ['display', 'full', 'major', 'minor'])
def _compute_lldb_ver(): def _compute_lldb_ver():
@ -32,7 +32,7 @@ def _compute_lldb_ver():
else: else:
full = top.split('-')[1] # "lldb-x.y.z" full = top.split('-')[1] # "lldb-x.y.z"
major, minor = full.split('.')[:2] 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() LLDB_VERSION = _compute_lldb_ver()
@ -150,8 +150,11 @@ class RegionInfoReader(object):
def full_mem(self): def full_mem(self):
# TODO: This may not work for Harvard architectures # TODO: This may not work for Harvard architectures
try:
sizeptr = int(parse_and_eval('sizeof(void*)')) * 8 sizeptr = int(parse_and_eval('sizeof(void*)')) * 8
return Region(0, 1 << sizeptr, 0, None, 'full memory') 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(): def _choose_region_info_reader():

View file

@ -35,6 +35,12 @@ public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtil
return DummyProc.which("expSpin"); return DummyProc.which("expSpin");
} }
}, },
READ {
@Override
public String getCommandLine() {
return DummyProc.which("expRead");
}
},
FORK_EXIT { FORK_EXIT {
@Override @Override
public String getCommandLine() { public String getCommandLine() {

View file

@ -19,8 +19,7 @@ import java.util.Objects;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin; import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.TraceRmiService; import ghidra.app.services.TraceRmiService;
import ghidra.debug.api.tracermi.TraceRmiAcceptor; import ghidra.debug.api.tracermi.*;
import ghidra.debug.api.tracermi.TraceRmiConnection;
public class ListenTraceRmiScript extends GhidraScript { public class ListenTraceRmiScript extends GhidraScript {
@ -42,8 +41,18 @@ public class ListenTraceRmiScript extends GhidraScript {
TraceRmiConnection connection = acceptor.accept(); TraceRmiConnection connection = acceptor.accept();
println("Connection from " + connection.getRemoteAddress()); println("Connection from " + connection.getRemoteAddress());
while (askYesNo("Execute?", "Execute 'echo test'?")) { RemoteMethod execute = connection.getMethods().get("execute");
connection.getMethods().get("execute").invoke(Map.of("cmd", "echo test")); 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());
}
} }
} }
} }

View file

@ -92,7 +92,11 @@ class Receiver(Thread):
dbg_seq = 0 dbg_seq = 0
while not self._is_shutdown: while not self._is_shutdown:
#print("Receiving message") #print("Receiving message")
try:
reply = recv_delimited(self.client.s, bufs.RootMessage(), dbg_seq) 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')}") #print(f"Got one: {reply.WhichOneof('msg')}")
dbg_seq += 1 dbg_seq += 1
try: try:
@ -333,6 +337,14 @@ class Trace(object):
return self.client._delete_bytes(self.id, snap, range) return self.client._delete_bytes(self.id, snap, range)
def put_registers(self, space, values, snap=None): 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: if snap is None:
snap = self.snap() snap = self.snap()
return self.client._put_registers(self.id, snap, space, values) return self.client._put_registers(self.id, snap, space, values)

View file

@ -160,6 +160,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
DebuggerObjectActionContext ctx) { DebuggerObjectActionContext ctx) {
Set<TraceModule> result = new HashSet<>(); Set<TraceModule> result = new HashSet<>();
for (TraceObjectValue value : ctx.getObjectValues()) { for (TraceObjectValue value : ctx.getObjectValues()) {
if (!value.isObject()) {
continue;
}
TraceObject child = value.getChild(); TraceObject child = value.getChild();
TraceObjectModule module = child.queryInterface(TraceObjectModule.class); TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
if (module != null) { if (module != null) {
@ -179,6 +182,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
DebuggerObjectActionContext ctx) { DebuggerObjectActionContext ctx) {
Set<TraceSection> result = new HashSet<>(); Set<TraceSection> result = new HashSet<>();
for (TraceObjectValue value : ctx.getObjectValues()) { for (TraceObjectValue value : ctx.getObjectValues()) {
if (!value.isObject()) {
continue;
}
TraceObject child = value.getChild(); TraceObject child = value.getChild();
TraceObjectModule module = child.queryInterface(TraceObjectModule.class); TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
if (module != null) { if (module != null) {

View file

@ -49,10 +49,11 @@ task testSpecimenWin_x86_64 {
task testSpecimenLinux_x86_64 { task testSpecimenLinux_x86_64 {
dependsOn 'expCloneExecExecutable'//Linux_x86_64Executable' dependsOn 'expCloneExecExecutable'//Linux_x86_64Executable'
dependsOn 'expCloneExitExecutable'//Linux_x86_64Executable' dependsOn 'expCloneExitLinux_x86_64Executable'
//dependsOn 'expCloneSpinExecutable'//Linux_x86_64Executable' //dependsOn 'expCloneSpinExecutable'//Linux_x86_64Executable'
dependsOn 'expForkExecutable'//Linux_x86_64Executable' dependsOn 'expForkExecutable'//Linux_x86_64Executable'
dependsOn 'expPrintLinux_x86_64Executable' dependsOn 'expPrintLinux_x86_64Executable'
dependsOn 'expReadLinux_x86_64Executable'
dependsOn 'expSpinLinux_x86_64Executable' dependsOn 'expSpinLinux_x86_64Executable'
//dependsOn 'expTypesExecutable'//Linux_x86_64Executable' //dependsOn 'expTypesExecutable'//Linux_x86_64Executable'
dependsOn 'expRegistersLinux_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) // TODO: testSpecimenMac_x86_64 (Intel)
// will likely need to codesign them to grant debugee-entitlement // will likely need to codesign them to grant debugee-entitlement
@ -91,6 +98,7 @@ model {
expCloneExit(NativeExecutableSpec) { expCloneExit(NativeExecutableSpec) {
targetPlatform "linux_x86_64" targetPlatform "linux_x86_64"
//targetPlatform "linux_x86_32" // TODO: Test on these //targetPlatform "linux_x86_32" // TODO: Test on these
targetPlatform "mac_arm_64"
} }
expCloneSpin(NativeExecutableSpec) { expCloneSpin(NativeExecutableSpec) {
targetPlatform "linux_x86_64" targetPlatform "linux_x86_64"
@ -105,6 +113,11 @@ model {
//targetPlatform "linux_x86_32" // TODO: Test on these //targetPlatform "linux_x86_32" // TODO: Test on these
targetPlatform "win_x86_64" targetPlatform "win_x86_64"
targetPlatform "win_x86_32" // TODO: Test on these targetPlatform "win_x86_32" // TODO: Test on these
targetPlatform "mac_arm_64"
}
expRead(NativeExecutableSpec) {
targetPlatform "linux_x86_64"
targetPlatform "mac_arm_64"
} }
expSpin(NativeExecutableSpec) { expSpin(NativeExecutableSpec) {
targetPlatform "linux_x86_64" targetPlatform "linux_x86_64"

View file

@ -15,6 +15,7 @@
*/ */
#include <pthread.h> #include <pthread.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h>
pthread_t thread; pthread_t thread;

View file

@ -21,7 +21,7 @@
#define DLLEXPORT __declspec(dllexport) #define DLLEXPORT __declspec(dllexport)
#else #else
#define DLLEXPORT #define DLLEXPORT
#define OutputDebugString(out) printf("%s\n", out) #define OutputDebugString(out) puts(out)
#endif #endif
DLLEXPORT volatile char overwrite[] = "Hello, World!"; DLLEXPORT volatile char overwrite[] = "Hello, World!";

View 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));
}

View file

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

View file

@ -24,9 +24,8 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.app.script.AskDialog; import ghidra.app.script.AskDialog;
import ghidra.pty.Pty; import ghidra.pty.*;
import ghidra.pty.PtyChild.Echo; import ghidra.pty.PtyChild.Echo;
import ghidra.pty.PtySession;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -49,33 +48,6 @@ public class SshPtyTest extends AbstractGhidraHeadedIntegrationTest {
return dialog.getValueAsString(); 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 @Test
public void testSessionBash() throws IOException, InterruptedException { public void testSessionBash() throws IOException, InterruptedException {
try (Pty pty = factory.openpty()) { try (Pty pty = factory.openpty()) {

View file

@ -20,7 +20,8 @@ import static org.junit.Assert.*;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.nio.file.*; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.Function; import java.util.function.Function;
@ -28,8 +29,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin; 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.framework.plugintool.util.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl; import ghidra.program.model.address.AddressRangeImpl;
import ghidra.pty.*;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet; import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
@ -54,56 +59,70 @@ import ghidra.util.Msg;
import ghidra.util.NumericUtilities; import ghidra.util.NumericUtilities;
public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebuggerTest { 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, * Some features have to be disabled to avoid permissions issues in the test container. Namely,
* don't try to disable ASLR. * don't try to disable ASLR.
*
* Color codes mess up the address parsing.
*/ */
public static final String PREAMBLE = """ public static final String PREAMBLE = """
script import ghidralldb script import ghidralldb
settings set use-color false
settings set target.disable-aslr false settings set target.disable-aslr false
"""; """;
// Connecting should be the first thing the script does, so use a tight timeout. // 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 CONNECT_TIMEOUT_MS = 3000;
protected static final int TIMEOUT_SECONDS = 300; protected static final int TIMEOUT_SECONDS = 300;
protected static final int QUIT_TIMEOUT_MS = 1000; 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; protected TraceRmiService traceRmi;
private Path lldbPath; private Path lldbPath;
private Path outFile;
private Path errFile;
// @BeforeClass // @BeforeClass
public static void setupPython() throws Throwable { public static void setupPython() throws Throwable {
new ProcessBuilder("gradle", "Debugger-agent-lldb:assemblePyPackage") new ProcessBuilder("gradle",
"Debugger-rmi-trace:assemblePyPackage",
"Debugger-agent-lldb:assemblePyPackage")
.directory(TestApplicationUtils.getInstallationDirectory()) .directory(TestApplicationUtils.getInstallationDirectory())
.inheritIO() .inheritIO()
.start() .start()
.waitFor(); .waitFor();
} }
protected void setPythonPath(ProcessBuilder pb) throws IOException { protected void setPythonPath(Map<String, String> env) throws IOException {
String sep = String sep =
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":"; OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace", String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
@ -111,7 +130,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-lldb", String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-lldb",
"build/pypkg/src").getAbsolutePath(); "build/pypkg/src").getAbsolutePath();
String add = rmiPyPkg + sep + gdbPyPkg; 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 @Before
@ -124,8 +143,6 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
catch (RuntimeException e) { catch (RuntimeException e) {
lldbPath = Paths.get(DummyProc.which("lldb")); lldbPath = Paths.get(DummyProc.which("lldb"));
} }
outFile = Files.createTempFile("lldbout", null);
errFile = Files.createTempFile("lldberr", null);
} }
protected void addAllDebuggerPlugins() throws PluginException { protected void addAllDebuggerPlugins() throws PluginException {
@ -156,71 +173,70 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
throw new AssertionError("Unhandled address type " + address); 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() { protected String handle() {
if (!"".equals(stderr) || (0 != exitCode && 143 != exitCode)) { if (0 != exitCode && 143 != exitCode) {
throw new LldbError(exitCode, stdout, stderr); 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 @SuppressWarnings("resource") // Do not close stdin
protected ExecInLldb execInLldb(String script) throws IOException { protected ExecInLldb execInLldb(String script) throws IOException {
ProcessBuilder pb = new ProcessBuilder(lldbPath.toString()); Pty pty = PtyFactory.local().openpty();
setPythonPath(pb); 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. OutputStream stdin = pty.getParent().getOutputStream();
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();
stdin.write(script.getBytes()); stdin.write(script.getBytes());
stdin.flush(); stdin.flush();
return new ExecInLldb(lldbProc, CompletableFuture.supplyAsync(() -> { return new ExecInLldb(pty, lldbSession, CompletableFuture.supplyAsync(() -> {
try { try {
if (!lldbProc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) { int exitVal = lldbSession.waitExited(TIMEOUT_SECONDS, TimeUnit.SECONDS);
Msg.error(this, "Timed out waiting for LLDB"); Msg.info(this, "LLDB exited with code " + exitVal);
lldbProc.destroyForcibly(); return new LldbResult(false, exitVal, capture.toString());
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()); catch (TimeoutException e) {
return new LldbResult(false, lldbProc.exitValue(), Files.readString(outFile), return new LldbResult(true, -1, capture.toString());
Files.readString(errFile));
} }
catch (Exception e) { catch (Exception e) {
return ExceptionUtils.rethrow(e); return ExceptionUtils.rethrow(e);
} }
finally { 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 static class LldbError extends RuntimeException {
public final int exitCode; public final int exitCode;
public final String stdout; public final String out;
public final String stderr;
public LldbError(int exitCode, String stdout, String stderr) { public LldbError(int exitCode, String out) {
super(""" super("""
exitCode=%d: exitCode=%d:
----stdout---- ----out----
%s %s
----stderr---- """.formatted(exitCode, out));
%s
""".formatted(exitCode, stdout, stderr));
this.exitCode = exitCode; this.exitCode = exitCode;
this.stdout = stdout; this.out = out;
this.stderr = stderr;
} }
} }
@ -250,17 +266,32 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true)); 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 @Override
public void close() throws Exception { public void close() throws Exception {
Msg.info(this, "Cleaning up lldb"); Msg.info(this, "Cleaning up lldb");
exec.lldb().destroy(); execute("settings set auto-confirm true");
exec.pty.getParent().getOutputStream().write("""
quit
""".getBytes());
try { try {
LldbResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); LldbResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
r.handle(); r.handle();
waitForPass(() -> assertTrue(connection.isClosed())); waitForPass(() -> assertTrue(connection.isClosed()));
} }
finally { finally {
exec.pty.close();
exec.lldb.destroyForcibly(); exec.lldb.destroyForcibly();
exec.pumper.interrupt();
} }
} }
} }
@ -276,8 +307,10 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return new LldbAndConnection(exec, connection); return new LldbAndConnection(exec, connection);
} }
catch (SocketTimeoutException e) { catch (SocketTimeoutException e) {
exec.pty.close();
exec.lldb.destroyForcibly(); exec.lldb.destroyForcibly();
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle(); exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
exec.pumper.interrupt();
throw e; throw e;
} }
} }
@ -285,7 +318,7 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
protected LldbAndConnection startAndConnectLldb() throws Exception { protected LldbAndConnection startAndConnectLldb() throws Exception {
return startAndConnectLldb(addr -> """ return startAndConnectLldb(addr -> """
%s %s
ghidra_trace_connect %s ghidra trace connect %s
""".formatted(PREAMBLE, addr)); """.formatted(PREAMBLE, addr));
} }
@ -294,36 +327,56 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
throws Exception { throws Exception {
LldbAndConnection conn = startAndConnectLldb(scriptSupplier); LldbAndConnection conn = startAndConnectLldb(scriptSupplier);
LldbResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); LldbResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
conn.exec.pty.close();
conn.exec.pumper.interrupt();
String stdout = r.handle(); String stdout = r.handle();
waitForPass(() -> assertTrue(conn.connection.isClosed())); waitForPass(() -> assertTrue(conn.connection.isClosed()));
return stdout; return stdout;
} }
protected void waitStopped() { protected void waitState(String state) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0))); 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(); waitTxDone();
} }
protected void waitRunning() { protected void waitStopped(LldbAndConnection conn) {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0))); waitForPass(() -> assertEquals(Boolean.TRUE,
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state"))); conn.pyeval("util.get_debugger().GetTargetAtIndex(0).GetProcess().is_stopped")));
waitTxDone(); // 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) { protected String extractOutSection(String out, String head) {
String[] split = out.split("\n"); String[] split = out.replace("\r", "").split("\n");
String xout = ""; String xout = "";
for (String s : split) { for (String s : split) {
if (!s.startsWith("(lldb)") && !s.equals("")) { if (!s.startsWith("(lldb)") && !s.contains("script print(") && !s.equals("")) {
xout += s + "\n"; xout += s + "\n";
} }
} }
return xout.split(head)[1].split("---")[0].replace("(lldb)", "").trim(); 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 { protected MemDump parseHexDump(String dump) throws IOException {
// First, get the address. Assume contiguous, so only need top line. // 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()); return new MemDump(address, buf.toByteArray());
} }
record RegDump() {
}
protected RegDump parseRegDump(String dump) {
return new RegDump();
}
protected ManagedDomainObject openDomainObject(String path) throws Exception { protected ManagedDomainObject openDomainObject(String path) throws Exception {
DomainFile df = env.getProject().getProjectData().getFile(path); DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df); 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, protected void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
Set<TraceBreakpointKind> kinds, String expression) throws Exception { Set<TraceBreakpointKind> kinds, String expression) throws Exception {
assertEquals(key, locVal.getEntryKey()); assertEquals(key, locVal.getEntryKey());

View file

@ -15,7 +15,8 @@
*/ */
package agent.lldb.rmi; 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 static org.junit.Assert.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -41,7 +42,7 @@ import ghidra.trace.model.time.TraceSnapshot;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test @Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class LldbHooksTest extends AbstractLldbTraceRmiTest { 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; private static final long RETRY_MS = 500;
record LldbAndTrace(LldbAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable { record LldbAndTrace(LldbAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable {
@ -64,10 +65,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
protected LldbAndTrace startAndSyncLldb() throws Exception { protected LldbAndTrace startAndSyncLldb() throws Exception {
LldbAndConnection conn = startAndConnectLldb(); LldbAndConnection conn = startAndConnectLldb();
try { try {
// TODO: Why does using 'set arch' cause a hang at quit? conn.execute("ghidra trace start");
conn.execute(
"ghidralldb.util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
conn.execute("ghidra_trace_start");
ManagedDomainObject mdo = waitDomainObject("/New Traces/lldb/noname"); ManagedDomainObject mdo = waitDomainObject("/New Traces/lldb/noname");
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
return new LldbAndTrace(conn, mdo); return new LldbAndTrace(conn, mdo);
@ -102,7 +100,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("continue"); conn.execute("continue");
waitStopped(); waitStopped(conn.conn);
txPut(conn, "threads"); txPut(conn, "threads");
waitForPass(() -> assertEquals(2, waitForPass(() -> assertEquals(2,
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()), tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
@ -131,7 +129,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
RUN_TIMEOUT_MS, RETRY_MS); RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("continue"); conn.execute("continue");
waitStopped(); waitStopped(conn.conn);
waitForPass(() -> { waitForPass(() -> {
TraceObject inf = tb.objAny("Processes[]"); TraceObject inf = tb.objAny("Processes[]");
assertNotNull(inf); assertNotNull(inf);
@ -207,11 +205,11 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
traceManager.openTrace(tb.trace); traceManager.openTrace(tb.trace);
start(conn, "bash"); start(conn, getSpecimenPrint());
conn.execute("breakpoint set -n read"); conn.execute("breakpoint set -n puts");
conn.execute("cont"); conn.execute("cont");
waitStopped(); waitStopped(conn.conn);
waitForPass(() -> assertThat( waitForPass(() -> assertThat(
tb.objValues(lastSnap(conn), "Processes[].Threads[].Stack[]").size(), tb.objValues(lastSnap(conn), "Processes[].Threads[].Stack[]").size(),
greaterThan(2)), greaterThan(2)),
@ -224,6 +222,8 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
conn.execute("frame select 0"); conn.execute("frame select 0");
waitForPass(() -> assertEquals("0", frameIndex(traceManager.getCurrentObject())), waitForPass(() -> assertEquals("0", frameIndex(traceManager.getCurrentObject())),
RUN_TIMEOUT_MS, RETRY_MS); 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. // 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 { public void testOnMemoryChanged() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]); long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
conn.execute("expr *((char*)(void(*)())main) = 0x7f"); conn.execute("expr *((char*)(void(*)())main) = 0x7f");
conn.execute("ghidra_trace_txstart 'Tx'"); //conn.execute("ghidra trace tx-start 'Tx'");
conn.execute("ghidra_trace_putmem `(void(*)())main` 10"); //conn.execute("ghidra trace putmem '(void(*)())main' 10");
conn.execute("ghidra_trace_txcommit"); //conn.execute("ghidra trace tx-commit");
waitForPass(() -> { waitForPass(() -> {
ByteBuffer buf = ByteBuffer.allocate(10); 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 { public void testOnRegisterChanged() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
conn.execute("expr $rax = 0x1234"); conn.execute("expr $%s = 0x1234".formatted(PLAT.intReg()));
conn.execute("ghidra_trace_txstart 'Tx'"); //conn.execute("ghidra trace tx-start 'Tx'");
conn.execute("ghidra_trace_putreg"); //conn.execute("ghidra trace putreg");
conn.execute("ghidra_trace_txcommit"); //conn.execute("ghidra trace tx-commit");
String path = "Processes[].Threads[].Stack[].Registers"; String path = "Processes[].Threads[].Stack[].Registers";
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0))); TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
@ -269,29 +269,33 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
.getAddressSpace(registers.getCanonicalPath().toString()); .getAddressSpace(registers.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false); TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false);
waitForPass(() -> assertEquals("1234", 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 @Test
public void testOnCont() throws Exception { public void testOnCont() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenRead());
conn.execute("cont"); conn.execute("cont");
waitRunning(); waitRunning(conn.conn);
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]")); TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> { waitForPass(() -> {
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state")); assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
conn.execute("process interrupt");
} }
} }
@Test @Test
public void testOnStop() throws Exception { public void testOnStop() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
TraceObject inf = waitForValue(() -> tb.objAny("Processes[]")); TraceObject inf = waitForValue(() -> tb.objAny("Processes[]"));
waitForPass(() -> { waitForPass(() -> {
@ -303,25 +307,22 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testOnExited() throws Exception { public void testOnExited() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
conn.execute("file bash"); start(conn, getSpecimenPrint());
conn.execute("ghidra_trace_sync_enable");
conn.execute("process launch --stop-at-entry -- -c 'exit 1'");
txPut(conn, "processes");
conn.execute("cont"); conn.execute("cont");
waitRunning(); waitRunning(conn.conn);
waitForPass(() -> { waitForPass(() -> {
TraceSnapshot snapshot = TraceSnapshot snapshot =
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false); tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
assertNotNull(snapshot); assertNotNull(snapshot);
assertEquals("Exited with code 1", snapshot.getDescription()); assertEquals("Exited with code 72", snapshot.getDescription());
TraceObject proc = tb.objAny("Processes[]"); TraceObject proc = tb.objAny("Processes[]");
assertNotNull(proc); assertNotNull(proc);
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code"); Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
assertThat(val, instanceOf(Number.class)); assertThat(val, instanceOf(Number.class));
assertEquals(1, ((Number) val).longValue()); assertEquals(72, ((Number) val).longValue());
}, RUN_TIMEOUT_MS, RETRY_MS); }, RUN_TIMEOUT_MS, RETRY_MS);
} }
} }
@ -329,7 +330,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testOnBreakpointCreated() throws Exception { public void testOnBreakpointCreated() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()); assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("breakpoint set -n main"); conn.execute("breakpoint set -n main");
@ -346,9 +347,10 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testOnBreakpointModified() throws Exception { public void testOnBreakpointModified() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
assertEquals(0, tb.objValues(lastSnap(conn), "Breakpoints[]").size()); assertEquals(0, tb.objValues(lastSnap(conn), "Breakpoints[]").size());
//conn.execute("script lldb.debugger.SetAsync(False)");
conn.execute("breakpoint set -n main"); conn.execute("breakpoint set -n main");
conn.execute("stepi"); conn.execute("stepi");
TraceObject brk = waitForPass(() -> { TraceObject brk = waitForPass(() -> {
@ -357,6 +359,8 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
return (TraceObject) brks.get(0); return (TraceObject) brks.get(0);
}); });
assertEquals(null, tb.objValue(brk, lastSnap(conn), "Condition")); assertEquals(null, tb.objValue(brk, lastSnap(conn), "Condition"));
waitStopped(conn.conn);
conn.execute("breakpoint modify -c 'x>3'"); conn.execute("breakpoint modify -c 'x>3'");
conn.execute("stepi"); conn.execute("stepi");
// NB: Testing "Commands" requires multi-line input - not clear how to do this // NB: Testing "Commands" requires multi-line input - not clear how to do this
@ -372,7 +376,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testOnBreakpointDeleted() throws Exception { public void testOnBreakpointDeleted() throws Exception {
try (LldbAndTrace conn = startAndSyncLldb()) { try (LldbAndTrace conn = startAndSyncLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()); assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
conn.execute("breakpoint set -n main"); conn.execute("breakpoint set -n main");
@ -384,6 +388,7 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
return (TraceObject) brks.get(0); return (TraceObject) brks.get(0);
}); });
waitStopped(conn.conn);
conn.execute("breakpoint delete %s".formatted(brk.getCanonicalPath().index())); conn.execute("breakpoint delete %s".formatted(brk.getCanonicalPath().index()));
conn.execute("stepi"); conn.execute("stepi");
@ -395,15 +400,15 @@ public class LldbHooksTest extends AbstractLldbTraceRmiTest {
private void start(LldbAndTrace conn, String obj) { private void start(LldbAndTrace conn, String obj) {
conn.execute("file " + obj); conn.execute("file " + obj);
conn.execute("ghidra_trace_sync_enable"); conn.execute("ghidra trace sync-enable");
conn.execute("process launch --stop-at-entry"); conn.execute("process launch --stop-at-entry");
txPut(conn, "processes"); txPut(conn, "processes");
} }
private void txPut(LldbAndTrace conn, String obj) { private void txPut(LldbAndTrace conn, String obj) {
conn.execute("ghidra_trace_txstart 'Tx" + obj + "'"); conn.execute("ghidra trace tx-start 'Tx" + obj + "'");
conn.execute("ghidra_trace_put_" + obj); conn.execute("ghidra trace put-" + obj);
conn.execute("ghidra_trace_txcommit"); conn.execute("ghidra trace tx-commit");
} }
} }

View file

@ -18,9 +18,11 @@ package agent.lldb.rmi;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import java.util.*; import java.util.*;
import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
@ -31,6 +33,7 @@ import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathPredicates;
import ghidra.debug.api.tracermi.RemoteMethod; import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.framework.OperatingSystem;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
@ -59,10 +62,10 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testExecute() throws Exception { public void testExecute() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
conn.execute("kill"); conn.execute("kill");
} }
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
// Just confirm it's present // Just confirm it's present
} }
} }
@ -70,7 +73,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testRefreshAvailable() throws Exception { public void testRefreshAvailable() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txCreate(conn, "Available"); txCreate(conn, "Available");
RemoteMethod refreshAvailable = conn.getMethod("refresh_available"); RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
@ -93,13 +96,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testRefreshBreakpoints() throws Exception { public void testRefreshBreakpoints() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
//waitStopped(); //waitStopped(conn);
conn.execute("breakpoint set --name main"); conn.execute("breakpoint set --name main");
conn.execute("breakpoint set -H --name main"); conn.execute("breakpoint set -H --name main");
@ -132,14 +135,14 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testRefreshProcBreakpoints() throws Exception { public void testRefreshProcBreakpoints() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
RemoteMethod refreshProcBreakpoints = conn.getMethod("refresh_proc_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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject locations = TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints")); Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
@ -171,19 +174,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testRefreshProcWatchpoints() throws Exception { public void testRefreshProcWatchpoints() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "all"); txPut(conn, "all");
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_proc_watchpoints"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject locations = TraceObject locations =
Objects.requireNonNull(tb.objAny("Processes[].Watchpoints")); Objects.requireNonNull(tb.objAny("Processes[].Watchpoints"));
conn.execute("watchpoint set expression -- `(void(*)())main`"); conn.execute("watchpoint set expression -s 1 -- `(void(*)())main`");
conn.execute("watchpoint set expression -w read -- `(void(*)())main`+-0x20"); conn.execute("watchpoint set expression -s 1 -w read -- `(void(*)())main`+-0x20");
conn.execute("watchpoint set expression -w read_write -- `(void(*)())main`+0x30"); conn.execute(
"watchpoint set expression -s 1 -w read_write -- `(void(*)())main`+0x30");
refreshProcWatchpoints.invoke(Map.of("node", locations)); refreshProcWatchpoints.invoke(Map.of("node", locations));
List<TraceObjectValue> procWatchLocVals = tb.trace.getObjectManager() List<TraceObjectValue> procWatchLocVals = tb.trace.getObjectManager()
@ -219,7 +223,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testRefreshProcesses() throws Exception { public void testRefreshProcesses() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txCreate(conn, "Processes"); txCreate(conn, "Processes");
txCreate(conn, "Processes[1]"); txCreate(conn, "Processes[1]");
@ -244,21 +248,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshEnvironment() throws Exception { public void testRefreshEnvironment() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Environment"; String path = "Processes[].Environment";
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "all"); txPut(conn, "all");
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject env = Objects.requireNonNull(tb.objAny(path)); TraceObject env = Objects.requireNonNull(tb.objAny(path));
refreshEnvironment.invoke(Map.of("node", env)); refreshEnvironment.invoke(Map.of("node", env));
// Assumes LLDB on Linux amd64
assertEquals("lldb", env.getValue(0, "_debugger").getValue()); assertEquals("lldb", env.getValue(0, "_debugger").getValue());
assertEquals("x86_64", env.getValue(0, "_arch").getValue()); assertEquals(PLAT.name(), env.getValue(0, "_arch").getValue());
assertEquals("linux", env.getValue(0, "_os").getValue()); assertLocalOs(env.getValue(0, "_os").castValue());
assertEquals("little", env.getValue(0, "_endian").getValue()); assertEquals(PLAT.endian(), env.getValue(0, "_endian").getValue());
} }
} }
} }
@ -267,11 +270,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshThreads() throws Exception { public void testRefreshThreads() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Threads"; String path = "Processes[].Threads";
start(conn, "bash"); start(conn, getSpecimenPrint());
txCreate(conn, path); txCreate(conn, path);
RemoteMethod refreshThreads = conn.getMethod("refresh_threads"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject threads = Objects.requireNonNull(tb.objAny(path)); TraceObject threads = Objects.requireNonNull(tb.objAny(path));
@ -287,15 +290,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshStack() throws Exception { public void testRefreshStack() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Threads[].Stack"; String path = "Processes[].Threads[].Stack";
conn.execute("file bash"); conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "read"); breakAt(conn, "puts");
RemoteMethod refreshStack = conn.getMethod("refresh_stack"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
waitTxDone();
txPut(conn, "frames"); txPut(conn, "frames");
TraceObject stack = Objects.requireNonNull(tb.objAny(path)); TraceObject stack = Objects.requireNonNull(tb.objAny(path));
@ -316,16 +320,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshRegisters() throws Exception { public void testRefreshRegisters() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Threads[].Stack[].Registers"; String path = "Processes[].Threads[].Stack[].Registers";
start(conn, "bash"); start(conn, getSpecimenPrint());
conn.execute("ghidra_trace_txstart 'Tx'"); conn.execute("ghidra trace tx-start 'Tx'");
conn.execute("ghidra_trace_putreg"); conn.execute("ghidra trace putreg");
conn.execute("ghidra_trace_txcommit"); conn.execute("ghidra trace tx-commit");
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers"); 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()); 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))); TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
refreshRegisters.invoke(Map.of("node", registers)); refreshRegisters.invoke(Map.of("node", registers));
@ -334,9 +338,9 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
AddressSpace t1f0 = tb.trace.getBaseAddressFactory() AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
.getAddressSpace(registers.getCanonicalPath().toString()); .getAddressSpace(registers.getCanonicalPath().toString());
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false); 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 // 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 { public void testRefreshMappings() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Memory"; String path = "Processes[].Memory";
start(conn, "bash"); start(conn, getSpecimenPrint());
txCreate(conn, path); txCreate(conn, path);
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject memory = Objects.requireNonNull(tb.objAny(path)); TraceObject memory = Objects.requireNonNull(tb.objAny(path));
@ -367,11 +371,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
public void testRefreshModules() throws Exception { public void testRefreshModules() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
String path = "Processes[].Modules"; String path = "Processes[].Modules";
start(conn, "bash"); start(conn, getSpecimenPrint());
txCreate(conn, path); txCreate(conn, path);
RemoteMethod refreshModules = conn.getMethod("refresh_modules"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject modules = Objects.requireNonNull(tb.objAny(path)); TraceObject modules = Objects.requireNonNull(tb.objAny(path));
@ -379,27 +383,30 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
// Would be nice to control / validate the specifics // Would be nice to control / validate the specifics
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules(); Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
TraceModule modBash = TraceModule modExpPrint = Unique.assertOne(
Unique.assertOne(all.stream().filter(m -> m.getName().contains("bash"))); all.stream().filter(m -> m.getName().contains("expPrint")));
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase())); assertNotEquals(tb.addr(0), Objects.requireNonNull(modExpPrint.getBase()));
} }
} }
} }
@Test @Test
public void testActivateThread() throws Exception { 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()) { try (LldbAndConnection conn = startAndConnectLldb()) {
// TODO: need to find this file (same issue in LldbHookTests // TODO: need to find this file (same issue in LldbHookTests
String dproc = DummyProc.which("expCloneExit"); String dproc = DummyProc.which("expCloneExit");
conn.execute("file " + dproc); conn.execute("file " + dproc);
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "work"); breakAt(conn, "work");
RemoteMethod activateThread = conn.getMethod("activate_thread"); RemoteMethod activateThread = conn.getMethod("activate_thread");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expCloneExit")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expCloneExit")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
waitTxDone();
txPut(conn, "threads"); txPut(conn, "threads");
@ -415,7 +422,10 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
activateThread.invoke(Map.of("thread", t)); activateThread.invoke(Map.of("thread", t));
String out = conn.executeCapture("thread info"); String out = conn.executeCapture("thread info");
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList()); 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 @Test
public void testActivateFrame() throws Exception { public void testActivateFrame() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "read"); breakAt(conn, "puts");
RemoteMethod activateFrame = conn.getMethod("activate_frame"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
waitTxDone();
txPut(conn, "frames"); txPut(conn, "frames");
@ -456,11 +467,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testRemoveProcess() throws Exception { public void testRemoveProcess() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod removeProcess = conn.getMethod("remove_process"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
@ -474,10 +485,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testAttachObj() throws Exception { public void testAttachObj() throws Exception {
// Missing specimen for macOS
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
String sleep = DummyProc.which("expTraceableSleep"); String sleep = DummyProc.which("expTraceableSleep");
try (DummyProc dproc = DummyProc.run(sleep)) { try (DummyProc dproc = DummyProc.run(sleep)) {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "available"); txPut(conn, "available");
txPut(conn, "processes"); txPut(conn, "processes");
@ -499,10 +512,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testAttachPid() throws Exception { public void testAttachPid() throws Exception {
// Missing specimen for macOS
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
String sleep = DummyProc.which("expTraceableSleep"); String sleep = DummyProc.which("expTraceableSleep");
try (DummyProc dproc = DummyProc.run(sleep)) { try (DummyProc dproc = DummyProc.run(sleep)) {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod attachPid = conn.getMethod("attach_pid"); RemoteMethod attachPid = conn.getMethod("attach_pid");
@ -522,12 +537,12 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testDetach() throws Exception { public void testDetach() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
//conn.execute("process attach -p %d".formatted(dproc.pid)); //conn.execute("process attach -p %d".formatted(dproc.pid));
RemoteMethod detach = conn.getMethod("detach"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
@ -543,7 +558,7 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testLaunchEntry() throws Exception { public void testLaunchEntry() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch_loader"); RemoteMethod launch = conn.getMethod("launch_loader");
@ -553,11 +568,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
launch.invoke(Map.ofEntries( launch.invoke(Map.ofEntries(
Map.entry("process", proc), Map.entry("process", proc),
Map.entry("file", "bash"))); Map.entry("file", getSpecimenPrint())));
waitStopped(); waitStopped(conn);
String out = conn.executeCapture("target list"); 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 @Test //Not clear how to send interrupt
public void testLaunch() throws Exception { public void testLaunch() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod launch = conn.getMethod("launch"); RemoteMethod launch = conn.getMethod("launch");
@ -575,17 +590,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
launch.invoke(Map.ofEntries( launch.invoke(Map.ofEntries(
Map.entry("process", proc), Map.entry("process", proc),
Map.entry("file", "bash"))); Map.entry("file", getSpecimenRead())));
txPut(conn, "processes"); txPut(conn, "processes");
waitRunning(); waitRunning(conn);
Thread.sleep(100); // Give it plenty of time to block on read Thread.sleep(100); // Give it plenty of time to block on read
conn.execute("process interrupt"); conn.execute("process interrupt");
txPut(conn, "processes"); txPut(conn, "processes");
waitStopped(); waitStopped(conn);
String out = conn.executeCapture("bt"); String out = conn.executeCapture("bt");
assertThat(out, containsString("read")); assertThat(out, containsString("read"));
@ -596,13 +611,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testKill() throws Exception { public void testKill() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod kill = conn.getMethod("kill"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
kill.invoke(Map.of("process", proc)); 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 @Test
public void testStepInto() throws Exception { public void testStepInto() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod step_into = conn.getMethod("step_into"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
waitTxDone();
txPut(conn, "threads"); txPut(conn, "threads");
conn.execute("script lldb.debugger.SetAsync(False)");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
stepToCall(conn, step_into, thread);
while (!conn.executeCapture("dis -c1 -s '$pc'").contains("call")) {
step_into.invoke(Map.of("thread", thread));
}
String dis2 = conn.executeCapture("dis -c2 -s '$pc'"); String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
// lab0: CallInstr instr = CallInstr.parse(dis2);
// -> addr0
//
// lab1:
// addr1
long pcNext = Long.decode(dis2.strip().split("\n")[4].strip().split("\\s+")[0]);
step_into.invoke(Map.of("thread", thread)); step_into.invoke(Map.of("thread", thread));
String disAt = conn.executeCapture("dis -c1 -s '$pc'"); String disAt = conn.executeCapture("dis -c1 -s '$pc'");
long pc = Long.decode(disAt.strip().split("\n")[1].strip().split("\\s+")[1]); FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertNotEquals(pcNext, pc); assertEquals(instr.target, pc.value);
} }
} }
} }
@Test //@Test // Debug information required (at least on macOS arm64)
public void testStepOver() throws Exception { public void testStepOver() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); start(conn, getSpecimenPrint());
conn.execute("ghidra_trace_start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "read");
RemoteMethod step_over = conn.getMethod("step_over"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
txPut(conn, "threads"); txPut(conn, "threads");
conn.execute("script lldb.debugger.SetAsync(False)");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
stepToCall(conn, step_over, thread);
while (!conn.executeCapture("dis -c1 -s '$pc'").contains("call")) {
step_over.invoke(Map.of("thread", thread));
}
String dis2 = conn.executeCapture("dis -c2 -s '$pc'"); String dis2 = conn.executeCapture("dis -c2 -s '$pc'");
// lab0: System.err.println(dis2);
// -> addr0 CallInstr instr = CallInstr.parse(dis2);
// addr1
long pcNext = Long.decode(dis2.strip().split("\n")[2].strip().split("\\s+")[0]);
// This winds up a step_into if lldb can't place its breakpoint
step_over.invoke(Map.of("thread", thread)); step_over.invoke(Map.of("thread", thread));
String disAt = conn.executeCapture("dis -c1 -s '$pc'"); String disAt = conn.executeCapture("dis -c1 -s '$pc'");
long pc = Long.decode(disAt.strip().split("\n")[1].strip().split("\\s+")[1]); FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(pcNext, pc); assertEquals(instr.next, pc.value);
} }
} }
} }
//@Test Not obvious "thread until -a" works (and definitely requires debug info") //@Test // Debug information required
public void testAdvance() throws Exception { public void testStepAdvance() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes");
RemoteMethod step_ext = conn.getMethod("step_ext"); RemoteMethod step_advance = conn.getMethod("step_advance");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
//waitStopped(); waitStopped(conn);
txPut(conn, "threads"); txPut(conn, "threads");
conn.execute("script lldb.debugger.SetAsync(False)");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
String dis3 = conn.executeCapture("disassemble -c3 -s '$pc'"); String dis3 = conn.executeCapture("disassemble -c3 -s '$pc'");
// TODO: Examine for control transfer? // 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'"); String disAt = conn.executeCapture("disassemble -c1 -s '$pc'");
long pc = Long.decode(dis1.strip().split("\n")[1].strip().split("\\s+")[1]); FoundHex pc = FoundHex.findHex(List.of(disAt.split("\\s+")), 0);
assertEquals(pcTarget, pc); assertEquals(addr.value, pc);
} }
} }
} }
@ -709,16 +752,18 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testFinish() throws Exception { public void testFinish() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "read"); breakAt(conn, "puts");
RemoteMethod activate = conn.getMethod("activate_thread"); RemoteMethod activate = conn.getMethod("activate_thread");
RemoteMethod step_out = conn.getMethod("step_out"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
waitTxDone();
txPut(conn, "threads"); txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
@ -735,18 +780,20 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
} }
@Test @Test
public void testReturn() throws Exception { public void testStepReturn() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); conn.execute("file " + getSpecimenPrint());
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "read"); breakAt(conn, "puts");
RemoteMethod activate = conn.getMethod("activate_thread"); RemoteMethod activate = conn.getMethod("activate_thread");
RemoteMethod ret = conn.getMethod("return"); RemoteMethod ret = conn.getMethod("step_return");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/bash")) { try (ManagedDomainObject mdo = openDomainObject("/New Traces/lldb/expPrint")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
waitTxDone();
txPut(conn, "threads"); txPut(conn, "threads");
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]")); TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
@ -765,11 +812,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakAddress() throws Exception { public void testBreakAddress() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_address"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
@ -786,13 +833,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakExpression() throws Exception { public void testBreakExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_expression"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
breakExpression.invoke(Map.of("expression", "main")); breakExpression.invoke(Map.of("expression", "main"));
@ -806,13 +853,13 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
// Are hardware breakpoints available on our VMs? // Are hardware breakpoints available on our VMs?
public void testBreakHardwareAddress() throws Exception { public void testBreakHardwareAddress() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakAddress = conn.getMethod("break_hw_address"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]); 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 //@Test There appear to be issues with hardware register availability in our virtual environments
public void testBreakHardwareExpression() throws Exception { public void testBreakHardwareExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_hw_expression"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
breakExpression.invoke(Map.of("expression", "`(void(*)())main`")); breakExpression.invoke(Map.of("expression", "`(void(*)())main`"));
@ -848,22 +895,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakReadRange() throws Exception { public void testBreakReadRange() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_read_range"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]); 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)); breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("0x%x".formatted(address))); assertThat(out, containsString("0x%x".formatted(address)));
assertThat(out, containsString("size = 4")); assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = r")); assertThat(out, containsString("type = r"));
} }
} }
@ -872,14 +919,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakReadExpression() throws Exception { public void testBreakReadExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_read_expression"); 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()); 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]); long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
@ -892,22 +941,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakWriteRange() throws Exception { public void testBreakWriteRange() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_write_range"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]); 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)); breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("0x%x".formatted(address))); assertThat(out, containsString("0x%x".formatted(address)));
assertThat(out, containsString("size = 4")); assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = w")); assertThat(out, containsString("type = w"));
} }
} }
@ -916,14 +965,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakWriteExpression() throws Exception { public void testBreakWriteExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_write_expression"); 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()); 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]); long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
@ -936,22 +987,22 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakAccessRange() throws Exception { public void testBreakAccessRange() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakRange = conn.getMethod("break_access_range"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]")); TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]); 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)); breakRange.invoke(Map.of("process", proc, "range", range));
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
assertThat(out, containsString("0x%x".formatted(address))); assertThat(out, containsString("0x%x".formatted(address)));
assertThat(out, containsString("size = 4")); assertThat(out, containsString("size = 1"));
assertThat(out, containsString("type = rw")); assertThat(out, containsString("type = rw"));
} }
} }
@ -960,14 +1011,16 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakAccessExpression() throws Exception { public void testBreakAccessExpression() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_access_expression"); 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()); 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]); long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
@ -981,11 +1034,11 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testBreakException() throws Exception { public void testBreakException() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExc = conn.getMethod("break_exception"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
breakExc.invoke(Map.of("lang", "C++")); breakExc.invoke(Map.of("lang", "C++"));
@ -1000,16 +1053,15 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testToggleBreakpoint() throws Exception { public void testToggleBreakpoint() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); start(conn, getSpecimenPrint());
conn.execute("ghidra_trace_start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "main");
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
conn.execute("breakpoint set -n main");
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]")); TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
@ -1024,19 +1076,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testToggleBreakpointLocation() throws Exception { public void testToggleBreakpointLocation() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); start(conn, getSpecimenPrint());
conn.execute("ghidra_trace_start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "main");
RemoteMethod toggleBreakpointLocation = conn.getMethod("toggle_breakpoint_location"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
conn.execute("breakpoint set -n main");
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
// NB. Requires canonical path. Inf[].Brk[] is a link
TraceObject loc = Objects.requireNonNull(tb.objAny("Breakpoints[][]")); TraceObject loc = Objects.requireNonNull(tb.objAny("Breakpoints[][]"));
toggleBreakpointLocation.invoke(Map.of("location", loc, "enabled", false)); toggleBreakpointLocation.invoke(Map.of("location", loc, "enabled", false));
@ -1050,16 +1100,15 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testDeleteBreakpoint() throws Exception { public void testDeleteBreakpoint() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
conn.execute("file bash"); start(conn, getSpecimenPrint());
conn.execute("ghidra_trace_start");
txPut(conn, "processes"); txPut(conn, "processes");
breakAt(conn, "main");
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint"); 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()); tb = new ToyDBTraceBuilder((Trace) mdo.get());
waitStopped(); waitStopped(conn);
conn.execute("breakpoint set -n main");
txPut(conn, "breakpoints"); txPut(conn, "breakpoints");
TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]")); TraceObject bpt = Objects.requireNonNull(tb.objAny("Breakpoints[]"));
@ -1074,15 +1123,17 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
@Test @Test
public void testDeleteWatchpoint() throws Exception { public void testDeleteWatchpoint() throws Exception {
try (LldbAndConnection conn = startAndConnectLldb()) { try (LldbAndConnection conn = startAndConnectLldb()) {
start(conn, "bash"); start(conn, getSpecimenPrint());
txPut(conn, "processes"); txPut(conn, "processes");
RemoteMethod breakExpression = conn.getMethod("break_read_expression"); RemoteMethod breakExpression = conn.getMethod("break_read_expression");
RemoteMethod deleteWatchpoint = conn.getMethod("delete_watchpoint"); 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()); 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]); long address = Long.decode(conn.executeCapture("dis -c1 -n main").split("\\s+")[1]);
String out = conn.executeCapture("watchpoint list"); String out = conn.executeCapture("watchpoint list");
@ -1101,25 +1152,26 @@ public class LldbMethodsTest extends AbstractLldbTraceRmiTest {
private void start(LldbAndConnection conn, String obj) { private void start(LldbAndConnection conn, String obj) {
conn.execute("file " + obj); conn.execute("file " + obj);
conn.execute("ghidra_trace_start"); conn.execute("ghidra trace start");
conn.execute("process launch --stop-at-entry"); conn.execute("process launch --stop-at-entry");
} }
private void txPut(LldbAndConnection conn, String obj) { private void txPut(LldbAndConnection conn, String obj) {
conn.execute("ghidra_trace_txstart 'Tx'"); conn.execute("ghidra trace tx-start 'Tx'");
conn.execute("ghidra_trace_put_" + obj); conn.execute("ghidra trace put-" + obj);
conn.execute("ghidra_trace_txcommit"); conn.execute("ghidra trace tx-commit");
} }
private void txCreate(LldbAndConnection conn, String path) { private void txCreate(LldbAndConnection conn, String path) {
conn.execute("ghidra_trace_txstart 'Fake'"); conn.execute("ghidra trace tx-start 'Fake'");
conn.execute("ghidra_trace_create_obj %s".formatted(path)); conn.execute("ghidra trace create-obj %s".formatted(path));
conn.execute("ghidra_trace_txcommit"); conn.execute("ghidra trace tx-commit");
} }
private void breakAt(LldbAndConnection conn, String fn) { 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("breakpoint set -n " + fn);
conn.execute("script lldb.debugger.SetAsync(False)");
conn.execute("run"); conn.execute("run");
} }