2020-07-15 09:31:13 +02:00
#!/usr/bin/env python
2012-08-31 01:11:17 +00:00
# ----------------------------------------------------------------------
# Be sure to add the python path that points to the LLDB shared library.
# On MacOSX csh, tcsh:
# setenv PYTHONPATH /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python
# On MacOSX sh, bash:
# export PYTHONPATH=/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python
# ----------------------------------------------------------------------
import optparse
import os
2012-08-31 02:55:56 +00:00
import platform
2012-08-31 01:11:17 +00:00
import sys
2019-04-18 23:24:54 +00:00
import subprocess
2019-03-26 14:46:15 +00:00
2012-08-31 02:55:56 +00:00
# ----------------------------------------------------------------------
# Code that auto imports LLDB
# ----------------------------------------------------------------------
try :
# Just try for LLDB in case PYTHONPATH is already correctly setup
import lldb
except ImportError :
lldb_python_dirs = list ( )
# lldb is not in the PYTHONPATH, try some defaults for the current platform
platform_system = platform . system ( )
if platform_system == " Darwin " :
# On Darwin, try the currently selected Xcode directory
2019-04-18 23:24:54 +00:00
xcode_dir = subprocess . check_output ( " xcode-select --print-path " , shell = True )
2012-08-31 02:55:56 +00:00
if xcode_dir :
lldb_python_dirs . append (
os . path . realpath (
xcode_dir + " /../SharedFrameworks/LLDB.framework/Resources/Python "
)
2023-05-25 08:48:57 -07:00
)
2012-08-31 02:55:56 +00:00
lldb_python_dirs . append (
xcode_dir + " /Library/PrivateFrameworks/LLDB.framework/Resources/Python "
)
lldb_python_dirs . append (
" /System/Library/PrivateFrameworks/LLDB.framework/Resources/Python "
)
success = False
for lldb_python_dir in lldb_python_dirs :
if os . path . exists ( lldb_python_dir ) :
if not ( sys . path . __contains__ ( lldb_python_dir ) ) :
sys . path . append ( lldb_python_dir )
try :
import lldb
except ImportError :
pass
else :
2019-03-21 18:27:40 +00:00
print ( ' imported lldb from: " %s " ' % ( lldb_python_dir ) )
2012-08-31 02:55:56 +00:00
success = True
break
if not success :
2019-03-21 18:27:40 +00:00
print (
" error: couldn ' t locate the ' lldb ' module, please set PYTHONPATH correctly "
)
2012-08-31 02:55:56 +00:00
sys . exit ( 1 )
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
def print_threads ( process , options ) :
if options . show_threads :
for thread in process :
2019-03-21 18:27:40 +00:00
print ( " %s %s " % ( thread , thread . GetFrameAtIndex ( 0 ) ) )
2012-08-31 01:11:17 +00:00
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
def run_commands ( command_interpreter , commands ) :
return_obj = lldb . SBCommandReturnObject ( )
for command in commands :
command_interpreter . HandleCommand ( command , return_obj )
if return_obj . Succeeded ( ) :
2019-03-21 18:27:40 +00:00
print ( return_obj . GetOutput ( ) )
2012-08-31 01:11:17 +00:00
else :
2019-03-21 18:27:40 +00:00
print ( return_obj )
2012-08-31 01:11:17 +00:00
if options . stop_on_error :
break
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
def main ( argv ) :
description = """ Debugs a program using the LLDB python API and uses asynchronous broadcast events to watch for process state changes. """
2012-08-31 02:55:56 +00:00
epilog = """ Examples:
#----------------------------------------------------------------------
# Run "/bin/ls" with the arguments "-lAF /tmp/", and set a breakpoint
# at "malloc" and backtrace and read all registers each time we stop
#----------------------------------------------------------------------
% . / process_events . py - - breakpoint malloc - - stop - command bt - - stop - command ' register read ' - - / bin / ls - lAF / tmp /
2023-05-25 08:48:57 -07:00
"""
2012-08-31 02:55:56 +00:00
optparse . OptionParser . format_epilog = lambda self , formatter : self . epilog
parser = optparse . OptionParser (
description = description ,
prog = " process_events " ,
usage = " usage: process_events [options] program [arg1 arg2] " ,
epilog = epilog ,
)
2012-08-31 01:11:17 +00:00
parser . add_option (
" -v " ,
" --verbose " ,
action = " store_true " ,
dest = " verbose " ,
help = " Enable verbose logging. " ,
default = False ,
)
2012-08-31 02:55:56 +00:00
parser . add_option (
" -b " ,
" --breakpoint " ,
action = " append " ,
type = " string " ,
metavar = " BPEXPR " ,
dest = " breakpoints " ,
help = ' Breakpoint commands to create after the target has been created, the values will be sent to the " _regexp-break " command which supports breakpoints by name, file:line, and address. ' ,
)
parser . add_option (
" -a " ,
" --arch " ,
type = " string " ,
dest = " arch " ,
help = " The architecture to use when creating the debug target. " ,
default = None ,
)
2013-06-26 22:23:45 +00:00
parser . add_option (
" --platform " ,
type = " string " ,
metavar = " platform " ,
dest = " platform " ,
help = ' Specify the platform to use when creating the debug target. Valid values include " localhost " , " darwin-kernel " , " ios-simulator " , " remote-freebsd " , " remote-macosx " , " remote-ios " , " remote-linux " . ' ,
default = None ,
)
2012-08-31 02:55:56 +00:00
parser . add_option (
" -l " ,
" --launch-command " ,
action = " append " ,
type = " string " ,
metavar = " CMD " ,
dest = " launch_commands " ,
help = " LLDB command interpreter commands to run once after the process has launched. This option can be specified more than once. " ,
default = [ ] ,
)
parser . add_option (
" -s " ,
" --stop-command " ,
action = " append " ,
type = " string " ,
metavar = " CMD " ,
dest = " stop_commands " ,
help = " LLDB command interpreter commands to run each time the process stops. This option can be specified more than once. " ,
default = [ ] ,
)
parser . add_option (
" -c " ,
" --crash-command " ,
action = " append " ,
type = " string " ,
metavar = " CMD " ,
dest = " crash_commands " ,
help = " LLDB command interpreter commands to run in case the process crashes. This option can be specified more than once. " ,
default = [ ] ,
)
parser . add_option (
" -x " ,
" --exit-command " ,
action = " append " ,
type = " string " ,
metavar = " CMD " ,
dest = " exit_commands " ,
help = " LLDB command interpreter commands to run once after the process has exited. This option can be specified more than once. " ,
default = [ ] ,
)
2012-08-31 01:11:17 +00:00
parser . add_option (
" -T " ,
" --no-threads " ,
action = " store_false " ,
dest = " show_threads " ,
help = " Don ' t show threads when process stops. " ,
default = True ,
)
2012-09-25 18:27:12 +00:00
parser . add_option (
" --ignore-errors " ,
action = " store_false " ,
dest = " stop_on_error " ,
help = " Don ' t stop executing LLDB commands if the command returns an error. This applies to all of the LLDB command interpreter commands that get run for launch, stop, crash and exit. " ,
default = True ,
)
2012-08-31 02:55:56 +00:00
parser . add_option (
" -n " ,
" --run-count " ,
type = " int " ,
dest = " run_count " ,
metavar = " N " ,
help = " How many times to run the process in case the process exits. " ,
default = 1 ,
)
2012-09-25 18:27:12 +00:00
parser . add_option (
" -t " ,
" --event-timeout " ,
type = " int " ,
dest = " event_timeout " ,
metavar = " SEC " ,
help = " Specify the timeout in seconds to wait for process state change events. " ,
default = lldb . UINT32_MAX ,
)
parser . add_option (
" -e " ,
" --environment " ,
action = " append " ,
type = " string " ,
metavar = " ENV " ,
dest = " env_vars " ,
help = " Environment variables to set in the inferior process when launching a process. " ,
)
parser . add_option (
" -d " ,
" --working-dir " ,
type = " string " ,
metavar = " DIR " ,
dest = " working_dir " ,
2021-05-30 21:14:17 +07:00
help = " The current working directory when launching a process. " ,
2012-09-25 18:27:12 +00:00
default = None ,
)
parser . add_option (
" -p " ,
" --attach-pid " ,
type = " int " ,
dest = " attach_pid " ,
metavar = " PID " ,
help = " Specify a process to attach to by process ID. " ,
default = - 1 ,
)
parser . add_option (
" -P " ,
" --attach-name " ,
type = " string " ,
dest = " attach_name " ,
metavar = " PROCESSNAME " ,
help = " Specify a process to attach to by name. " ,
default = None ,
)
parser . add_option (
" -w " ,
" --attach-wait " ,
action = " store_true " ,
dest = " attach_wait " ,
help = " Wait for the next process to launch when attaching to a process by name. " ,
default = False ,
)
2012-08-31 01:11:17 +00:00
try :
( options , args ) = parser . parse_args ( argv )
except :
return
2016-09-06 20:57:50 +00:00
2012-09-25 18:27:12 +00:00
attach_info = None
launch_info = None
exe = None
if args :
exe = args . pop ( 0 )
launch_info = lldb . SBLaunchInfo ( args )
if options . env_vars :
launch_info . SetEnvironmentEntries ( options . env_vars , True )
if options . working_dir :
launch_info . SetWorkingDirectory ( options . working_dir )
elif options . attach_pid != - 1 :
if options . run_count == 1 :
attach_info = lldb . SBAttachInfo ( options . attach_pid )
else :
2019-03-21 18:27:40 +00:00
print ( " error: --run-count can ' t be used with the --attach-pid option " )
2012-09-25 18:27:12 +00:00
sys . exit ( 1 )
elif not options . attach_name is None :
if options . run_count == 1 :
attach_info = lldb . SBAttachInfo ( options . attach_name , options . attach_wait )
else :
2019-03-21 18:27:40 +00:00
print ( " error: --run-count can ' t be used with the --attach-name option " )
2012-09-25 18:27:12 +00:00
sys . exit ( 1 )
else :
2019-03-21 18:27:40 +00:00
print (
" error: a program path for a program to debug and its arguments are required "
)
2012-08-31 01:11:17 +00:00
sys . exit ( 1 )
2012-08-31 02:55:56 +00:00
2012-08-31 01:11:17 +00:00
# Create a new debugger instance
debugger = lldb . SBDebugger . Create ( )
2013-06-26 22:23:45 +00:00
debugger . SetAsync ( True )
2012-08-31 01:11:17 +00:00
command_interpreter = debugger . GetCommandInterpreter ( )
# Create a target from a file and arch
2016-09-06 20:57:50 +00:00
2012-09-25 18:27:12 +00:00
if exe :
2019-03-21 18:27:40 +00:00
print ( " Creating a target for ' %s ' " % exe )
2013-06-26 22:23:45 +00:00
error = lldb . SBError ( )
target = debugger . CreateTarget ( exe , options . arch , options . platform , True , error )
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
if target :
2013-06-26 22:23:45 +00:00
# Set any breakpoints that were specified in the args if we are launching. We use the
# command line command to take advantage of the shorthand breakpoint
# creation
2012-09-25 18:27:12 +00:00
if launch_info and options . breakpoints :
2012-08-31 02:55:56 +00:00
for bp in options . breakpoints :
debugger . HandleCommand ( " _regexp-break %s " % ( bp ) )
run_commands ( command_interpreter , [ " breakpoint list " ] )
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
for run_idx in range ( options . run_count ) :
# Launch the process. Since we specified synchronous mode, we won't return
# from this function until we hit the breakpoint at main
2012-09-25 18:27:12 +00:00
error = lldb . SBError ( )
2016-09-06 20:57:50 +00:00
2012-09-25 18:27:12 +00:00
if launch_info :
if options . run_count == 1 :
2019-03-21 18:27:40 +00:00
print ( ' Launching " %s " ... ' % ( exe ) )
2012-09-25 18:27:12 +00:00
else :
2019-03-21 18:27:40 +00:00
print (
' Launching " %s " ... (launch %u of %u ) '
% ( exe , run_idx + 1 , options . run_count )
2023-05-25 08:48:57 -07:00
)
2016-09-06 20:57:50 +00:00
2012-09-25 18:27:12 +00:00
process = target . Launch ( launch_info , error )
else :
if options . attach_pid != - 1 :
2019-03-21 18:27:40 +00:00
print ( " Attaching to process %i ... " % ( options . attach_pid ) )
2012-09-25 18:27:12 +00:00
else :
if options . attach_wait :
2019-03-21 18:27:40 +00:00
print (
' Waiting for next to process named " %s " to launch... '
% ( options . attach_name )
2023-05-25 08:48:57 -07:00
)
2012-09-25 18:27:12 +00:00
else :
2019-03-21 18:27:40 +00:00
print (
' Attaching to existing process named " %s " ... '
% ( options . attach_name )
2023-05-25 08:48:57 -07:00
)
2012-09-25 18:27:12 +00:00
process = target . Attach ( attach_info , error )
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
# Make sure the launch went ok
2012-09-25 18:27:12 +00:00
if process and process . GetProcessID ( ) != lldb . LLDB_INVALID_PROCESS_ID :
2012-08-31 01:11:17 +00:00
pid = process . GetProcessID ( )
2019-03-21 18:27:40 +00:00
print ( " Process is %i " % ( pid ) )
2015-08-12 20:04:01 +00:00
if attach_info :
# continue process if we attached as we won't get an
# initial event
process . Continue ( )
2013-06-26 22:23:45 +00:00
listener = debugger . GetListener ( )
2012-08-31 01:11:17 +00:00
# sign up for process state change events
stop_idx = 0
done = False
while not done :
event = lldb . SBEvent ( )
if listener . WaitForEvent ( options . event_timeout , event ) :
2013-06-26 22:23:45 +00:00
if lldb . SBProcess . EventIsProcessEvent ( event ) :
state = lldb . SBProcess . GetStateFromEvent ( event )
2013-06-27 18:08:32 +00:00
if state == lldb . eStateInvalid :
# Not a state event
2019-03-21 18:27:40 +00:00
print ( " process event = %s " % ( event ) )
2013-06-27 18:08:32 +00:00
else :
2019-03-21 18:27:40 +00:00
print (
" process state changed event: %s "
% ( lldb . SBDebugger . StateAsCString ( state ) )
2023-05-25 08:48:57 -07:00
)
2013-06-27 18:08:32 +00:00
if state == lldb . eStateStopped :
if stop_idx == 0 :
if launch_info :
2019-03-21 18:27:40 +00:00
print ( " process %u launched " % ( pid ) )
2013-06-26 22:23:45 +00:00
run_commands (
command_interpreter , [ " breakpoint list " ]
)
2013-06-27 18:08:32 +00:00
else :
2019-03-21 18:27:40 +00:00
print ( " attached to process %u " % ( pid ) )
2013-06-27 18:08:32 +00:00
for m in target . modules :
2019-03-21 18:27:40 +00:00
print ( m )
2013-06-27 18:08:32 +00:00
if options . breakpoints :
for bp in options . breakpoints :
debugger . HandleCommand (
" _regexp-break %s " % ( bp )
2023-05-25 08:48:57 -07:00
)
2013-06-27 18:08:32 +00:00
run_commands (
command_interpreter ,
[ " breakpoint list " ] ,
)
run_commands (
command_interpreter , options . launch_commands
)
else :
if options . verbose :
2019-03-21 18:27:40 +00:00
print ( " process %u stopped " % ( pid ) )
2013-06-27 18:08:32 +00:00
run_commands (
command_interpreter , options . stop_commands
)
stop_idx + = 1
print_threads ( process , options )
2019-03-21 18:27:40 +00:00
print ( " continuing process %u " % ( pid ) )
2013-06-27 18:08:32 +00:00
process . Continue ( )
elif state == lldb . eStateExited :
exit_desc = process . GetExitDescription ( )
if exit_desc :
2019-03-21 18:27:40 +00:00
print (
" process %u exited with status %u : %s "
% ( pid , process . GetExitStatus ( ) , exit_desc )
2023-05-25 08:48:57 -07:00
)
2013-06-27 18:08:32 +00:00
else :
2019-03-21 18:27:40 +00:00
print (
" process %u exited with status %u "
% ( pid , process . GetExitStatus ( ) )
2023-05-25 08:48:57 -07:00
)
2013-06-27 18:08:32 +00:00
run_commands (
command_interpreter , options . exit_commands
)
done = True
elif state == lldb . eStateCrashed :
2019-03-21 18:27:40 +00:00
print ( " process %u crashed " % ( pid ) )
2013-06-27 18:08:32 +00:00
print_threads ( process , options )
run_commands (
command_interpreter , options . crash_commands
)
done = True
elif state == lldb . eStateDetached :
2019-03-21 18:27:40 +00:00
print ( " process %u detached " % ( pid ) )
2013-06-27 18:08:32 +00:00
done = True
elif state == lldb . eStateRunning :
# process is running, don't say anything,
# we will always get one of these after
# resuming
2013-06-26 22:23:45 +00:00
if options . verbose :
2019-03-21 18:27:40 +00:00
print ( " process %u resumed " % ( pid ) )
2013-06-27 18:08:32 +00:00
elif state == lldb . eStateUnloaded :
2019-03-21 18:27:40 +00:00
print (
" process %u unloaded, this shouldn ' t happen "
% ( pid )
2023-05-25 08:48:57 -07:00
)
2013-06-27 18:08:32 +00:00
done = True
elif state == lldb . eStateConnected :
2019-03-21 18:27:40 +00:00
print ( " process connected " )
2013-06-27 18:08:32 +00:00
elif state == lldb . eStateAttaching :
2019-03-21 18:27:40 +00:00
print ( " process attaching " )
2013-06-27 18:08:32 +00:00
elif state == lldb . eStateLaunching :
2019-03-21 18:27:40 +00:00
print ( " process launching " )
2013-06-26 22:23:45 +00:00
else :
2019-03-21 18:27:40 +00:00
print ( " event = %s " % ( event ) )
2013-06-27 18:08:32 +00:00
else :
# timeout waiting for an event
2019-03-21 18:27:40 +00:00
print (
" no process event for %u seconds, killing the process... "
% ( options . event_timeout )
2023-05-25 08:48:57 -07:00
)
2013-06-27 18:08:32 +00:00
done = True
# Now that we are done dump the stdout and stderr
process_stdout = process . GetSTDOUT ( 1024 )
if process_stdout :
2019-03-21 18:27:40 +00:00
print ( " Process STDOUT: \n %s " % ( process_stdout ) )
2013-06-27 18:08:32 +00:00
while process_stdout :
process_stdout = process . GetSTDOUT ( 1024 )
2019-03-21 18:27:40 +00:00
print ( process_stdout )
2013-06-27 18:08:32 +00:00
process_stderr = process . GetSTDERR ( 1024 )
if process_stderr :
2019-03-21 18:27:40 +00:00
print ( " Process STDERR: \n %s " % ( process_stderr ) )
2013-06-27 18:08:32 +00:00
while process_stderr :
process_stderr = process . GetSTDERR ( 1024 )
2019-03-21 18:27:40 +00:00
print ( process_stderr )
2012-08-31 01:11:17 +00:00
process . Kill ( ) # kill the process
2012-09-25 18:27:12 +00:00
else :
if error :
2019-03-21 18:27:40 +00:00
print ( error )
2012-09-25 18:27:12 +00:00
else :
if launch_info :
2019-03-21 18:27:40 +00:00
print ( " error: launch failed " )
2012-09-25 18:27:12 +00:00
else :
2019-03-21 18:27:40 +00:00
print ( " error: attach failed " )
2016-09-06 20:57:50 +00:00
2012-08-31 01:11:17 +00:00
lldb . SBDebugger . Terminate ( )
2023-05-25 08:48:57 -07:00
2012-08-31 01:11:17 +00:00
if __name__ == " __main__ " :
main ( sys . argv [ 1 : ] )