1#!/usr/bin/python 2 3#---------------------------------------------------------------------- 4# Be sure to add the python path that points to the LLDB shared library. 5# On MacOSX csh, tcsh: 6# setenv PYTHONPATH /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python 7# On MacOSX sh, bash: 8# export PYTHONPATH=/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python 9#---------------------------------------------------------------------- 10 11from __future__ import print_function 12 13import optparse 14import os 15import platform 16import sys 17import subprocess 18 19#---------------------------------------------------------------------- 20# Code that auto imports LLDB 21#---------------------------------------------------------------------- 22try: 23 # Just try for LLDB in case PYTHONPATH is already correctly setup 24 import lldb 25except ImportError: 26 lldb_python_dirs = list() 27 # lldb is not in the PYTHONPATH, try some defaults for the current platform 28 platform_system = platform.system() 29 if platform_system == 'Darwin': 30 # On Darwin, try the currently selected Xcode directory 31 xcode_dir = subprocess.check_output("xcode-select --print-path", shell=True) 32 if xcode_dir: 33 lldb_python_dirs.append( 34 os.path.realpath( 35 xcode_dir + 36 '/../SharedFrameworks/LLDB.framework/Resources/Python')) 37 lldb_python_dirs.append( 38 xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 39 lldb_python_dirs.append( 40 '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 41 success = False 42 for lldb_python_dir in lldb_python_dirs: 43 if os.path.exists(lldb_python_dir): 44 if not (sys.path.__contains__(lldb_python_dir)): 45 sys.path.append(lldb_python_dir) 46 try: 47 import lldb 48 except ImportError: 49 pass 50 else: 51 print('imported lldb from: "%s"' % (lldb_python_dir)) 52 success = True 53 break 54 if not success: 55 print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly") 56 sys.exit(1) 57 58 59def print_threads(process, options): 60 if options.show_threads: 61 for thread in process: 62 print('%s %s' % (thread, thread.GetFrameAtIndex(0))) 63 64 65def run_commands(command_interpreter, commands): 66 return_obj = lldb.SBCommandReturnObject() 67 for command in commands: 68 command_interpreter.HandleCommand(command, return_obj) 69 if return_obj.Succeeded(): 70 print(return_obj.GetOutput()) 71 else: 72 print(return_obj) 73 if options.stop_on_error: 74 break 75 76 77def main(argv): 78 description = '''Debugs a program using the LLDB python API and uses asynchronous broadcast events to watch for process state changes.''' 79 epilog = '''Examples: 80 81#---------------------------------------------------------------------- 82# Run "/bin/ls" with the arguments "-lAF /tmp/", and set a breakpoint 83# at "malloc" and backtrace and read all registers each time we stop 84#---------------------------------------------------------------------- 85% ./process_events.py --breakpoint malloc --stop-command bt --stop-command 'register read' -- /bin/ls -lAF /tmp/ 86 87''' 88 optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog 89 parser = optparse.OptionParser( 90 description=description, 91 prog='process_events', 92 usage='usage: process_events [options] program [arg1 arg2]', 93 epilog=epilog) 94 parser.add_option( 95 '-v', 96 '--verbose', 97 action='store_true', 98 dest='verbose', 99 help="Enable verbose logging.", 100 default=False) 101 parser.add_option( 102 '-b', 103 '--breakpoint', 104 action='append', 105 type='string', 106 metavar='BPEXPR', 107 dest='breakpoints', 108 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.') 109 parser.add_option( 110 '-a', 111 '--arch', 112 type='string', 113 dest='arch', 114 help='The architecture to use when creating the debug target.', 115 default=None) 116 parser.add_option( 117 '--platform', 118 type='string', 119 metavar='platform', 120 dest='platform', 121 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".', 122 default=None) 123 parser.add_option( 124 '-l', 125 '--launch-command', 126 action='append', 127 type='string', 128 metavar='CMD', 129 dest='launch_commands', 130 help='LLDB command interpreter commands to run once after the process has launched. This option can be specified more than once.', 131 default=[]) 132 parser.add_option( 133 '-s', 134 '--stop-command', 135 action='append', 136 type='string', 137 metavar='CMD', 138 dest='stop_commands', 139 help='LLDB command interpreter commands to run each time the process stops. This option can be specified more than once.', 140 default=[]) 141 parser.add_option( 142 '-c', 143 '--crash-command', 144 action='append', 145 type='string', 146 metavar='CMD', 147 dest='crash_commands', 148 help='LLDB command interpreter commands to run in case the process crashes. This option can be specified more than once.', 149 default=[]) 150 parser.add_option( 151 '-x', 152 '--exit-command', 153 action='append', 154 type='string', 155 metavar='CMD', 156 dest='exit_commands', 157 help='LLDB command interpreter commands to run once after the process has exited. This option can be specified more than once.', 158 default=[]) 159 parser.add_option( 160 '-T', 161 '--no-threads', 162 action='store_false', 163 dest='show_threads', 164 help="Don't show threads when process stops.", 165 default=True) 166 parser.add_option( 167 '--ignore-errors', 168 action='store_false', 169 dest='stop_on_error', 170 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.", 171 default=True) 172 parser.add_option( 173 '-n', 174 '--run-count', 175 type='int', 176 dest='run_count', 177 metavar='N', 178 help='How many times to run the process in case the process exits.', 179 default=1) 180 parser.add_option( 181 '-t', 182 '--event-timeout', 183 type='int', 184 dest='event_timeout', 185 metavar='SEC', 186 help='Specify the timeout in seconds to wait for process state change events.', 187 default=lldb.UINT32_MAX) 188 parser.add_option( 189 '-e', 190 '--environment', 191 action='append', 192 type='string', 193 metavar='ENV', 194 dest='env_vars', 195 help='Environment variables to set in the inferior process when launching a process.') 196 parser.add_option( 197 '-d', 198 '--working-dir', 199 type='string', 200 metavar='DIR', 201 dest='working_dir', 202 help='The the current working directory when launching a process.', 203 default=None) 204 parser.add_option( 205 '-p', 206 '--attach-pid', 207 type='int', 208 dest='attach_pid', 209 metavar='PID', 210 help='Specify a process to attach to by process ID.', 211 default=-1) 212 parser.add_option( 213 '-P', 214 '--attach-name', 215 type='string', 216 dest='attach_name', 217 metavar='PROCESSNAME', 218 help='Specify a process to attach to by name.', 219 default=None) 220 parser.add_option( 221 '-w', 222 '--attach-wait', 223 action='store_true', 224 dest='attach_wait', 225 help='Wait for the next process to launch when attaching to a process by name.', 226 default=False) 227 try: 228 (options, args) = parser.parse_args(argv) 229 except: 230 return 231 232 attach_info = None 233 launch_info = None 234 exe = None 235 if args: 236 exe = args.pop(0) 237 launch_info = lldb.SBLaunchInfo(args) 238 if options.env_vars: 239 launch_info.SetEnvironmentEntries(options.env_vars, True) 240 if options.working_dir: 241 launch_info.SetWorkingDirectory(options.working_dir) 242 elif options.attach_pid != -1: 243 if options.run_count == 1: 244 attach_info = lldb.SBAttachInfo(options.attach_pid) 245 else: 246 print("error: --run-count can't be used with the --attach-pid option") 247 sys.exit(1) 248 elif not options.attach_name is None: 249 if options.run_count == 1: 250 attach_info = lldb.SBAttachInfo( 251 options.attach_name, options.attach_wait) 252 else: 253 print("error: --run-count can't be used with the --attach-name option") 254 sys.exit(1) 255 else: 256 print('error: a program path for a program to debug and its arguments are required') 257 sys.exit(1) 258 259 # Create a new debugger instance 260 debugger = lldb.SBDebugger.Create() 261 debugger.SetAsync(True) 262 command_interpreter = debugger.GetCommandInterpreter() 263 # Create a target from a file and arch 264 265 if exe: 266 print("Creating a target for '%s'" % exe) 267 error = lldb.SBError() 268 target = debugger.CreateTarget( 269 exe, options.arch, options.platform, True, error) 270 271 if target: 272 273 # Set any breakpoints that were specified in the args if we are launching. We use the 274 # command line command to take advantage of the shorthand breakpoint 275 # creation 276 if launch_info and options.breakpoints: 277 for bp in options.breakpoints: 278 debugger.HandleCommand("_regexp-break %s" % (bp)) 279 run_commands(command_interpreter, ['breakpoint list']) 280 281 for run_idx in range(options.run_count): 282 # Launch the process. Since we specified synchronous mode, we won't return 283 # from this function until we hit the breakpoint at main 284 error = lldb.SBError() 285 286 if launch_info: 287 if options.run_count == 1: 288 print('Launching "%s"...' % (exe)) 289 else: 290 print('Launching "%s"... (launch %u of %u)' % (exe, run_idx + 1, options.run_count)) 291 292 process = target.Launch(launch_info, error) 293 else: 294 if options.attach_pid != -1: 295 print('Attaching to process %i...' % (options.attach_pid)) 296 else: 297 if options.attach_wait: 298 print('Waiting for next to process named "%s" to launch...' % (options.attach_name)) 299 else: 300 print('Attaching to existing process named "%s"...' % (options.attach_name)) 301 process = target.Attach(attach_info, error) 302 303 # Make sure the launch went ok 304 if process and process.GetProcessID() != lldb.LLDB_INVALID_PROCESS_ID: 305 306 pid = process.GetProcessID() 307 print('Process is %i' % (pid)) 308 if attach_info: 309 # continue process if we attached as we won't get an 310 # initial event 311 process.Continue() 312 313 listener = debugger.GetListener() 314 # sign up for process state change events 315 stop_idx = 0 316 done = False 317 while not done: 318 event = lldb.SBEvent() 319 if listener.WaitForEvent(options.event_timeout, event): 320 if lldb.SBProcess.EventIsProcessEvent(event): 321 state = lldb.SBProcess.GetStateFromEvent(event) 322 if state == lldb.eStateInvalid: 323 # Not a state event 324 print('process event = %s' % (event)) 325 else: 326 print("process state changed event: %s" % (lldb.SBDebugger.StateAsCString(state))) 327 if state == lldb.eStateStopped: 328 if stop_idx == 0: 329 if launch_info: 330 print("process %u launched" % (pid)) 331 run_commands( 332 command_interpreter, ['breakpoint list']) 333 else: 334 print("attached to process %u" % (pid)) 335 for m in target.modules: 336 print(m) 337 if options.breakpoints: 338 for bp in options.breakpoints: 339 debugger.HandleCommand( 340 "_regexp-break %s" % (bp)) 341 run_commands( 342 command_interpreter, ['breakpoint list']) 343 run_commands( 344 command_interpreter, options.launch_commands) 345 else: 346 if options.verbose: 347 print("process %u stopped" % (pid)) 348 run_commands( 349 command_interpreter, options.stop_commands) 350 stop_idx += 1 351 print_threads(process, options) 352 print("continuing process %u" % (pid)) 353 process.Continue() 354 elif state == lldb.eStateExited: 355 exit_desc = process.GetExitDescription() 356 if exit_desc: 357 print("process %u exited with status %u: %s" % (pid, process.GetExitStatus(), exit_desc)) 358 else: 359 print("process %u exited with status %u" % (pid, process.GetExitStatus())) 360 run_commands( 361 command_interpreter, options.exit_commands) 362 done = True 363 elif state == lldb.eStateCrashed: 364 print("process %u crashed" % (pid)) 365 print_threads(process, options) 366 run_commands( 367 command_interpreter, options.crash_commands) 368 done = True 369 elif state == lldb.eStateDetached: 370 print("process %u detached" % (pid)) 371 done = True 372 elif state == lldb.eStateRunning: 373 # process is running, don't say anything, 374 # we will always get one of these after 375 # resuming 376 if options.verbose: 377 print("process %u resumed" % (pid)) 378 elif state == lldb.eStateUnloaded: 379 print("process %u unloaded, this shouldn't happen" % (pid)) 380 done = True 381 elif state == lldb.eStateConnected: 382 print("process connected") 383 elif state == lldb.eStateAttaching: 384 print("process attaching") 385 elif state == lldb.eStateLaunching: 386 print("process launching") 387 else: 388 print('event = %s' % (event)) 389 else: 390 # timeout waiting for an event 391 print("no process event for %u seconds, killing the process..." % (options.event_timeout)) 392 done = True 393 # Now that we are done dump the stdout and stderr 394 process_stdout = process.GetSTDOUT(1024) 395 if process_stdout: 396 print("Process STDOUT:\n%s" % (process_stdout)) 397 while process_stdout: 398 process_stdout = process.GetSTDOUT(1024) 399 print(process_stdout) 400 process_stderr = process.GetSTDERR(1024) 401 if process_stderr: 402 print("Process STDERR:\n%s" % (process_stderr)) 403 while process_stderr: 404 process_stderr = process.GetSTDERR(1024) 405 print(process_stderr) 406 process.Kill() # kill the process 407 else: 408 if error: 409 print(error) 410 else: 411 if launch_info: 412 print('error: launch failed') 413 else: 414 print('error: attach failed') 415 416 lldb.SBDebugger.Terminate() 417 418if __name__ == '__main__': 419 main(sys.argv[1:]) 420