Loading...
tools/lldbmacros/utils.py xnu-12377.101.15 /dev/null
--- xnu/xnu-12377.101.15/tools/lldbmacros/utils.py
+++ /dev/null
@@ -1,664 +0,0 @@
-#General Utility functions for debugging or introspection
-
-""" Please make sure you read the README file COMPLETELY BEFORE reading anything below.
-    It is very critical that you read coding guidelines in Section E in README file. 
-"""
-import sys, re, time, os, time
-import lldb
-import struct
-from typing import (
-    Optional,
-)
-
-from core.cvalue import *
-from core.configuration import *
-from core.lazytarget import *
-
-#DONOTTOUCHME: exclusive use for lldb_run_command only. 
-lldb_run_command_state = {'active':False}
-
-def lldb_run_command(cmdstring):
-    """ Run a lldb command and get the string output.
-        params: cmdstring - str : lldb command string which could be executed at (lldb) prompt. (eg. "register read")
-        returns: str - output of command. it may be "" in case if command did not return any output.
-    """
-    global lldb_run_command_state
-    retval =""
-    res = lldb.SBCommandReturnObject()
-    # set special attribute to notify xnu framework to not print on stdout
-    lldb_run_command_state['active'] = True
-    lldb.debugger.GetCommandInterpreter().HandleCommand(cmdstring, res)
-    lldb_run_command_state['active'] = False
-    if res.Succeeded():
-        retval = res.GetOutput()
-    else:
-        retval = "ERROR:" + res.GetError()
-    return retval
-
-def EnableLLDBAPILogging():
-    """ Enable file based logging for lldb and also provide essential information about what information
-        to include when filing a bug with lldb or xnu.
-    """
-    logfile_name = "/tmp/lldb.%d.log" % int(time.time())
-    enable_log_base_cmd = "log enable --file %s " % logfile_name
-    cmd_str = enable_log_base_cmd + ' lldb api'
-    print(cmd_str)
-    print(lldb_run_command(cmd_str))
-    cmd_str = enable_log_base_cmd + ' gdb-remote packets'
-    print(cmd_str)
-    print(lldb_run_command(cmd_str))
-    cmd_str = enable_log_base_cmd + ' kdp-remote packets'
-    print(cmd_str)
-    print(lldb_run_command(cmd_str))
-    print(f"{lldb.SBDebugger.GetVersionString()}\n")
-    print("Please collect the logs from %s for filing a radar. If you had encountered an exception in a lldbmacro command please re-run it." % logfile_name)
-    print("Please make sure to provide the output of 'version', 'image list' and output of command that failed.")
-    return
-
-def GetConnectionProtocol():
-    """ Returns a string representing what kind of connection is used for debugging the target.
-        params: None
-        returns:
-            str - connection type. One of ("core","kdp","gdb", "unknown")
-    """
-    retval = "unknown"
-    process_plugin_name = LazyTarget.GetProcess().GetPluginName().lower()
-    if "kdp" in process_plugin_name:
-        retval = "kdp"
-    elif "gdb" in process_plugin_name:
-        retval = "gdb"
-    elif "mach-o" in process_plugin_name and "core" in process_plugin_name:
-        retval = "core"
-    return retval
-
-def SBValueToPointer(sbval):
-    """ Helper function for getting pointer value from an object of pointer type. 
-        ex. void *astring = 0x12345
-        use SBValueToPointer(astring_val) to get 0x12345
-        params: sbval - value object of type '<type> *'
-        returns: int - pointer value as an int. 
-    """
-    if type(sbval) == core.value:
-        sbval = sbval.GetSBValue()
-    if sbval.IsPointerType():
-        return sbval.GetValueAsUnsigned()
-    else:
-        return int(sbval.GetAddress())
-
-def ArgumentStringToAddress(arg_string) -> int:
-    """ converts an argument to an address
-        params:
-            arg_string: str - typically a string passed from the commandline.
-                        Accepted inputs:
-                        1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5"
-                        2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))"
-        returns:
-            int - integer representation of the string
-    """
-    try:
-        return int(arg_string, 0)
-    except ValueError:
-        val = LazyTarget.GetTarget().chkEvaluateExpression(arg_string)
-        return val.unsigned
-
-def ArgumentStringToSBValue(arg_string: str, type: str) -> int:
-    """ converts an argument to an SBValue of type
-        params:
-            arg_string: str - typically a string passed from the commandline.
-                        Accepted inputs:
-                        1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5"
-                        2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))"
-            type:       str - a C/C++ type name
-        returns:
-            SBValue - an lldb value of the proper type
-    """
-
-    target = LazyTarget.GetTarget()
-    addr   = target.chkEvaluateExpression(arg_string).xGetValueAsInteger()
-    return target.xCreateValueFromAddress(None, addr, gettype(type))
-
-def ArgumentStringToInt(arg_string) -> int:
-    """ converts an argument to an int
-        params:
-            arg_string: str - typically a string passed from the commandline.
-                        Accepted inputs:
-                        1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5"
-                        2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))"
-        returns:
-            int - integer representation of the string
-    """
-    try:
-        return int(arg_string, 0)
-    except ValueError:
-        val = LazyTarget.GetTarget().chkEvaluateExpression(arg_string)
-        return val.signed
-
-def GetLongestMatchOption(searchstr, options=[], ignore_case=True):
-    """ Get longest matched string from set of options. 
-        params:
-            searchstr : string of chars to be matched
-            options : array of strings that are to be matched
-        returns:
-            [] - array of matched options. The order of options is same as the arguments.
-                 empty array is returned if searchstr does not match any option.
-        example:
-            subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True)
-            print subcommand # prints ['reload']
-    """
-    if ignore_case:
-        searchstr = searchstr.lower()
-    found_options = []
-    for o in options:
-        so = o
-        if ignore_case:
-            so = o.lower()
-        if so == searchstr:
-            return [o]
-        if so.find(searchstr) >=0 :
-            found_options.append(o)
-    return found_options
-
-def GetType(target_type):
-    """ type cast an object to new type.
-        params:
-            target_type - str, ex. 'char', 'uint32_t' etc
-        returns:
-            lldb.SBType - a new Type that can be used as param to  lldb.SBValue.Cast()
-        raises:
-            NameError  - Incase the type is not identified
-    """
-    return gettype(target_type)
-
-    
-def Cast(obj, target_type):
-    """ Type cast an object to another C type.
-        params:
-            obj - core.value  object representing some C construct in lldb
-            target_type - str : ex 'char *'
-                        - lldb.SBType :
-    """
-    return cast(obj, target_type)
-
-def ContainerOf(obj, target_type, field_name):
-    """ Type cast an object to another C type from a pointer to a field.
-        params:
-            obj - core.value  object representing some C construct in lldb
-            target_type - str : ex 'struct thread'
-                        - lldb.SBType :
-            field_name - the field name within the target_type obj is a pointer to
-    """
-    return containerof(obj, target_type, field_name)
-
-def loadLLDB():
-    """ Util function to load lldb python framework in case not available in common include paths.
-    """
-    try:
-        import lldb
-        print('Found LLDB on path')
-    except:
-        platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split())
-        offset = platdir.find("Contents/Developer")
-        if offset == -1:
-            lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python')
-        else:
-            lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python')
-        if os.path.isdir(lldb_py):
-            sys.path.append(lldb_py)
-            global lldb
-            lldb = __import__('lldb')
-            print('Found LLDB in SDK')
-        else:
-            print('Failed to locate lldb.py from', lldb_py)
-            sys.exit(-1)
-    return True
-
-class Logger(object):
-    """ A logging utility """
-    def __init__(self, log_file_path="/tmp/xnu.log"):
-        self.log_file_handle = open(log_file_path, "w+")
-        self.redirect_to_stdout = False
-        
-    def log_debug(self, *args):
-        current_timestamp = time.time()
-        debug_line_str = "DEBUG:" + str(current_timestamp) + ":"
-        for arg in args:
-            debug_line_str += " " + str(arg).replace("\n", " ") + ", "
-        
-        self.log_file_handle.write(debug_line_str + "\n")
-        if self.redirect_to_stdout :
-            print(debug_line_str)
-    
-    def write(self, line):
-        self.log_debug(line)
-
-
-def sizeof_fmt(num, unit_str='B'):
-    """ format large number into human readable values.
-        convert any number into Kilo, Mega, Giga, Tera format for human understanding.
-        params:
-            num - int : number to be converted
-            unit_str - str : a suffix for unit. defaults to 'B' for bytes.
-        returns:
-            str - formatted string for printing.
-    """
-    for x in ['','K','M','G','T']:
-        if num < 1024.0:
-            return "%3.1f%s%s" % (num, x,unit_str)
-        num /= 1024.0
-    return "%3.1f%s%s" % (num, 'P', unit_str)
-
-def WriteStringToMemoryAddress(stringval, addr):
-    """ write a null terminated string to address. 
-        params:
-            stringval: str- string to be written to memory. a '\0' will be added at the end
-            addr : int - address where data is to be written
-        returns:
-            bool - True if successfully written
-    """
-    serr = lldb.SBError()
-    length = len(stringval) + 1
-    format_string = "%ds" % length
-    sdata = struct.pack(format_string,stringval.encode())
-    numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr)
-    if numbytes == length and serr.Success():
-        return True
-    return False
-
-def WriteInt64ToMemoryAddress(intval, addr):
-    """ write a 64 bit integer at an address.
-        params:
-          intval - int - an integer value to be saved
-          addr - int - address where int is to be written
-        returns:
-          bool - True if successfully written.
-    """
-    serr = lldb.SBError()
-    sdata = struct.pack('Q', intval)
-    addr = int(hex(addr).rstrip('L'), 16)
-    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
-    if numbytes == 8 and serr.Success():
-        return True
-    return False 
-
-def WritePtrDataToMemoryAddress(intval, addr):
-    """ Write data to pointer size memory. 
-        This is equivalent of doing *(&((struct pmap *)addr)) = intval
-        It will identify 32/64 bit kernel and write memory accordingly.
-        params:
-          intval - int - an integer value to be saved
-          addr - int - address where int is to be written
-        returns:
-          bool - True if successfully written.
-    """
-    if kern.ptrsize == 8:
-        return WriteInt64ToMemoryAddress(intval, addr)
-    else:
-        return WriteInt32ToMemoryAddress(intval, addr)
-
-def WriteInt32ToMemoryAddress(intval, addr):
-    """ write a 32 bit integer at an address.
-        params:
-          intval - int - an integer value to be saved
-          addr - int - address where int is to be written
-        returns:
-          bool - True if successfully written.
-    """
-    serr = lldb.SBError()
-    sdata = struct.pack('I', intval)
-    addr = int(hex(addr).rstrip('L'), 16)
-    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
-    if numbytes == 4 and serr.Success():
-        return True
-    return False 
-
-def WriteInt16ToMemoryAddress(intval, addr):
-    """ write a 16 bit integer at an address.
-        params:
-          intval - int - an integer value to be saved
-          addr - int - address where int is to be written
-        returns:
-          bool - True if successfully written.
-    """
-    serr = lldb.SBError()
-    sdata = struct.pack('H', intval)
-    addr = int(hex(addr).rstrip('L'), 16)
-    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
-    if numbytes == 2 and serr.Success():
-        return True
-    return False 
-
-def WriteInt8ToMemoryAddress(intval, addr):
-    """ write a 8 bit integer at an address.
-        params:
-          intval - int - an integer value to be saved
-          addr - int - address where int is to be written
-        returns:
-          bool - True if successfully written.
-    """
-    serr = lldb.SBError()
-    sdata = struct.pack('B', intval)
-    addr = int(hex(addr).rstrip('L'), 16)
-    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
-    if numbytes == 1 and serr.Success():
-        return True
-    return False 
-
-_enum_cache = {}
-def GetEnumValue(enum_name_or_combined, member_name = None):
-    """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION  => 0x3
-        params:
-            enum_name_or_combined: str
-                name of an enum of the format type::name (legacy)
-                name of an enum type
-            member_name: None, or the name of an enum member
-                   (then enum_name_or_combined is a type name).
-        returns:
-            int - value of the particular enum.
-        raises:
-            TypeError - if the enum is not found
-    """
-    global _enum_cache
-    if member_name is None:
-        enum_name, member_name = enum_name_or_combined.strip().split("::")
-    else:
-        enum_name = enum_name_or_combined
-
-    if enum_name not in _enum_cache:
-        ty = GetType(enum_name)
-        d  = {}
-
-        for e in ty.get_enum_members_array():
-            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
-                d[e.GetName()] = e.GetValueAsSigned()
-            else:
-                d[e.GetName()] = e.GetValueAsUnsigned()
-
-        _enum_cache[enum_name] = d
-
-    return _enum_cache[enum_name][member_name]
-
-def GetEnumValues(enum_name, names):
-    """ Finds the values of a particular set of enum defines.
-        params:
-            enum_name: str
-                name of an enum type
-            member_name: str list
-                list of fields to resolve
-        returns:
-            int list - value of the particular enum.
-        raises:
-            TypeError - if the enum is not found
-    """
-    return [GetEnumValue(enum_name, x) for x in names]
-
-_enum_name_cache = {}
-def GetEnumName(enum_name, value, prefix = ''):
-    """ Finds symbolic name for a particular enum integer value
-        params:
-            enum_name - str:   name of an enum type
-            value     - value: the value to decode
-            prefix    - str:   a prefix to strip from the tag
-        returns:
-            str - the symbolic name or UNKNOWN(value)
-        raises:
-            TypeError - if the enum is not found
-    """
-    global _enum_name_cache
-
-    ty = GetType(enum_name)
-
-    if enum_name not in _enum_name_cache:
-        ty_dict  = {}
-
-        for e in ty.get_enum_members_array():
-            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
-                ty_dict[e.GetValueAsSigned()] = e.GetName()
-            else:
-                ty_dict[e.GetValueAsUnsigned()] = e.GetName()
-
-        _enum_name_cache[enum_name] = ty_dict
-    else:
-        ty_dict = _enum_name_cache[enum_name]
-
-    if ty.GetTypeFlags() & lldb.eTypeIsSigned:
-        key = int(value)
-    else:
-        key = unsigned(value)
-
-    name = ty_dict.get(key, "UNKNOWN({:d})".format(key))
-    if name.startswith(prefix):
-        return name[len(prefix):]
-    return name
-
-def GetOptionString(enum_name, value, prefix = ''):
-    """ Tries to format a given value as a combination of options
-        params:
-            enum_name - str:   name of an enum type
-            value     - value: the value to decode
-            prefix    - str:   a prefix to strip from the tag
-        raises:
-            TypeError - if the enum is not found
-    """
-    ty = GetType(enum_name)
-
-    if enum_name not in _enum_name_cache:
-        ty_dict  = {}
-
-        for e in ty.get_enum_members_array():
-            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
-                ty_dict[e.GetValueAsSigned()] = e.GetName()
-            else:
-                ty_dict[e.GetValueAsUnsigned()] = e.GetName()
-
-        _enum_name_cache[enum_name] = ty_dict
-    else:
-        ty_dict = _enum_name_cache[enum_name]
-
-    if ty.GetTypeFlags() & lldb.eTypeIsSigned:
-        v = int(value)
-    else:
-        v = unsigned(value)
-
-    flags = []
-    for bit in range(0, 64):
-        mask = 1 << bit
-        if not v & mask: continue
-        if mask not in ty_dict: continue
-        name = ty_dict[mask]
-        if name.startswith(prefix):
-            name = name[len(prefix):]
-        flags.append(name)
-        v &= ~mask
-    if v:
-        flags.append("UNKNOWN({:d})".format(v))
-    return " ".join(flags)
-
-def ResolveFSPath(path):
-    """ expand ~user directories and return absolute path.
-        params: path - str - eg "~rc/Software"
-        returns:
-                str - abs path with user directories and symlinks expanded.
-                str - if path resolution fails then returns the same string back
-    """
-    expanded_path = os.path.expanduser(path)
-    norm_path = os.path.normpath(expanded_path)
-    return norm_path
-
-_dsymlist = {}
-uuid_regex = re.compile("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",re.IGNORECASE|re.DOTALL)
-def addDSYM(uuid, info):
-    """ add a module by dsym into the target modules. 
-        params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
-                info - dict - info dictionary passed from dsymForUUID
-    """
-    global _dsymlist
-    if "DBGSymbolRichExecutable" not in info:
-        print("Error: Unable to find syms for %s" % uuid)
-        return False
-    if not uuid in _dsymlist:
-        # add the dsym itself
-        cmd_str = "target modules add --uuid %s" % uuid
-        debuglog(cmd_str)
-        lldb.debugger.HandleCommand(cmd_str)
-        # set up source path
-        #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"]))
-        # modify the list to show we loaded this
-        _dsymlist[uuid] = True
-
-def loadDSYM(uuid, load_address, sections=[]):
-    """ Load an already added symbols to a particular load address
-        params: uuid - str - uuid string
-                load_address - int - address where to load the symbols
-        returns bool:
-            True - if successful
-            False - if failed. possible because uuid is not presently loaded.
-    """
-    if uuid not in _dsymlist:
-        return False
-    if not sections:
-        cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address)
-        debuglog(cmd_str)
-    else:
-        cmd_str = "target modules load --uuid {}   ".format(uuid)
-        sections_str = ""
-        for s in sections:
-            sections_str += " {} {:#0x} ".format(s.name, s.vmaddr)
-        cmd_str += sections_str
-        debuglog(cmd_str)
-
-    lldb.debugger.HandleCommand(cmd_str)
-    return True
-
-
-def RunShellCommand(command):
-    """ Run a shell command in subprocess.
-        params: command with arguments to run (a list is preferred, but a string is also supported)
-        returns: (exit_code, stdout, stderr)
-    """
-    import subprocess
-
-    if not isinstance(command, list):
-        import shlex
-        command = shlex.split(command)
-
-    result = subprocess.run(command, capture_output=True, encoding="utf-8")
-    returncode =  result.returncode
-    stdout = result.stdout
-    stderr = result.stderr
-
-    if returncode != 0:
-        print("Failed to run command. Command: {}, "
-              "exit code: {}, stdout: '{}', stderr: '{}'".format(command, returncode, stdout, stderr))
-
-    return (returncode, stdout, stderr)
-
-def dsymForUUID(uuid):
-    """ Get dsym informaiton by calling dsymForUUID 
-        params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
-        returns:
-            {} - a dictionary holding dsym information printed by dsymForUUID. 
-            None - if failed to find information
-    """
-    import plistlib
-    rc, output, _ = RunShellCommand(["/usr/local/bin/dsymForUUID", "--copyExecutable", uuid])
-    if rc != 0:
-        return None
-
-    if output:
-        # because of <rdar://12713712>
-        #plist = plistlib.readPlistFromString(output)
-        #beginworkaround
-        keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL)
-        plist={}
-        plist[uuid] = {}
-        for item in keyvalue_extract_re.findall(output):
-            plist[uuid][item[0]] = item[1]
-        #endworkaround
-        if plist and plist[uuid]:
-            return plist[uuid]
-    return None
-
-def debuglog(s):
-    """ Print a object in the debug stream
-    """
-    global config
-    if config['debug']:
-      print("DEBUG:",s)
-    return None
-
-def IsAppleInternal():
-    """ check if apple_internal modules are available
-        returns: True if apple_internal module is present
-    """
-    import imp
-    try:
-        imp.find_module("apple_internal")
-        retval = True
-    except ImportError:
-        retval = False
-    return retval
-
-def print_hex_data(data, start=0, desc="", marks={}, prefix=" ", extra=None):
-    """ print on stdout "hexdump -C < data" like output
-        params:
-            data - bytearray or array of int where each int < 255
-            start - int offset that should be printed in left column
-            desc - str optional description to print on the first line to describe data
-            mark - dictionary of markers
-            extra - a function returning some extra things to add on the line
-    """
-
-    if desc:
-        print("{}:".format(desc))
-
-    end = start + len(data)
-
-    for row in range(start & -16, end, 16):
-        line  = ""
-        chars = ""
-        lend  = ""
-
-        for col in range(16):
-            addr = row + col
-
-            if col == 8:
-                line += " "
-            if start <= addr < end:
-                b      = data[addr - start]
-                line  += "{}{:02x}".format(marks.get(addr, ' '), b)
-                chars += chr(b) if 0x20 <= b < 0x80 else '.'
-            else:
-                line  += "   "
-                chars += ' '
-
-        if extra:
-            lend = extra(row)
-            if lend: lend = " " + lend
-
-        print("{}{:#016x} {}  |{}|{}".format(prefix, row, line, chars, lend))
-
-def Ones(x):
-    return (1 << x)-1
-
-def CanonicalAddress(x, TySz):
-    """ Canonicalize an address. That is to say, sign-extend the upper 64-TySz
-        address bits with either 0 or 1 depending on bit 55.
-
-        params:
-            x: The address to modify.
-            TySz: Size of the corresponding VA region.
-    """
-    sign_mask = 1 << 55
-    ptr_mask = Ones(64-TySz)
-    msb_mask = ~ptr_mask
-    sign = x & sign_mask
-    if sign:
-        # Sign-extend
-        return (x | msb_mask) + 2**64
-    else:
-        # Zero-extend
-        return x & ptr_mask
-
-@cache_statically
-def IsDebuggingCore(target: lldb.SBTarget=None):
-    return "mach-o-core" in target.process.GetPluginName().lower()