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