418 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			418 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env 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
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
import optparse
 | 
						|
import os
 | 
						|
import platform
 | 
						|
import sys
 | 
						|
import subprocess
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# 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 = subprocess.check_output("xcode-select --print-path", shell=True)
 | 
						|
        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)
 | 
						|
 | 
						|
 | 
						|
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.'''
 | 
						|
    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)
 | 
						|
    parser.add_option(
 | 
						|
        '-v',
 | 
						|
        '--verbose',
 | 
						|
        action='store_true',
 | 
						|
        dest='verbose',
 | 
						|
        help="Enable verbose logging.",
 | 
						|
        default=False)
 | 
						|
    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)
 | 
						|
    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)
 | 
						|
    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=[])
 | 
						|
    parser.add_option(
 | 
						|
        '-T',
 | 
						|
        '--no-threads',
 | 
						|
        action='store_false',
 | 
						|
        dest='show_threads',
 | 
						|
        help="Don't show threads when process stops.",
 | 
						|
        default=True)
 | 
						|
    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)
 | 
						|
    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)
 | 
						|
    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 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)
 | 
						|
    try:
 | 
						|
        (options, args) = parser.parse_args(argv)
 | 
						|
    except:
 | 
						|
        return
 | 
						|
 | 
						|
    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:
 | 
						|
        print('error: a program path for a program to debug and its arguments are required')
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    # Create a new debugger instance
 | 
						|
    debugger = lldb.SBDebugger.Create()
 | 
						|
    debugger.SetAsync(True)
 | 
						|
    command_interpreter = debugger.GetCommandInterpreter()
 | 
						|
    # Create a target from a file and arch
 | 
						|
 | 
						|
    if exe:
 | 
						|
        print("Creating a target for '%s'" % exe)
 | 
						|
    error = lldb.SBError()
 | 
						|
    target = debugger.CreateTarget(
 | 
						|
        exe, options.arch, options.platform, True, error)
 | 
						|
 | 
						|
    if target:
 | 
						|
 | 
						|
        # 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
 | 
						|
        if launch_info and options.breakpoints:
 | 
						|
            for bp in options.breakpoints:
 | 
						|
                debugger.HandleCommand("_regexp-break %s" % (bp))
 | 
						|
            run_commands(command_interpreter, ['breakpoint list'])
 | 
						|
 | 
						|
        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
 | 
						|
            error = lldb.SBError()
 | 
						|
 | 
						|
            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)
 | 
						|
 | 
						|
            # Make sure the launch went ok
 | 
						|
            if process and process.GetProcessID() != lldb.LLDB_INVALID_PROCESS_ID:
 | 
						|
 | 
						|
                pid = process.GetProcessID()
 | 
						|
                print('Process is %i' % (pid))
 | 
						|
                if attach_info:
 | 
						|
                    # continue process if we attached as we won't get an
 | 
						|
                    # initial event
 | 
						|
                    process.Continue()
 | 
						|
 | 
						|
                listener = debugger.GetListener()
 | 
						|
                # 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):
 | 
						|
                        if lldb.SBProcess.EventIsProcessEvent(event):
 | 
						|
                            state = lldb.SBProcess.GetStateFromEvent(event)
 | 
						|
                            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))
 | 
						|
                                            run_commands(
 | 
						|
                                                command_interpreter, ['breakpoint list'])
 | 
						|
                                        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
 | 
						|
                                    if options.verbose:
 | 
						|
                                        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")
 | 
						|
                        else:
 | 
						|
                            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)
 | 
						|
                process.Kill()  # kill the process
 | 
						|
            else:
 | 
						|
                if error:
 | 
						|
                    print(error)
 | 
						|
                else:
 | 
						|
                    if launch_info:
 | 
						|
                        print('error: launch failed')
 | 
						|
                    else:
 | 
						|
                        print('error: attach failed')
 | 
						|
 | 
						|
    lldb.SBDebugger.Terminate()
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main(sys.argv[1:])
 |