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