2012-08-31 09:11:17 +08:00
#!/usr/bin/python
#----------------------------------------------------------------------
# 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
#----------------------------------------------------------------------
2012-08-31 10:55:56 +08:00
import commands
2012-08-31 09:11:17 +08:00
import optparse
import os
2012-08-31 10:55:56 +08:00
import platform
2012-08-31 09:11:17 +08:00
import sys
2012-08-31 10:55:56 +08: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
xcode_dir = commands . getoutput ( " xcode-select --print-path " )
if xcode_dir :
lldb_python_dirs . append ( os . path . realpath ( xcode_dir + ' /../SharedFrameworks/LLDB.framework/Resources/Python ' ) )
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 :
print ' imported lldb from: " %s " ' % ( lldb_python_dir )
success = True
break
if not success :
print " error: couldn ' t locate the ' lldb ' module, please set PYTHONPATH correctly "
sys . exit ( 1 )
2012-08-31 09:11:17 +08:00
def print_threads ( process , options ) :
if options . show_threads :
for thread in process :
print ' %s %s ' % ( thread , thread . GetFrameAtIndex ( 0 ) )
def run_commands ( command_interpreter , commands ) :
return_obj = lldb . SBCommandReturnObject ( )
for command in commands :
command_interpreter . HandleCommand ( command , return_obj )
if return_obj . Succeeded ( ) :
print return_obj . GetOutput ( )
else :
print return_obj
if options . stop_on_error :
break
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 10:55:56 +08: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 /
'''
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 09:11:17 +08:00
parser . add_option ( ' -v ' , ' --verbose ' , action = ' store_true ' , dest = ' verbose ' , help = " Enable verbose logging. " , default = False )
2012-08-31 10:55:56 +08: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-27 06:23:45 +08: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 10:55:56 +08: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 09:11:17 +08: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-26 02:27:12 +08: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 10:55:56 +08: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-26 02:27:12 +08: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 ' , help = ' The the current working directory when launching a process. ' , 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 09:11:17 +08:00
try :
( options , args ) = parser . parse_args ( argv )
except :
return
2012-09-26 02:27:12 +08: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 :
print " error: --run-count can ' t be used with the --attach-pid option "
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 :
print " error: --run-count can ' t be used with the --attach-name option "
sys . exit ( 1 )
else :
2012-08-31 09:11:17 +08:00
print ' error: a program path for a program to debug and its arguments are required '
sys . exit ( 1 )
2012-09-26 02:27:12 +08:00
2012-08-31 09:11:17 +08:00
2012-08-31 10:55:56 +08:00
2012-08-31 09:11:17 +08:00
# Create a new debugger instance
debugger = lldb . SBDebugger . Create ( )
2013-06-27 06:23:45 +08:00
debugger . SetAsync ( True )
2012-08-31 09:11:17 +08:00
command_interpreter = debugger . GetCommandInterpreter ( )
# Create a target from a file and arch
2012-09-26 02:27:12 +08:00
if exe :
print " Creating a target for ' %s ' " % exe
2013-06-27 06:23:45 +08:00
error = lldb . SBError ( )
target = debugger . CreateTarget ( exe , options . arch , options . platform , True , error )
2012-08-31 09:11:17 +08:00
if target :
2013-06-27 06:23:45 +08: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-26 02:27:12 +08:00
if launch_info and options . breakpoints :
2012-08-31 10:55:56 +08:00
for bp in options . breakpoints :
debugger . HandleCommand ( " _regexp-break %s " % ( bp ) )
run_commands ( command_interpreter , [ ' breakpoint list ' ] )
2012-08-31 09:11:17 +08: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-26 02:27:12 +08:00
error = lldb . SBError ( )
2012-08-31 09:11:17 +08:00
2012-09-26 02:27:12 +08:00
if launch_info :
if options . run_count == 1 :
print ' Launching " %s " ... ' % ( exe )
else :
print ' Launching " %s " ... (launch %u of %u ) ' % ( exe , run_idx + 1 , options . run_count )
process = target . Launch ( launch_info , error )
else :
if options . attach_pid != - 1 :
print ' Attaching to process %i ... ' % ( options . attach_pid )
else :
if options . attach_wait :
print ' Waiting for next to process named " %s " to launch... ' % ( options . attach_name )
else :
print ' Attaching to existing process named " %s " ... ' % ( options . attach_name )
process = target . Attach ( attach_info , error )
2012-08-31 09:11:17 +08:00
# Make sure the launch went ok
2012-09-26 02:27:12 +08:00
if process and process . GetProcessID ( ) != lldb . LLDB_INVALID_PROCESS_ID :
2015-08-13 04:04:01 +08:00
2012-08-31 09:11:17 +08:00
pid = process . GetProcessID ( )
2015-08-13 04:04:01 +08:00
print ' Process is %i ' % ( pid )
if attach_info :
# continue process if we attached as we won't get an initial event
process . Continue ( )
2013-06-27 06:23:45 +08:00
listener = debugger . GetListener ( )
2012-08-31 09:11:17 +08: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-27 06:23:45 +08:00
if lldb . SBProcess . EventIsProcessEvent ( event ) :
state = lldb . SBProcess . GetStateFromEvent ( event )
2013-06-28 02:08:32 +08:00
if state == lldb . eStateInvalid :
# Not a state event
print ' process event = %s ' % ( event )
else :
print " process state changed event: %s " % ( lldb . SBDebugger . StateAsCString ( state ) )
if state == lldb . eStateStopped :
if stop_idx == 0 :
if launch_info :
print " process %u launched " % ( pid )
2013-06-27 06:23:45 +08:00
run_commands ( command_interpreter , [ ' breakpoint list ' ] )
2013-06-28 02:08:32 +08:00
else :
print " attached to process %u " % ( pid )
for m in target . modules :
print m
if options . breakpoints :
for bp in options . breakpoints :
debugger . HandleCommand ( " _regexp-break %s " % ( bp ) )
run_commands ( command_interpreter , [ ' breakpoint list ' ] )
run_commands ( command_interpreter , options . launch_commands )
else :
if options . verbose :
print " process %u stopped " % ( pid )
run_commands ( command_interpreter , options . stop_commands )
stop_idx + = 1
print_threads ( process , options )
print " continuing process %u " % ( pid )
process . Continue ( )
elif state == lldb . eStateExited :
exit_desc = process . GetExitDescription ( )
if exit_desc :
print " process %u exited with status %u : %s " % ( pid , process . GetExitStatus ( ) , exit_desc )
else :
print " process %u exited with status %u " % ( pid , process . GetExitStatus ( ) )
run_commands ( command_interpreter , options . exit_commands )
done = True
elif state == lldb . eStateCrashed :
print " process %u crashed " % ( pid )
print_threads ( process , options )
run_commands ( command_interpreter , options . crash_commands )
done = True
elif state == lldb . eStateDetached :
print " process %u detached " % ( pid )
done = True
elif state == lldb . eStateRunning :
# process is running, don't say anything, we will always get one of these after resuming
2013-06-27 06:23:45 +08:00
if options . verbose :
2013-06-28 02:08:32 +08:00
print " process %u resumed " % ( pid )
elif state == lldb . eStateUnloaded :
print " process %u unloaded, this shouldn ' t happen " % ( pid )
done = True
elif state == lldb . eStateConnected :
print " process connected "
elif state == lldb . eStateAttaching :
print " process attaching "
elif state == lldb . eStateLaunching :
print " process launching "
2013-06-27 06:23:45 +08:00
else :
2013-06-28 02:08:32 +08:00
print ' event = %s ' % ( event )
else :
# timeout waiting for an event
print " no process event for %u seconds, killing the process... " % ( options . event_timeout )
done = True
# Now that we are done dump the stdout and stderr
process_stdout = process . GetSTDOUT ( 1024 )
if process_stdout :
print " Process STDOUT: \n %s " % ( process_stdout )
while process_stdout :
process_stdout = process . GetSTDOUT ( 1024 )
print process_stdout
process_stderr = process . GetSTDERR ( 1024 )
if process_stderr :
print " Process STDERR: \n %s " % ( process_stderr )
while process_stderr :
process_stderr = process . GetSTDERR ( 1024 )
print process_stderr
2012-08-31 09:11:17 +08:00
process . Kill ( ) # kill the process
2012-09-26 02:27:12 +08:00
else :
if error :
print error
else :
if launch_info :
print ' error: launch failed '
else :
print ' error: attach failed '
2012-08-31 09:11:17 +08:00
lldb . SBDebugger . Terminate ( )
if __name__ == ' __main__ ' :
main ( sys . argv [ 1 : ] )