472 lines · python
1#!/usr/bin/env python2 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/Python7# On MacOSX sh, bash:8# export PYTHONPATH=/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python9# ----------------------------------------------------------------------10 11import optparse12import os13import platform14import sys15import subprocess16 17# ----------------------------------------------------------------------18# Code that auto imports LLDB19# ----------------------------------------------------------------------20try:21 # Just try for LLDB in case PYTHONPATH is already correctly setup22 import lldb23except ImportError:24 lldb_python_dirs = list()25 # lldb is not in the PYTHONPATH, try some defaults for the current platform26 platform_system = platform.system()27 if platform_system == "Darwin":28 # On Darwin, try the currently selected Xcode directory29 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 = False43 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 lldb49 except ImportError:50 pass51 else:52 print('imported lldb from: "%s"' % (lldb_python_dir))53 success = True54 break55 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 break78 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 breakpoint86# at "malloc" and backtrace and read all registers each time we stop87#----------------------------------------------------------------------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.epilog92 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 return252 253 attach_info = None254 launch_info = None255 exe = None256 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 instance282 debugger = lldb.SBDebugger.Create()283 debugger.SetAsync(True)284 command_interpreter = debugger.GetCommandInterpreter()285 # Create a target from a file and arch286 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 the294 # command line command to take advantage of the shorthand breakpoint295 # creation296 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 return303 # from this function until we hit the breakpoint at main304 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 ok333 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 an338 # initial event339 process.Continue()340 341 listener = debugger.GetListener()342 # sign up for process state change events343 stop_idx = 0344 done = False345 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 event352 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_commands380 )381 else:382 if options.verbose:383 print("process %u stopped" % (pid))384 run_commands(385 command_interpreter, options.stop_commands386 )387 stop_idx += 1388 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_commands405 )406 done = True407 elif state == lldb.eStateCrashed:408 print("process %u crashed" % (pid))409 print_threads(process, options)410 run_commands(411 command_interpreter, options.crash_commands412 )413 done = True414 elif state == lldb.eStateDetached:415 print("process %u detached" % (pid))416 done = True417 elif state == lldb.eStateRunning:418 # process is running, don't say anything,419 # we will always get one of these after420 # resuming421 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 = True429 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 event439 print(440 "no process event for %u seconds, killing the process..."441 % (options.event_timeout)442 )443 done = True444 # Now that we are done dump the stdout and stderr445 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 process458 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