#!/usr/bin/env python3 """ A gdb-compatible frontend for lldb that implements just enough commands to run the tests in the debuginfo-tests repository with lldb. """ # ---------------------------------------------------------------------- # Auto-detect lldb python module. import subprocess, platform, os, sys try: # Just try for LLDB in case PYTHONPATH is already correctly setup. import lldb except ImportError: # Ask the command line driver for the path to the lldb module. Copy over # the environment so that SDKROOT is propagated to xcrun. command = ( ["xcrun", "lldb", "-P"] if platform.system() == "Darwin" else ["lldb", "-P"] ) # Extend the PYTHONPATH if the path exists and isn't already there. lldb_python_path = subprocess.check_output(command).decode("utf-8").strip() if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path): sys.path.append(lldb_python_path) # Try importing LLDB again. try: import lldb print('imported lldb from: "%s"' % lldb_python_path) except ImportError: print( "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly" ) sys.exit(1) # ---------------------------------------------------------------------- # Command line option handling. import argparse parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--quiet", "-q", action="store_true", help="ignored") parser.add_argument( "-batch", action="store_true", help="exit after processing comand line" ) parser.add_argument("-n", action="store_true", help="ignore .lldb file") parser.add_argument( "-x", dest="script", type=argparse.FileType("r"), help="execute commands from file" ) parser.add_argument("target", help="the program to debug") args = parser.parse_args() # Create a new debugger instance. debugger = lldb.SBDebugger.Create() debugger.SkipLLDBInitFiles(args.n) # Make sure to clean up the debugger on exit. import atexit def on_exit(): debugger.Terminate() atexit.register(on_exit) # Don't return from lldb function calls until the process stops. debugger.SetAsync(False) # Create a target from a file and arch. arch = os.popen("file " + args.target).read().split()[-1] target = debugger.CreateTargetWithFileAndArch(args.target, arch) if not target: print("Could not create target %s" % args.target) sys.exit(1) if not args.script: print("Interactive mode is not implemented.") sys.exit(1) import re for command in args.script: # Strip newline and whitespaces and split into words. cmd = command[:-1].strip().split() if not cmd: continue print("> %s" % command[:-1]) try: if re.match("^r|(run)$", cmd[0]): error = lldb.SBError() launchinfo = lldb.SBLaunchInfo([]) launchinfo.SetWorkingDirectory(os.getcwd()) process = target.Launch(launchinfo, error) print(error) if not process or error.fail: state = process.GetState() print("State = %d" % state) print( """ ERROR: Could not launch process. NOTE: There are several reasons why this may happen: * Root needs to run "DevToolsSecurity --enable". * Older versions of lldb cannot launch more than one process simultaneously. """ ) sys.exit(1) elif re.match("^b|(break)$", cmd[0]) and len(cmd) == 2: if re.match("[0-9]+", cmd[1]): # b line mainfile = target.FindFunctions("main")[0].compile_unit.file print(target.BreakpointCreateByLocation(mainfile, int(cmd[1]))) else: # b file:line file, line = cmd[1].split(":") print(target.BreakpointCreateByLocation(file, int(line))) elif re.match("^ptype$", cmd[0]) and len(cmd) == 2: # GDB's ptype has multiple incarnations depending on its # argument (global variable, function, type). The definition # here is for looking up the signature of a function and only # if that fails it looks for a type with that name. # Type lookup in LLDB would be "image lookup --type". for elem in target.FindFunctions(cmd[1]): print(elem.function.type) continue print(target.FindFirstType(cmd[1])) elif re.match("^po$", cmd[0]) and len(cmd) > 1: try: opts = lldb.SBExpressionOptions() opts.SetFetchDynamicValue(True) opts.SetCoerceResultToId(True) print(target.EvaluateExpression(" ".join(cmd[1:]), opts)) except: # FIXME: This is a fallback path for the lab.llvm.org # buildbot running OS X 10.7; it should be removed. thread = process.GetThreadAtIndex(0) frame = thread.GetFrameAtIndex(0) print(frame.EvaluateExpression(" ".join(cmd[1:]))) elif re.match("^p|(print)$", cmd[0]) and len(cmd) > 1: thread = process.GetThreadAtIndex(0) frame = thread.GetFrameAtIndex(0) print(frame.EvaluateExpression(" ".join(cmd[1:]))) elif re.match("^n|(next)$", cmd[0]): thread = process.GetThreadAtIndex(0) thread.StepOver() elif re.match("^q|(quit)$", cmd[0]): sys.exit(0) else: print(debugger.HandleCommand(" ".join(cmd))) except SystemExit: raise except: print('Could not handle the command "%s"' % " ".join(cmd))