先贴个源码,全部的源码就不贴了,太长了。。。

直接从main函数按照二级标题的顺序分析流程把

解析参数

    parser = OptionParser(usage)
    parser.add_option('', '--logcat_limit_time_sec',
                      dest='logcat_limit_time', type='int', default=0,
                      help='Defined the max time logcat parse running')
    parser.add_option('', '--ftrace_limit_time_sec',
                      dest='ftrace_limit_time', type='int', default=0,
                      help='Defined the max time ftrace parse running')
    parser.add_option('-e', '--ram-file', dest='ram_addr',
                      help='List of ram files (name, start, end)', action='callback', callback=parse_ram_file)
    parser.add_option('-v', '--vmlinux', dest='vmlinux', help='vmlinux path')
    parser.add_option('-n', '--nm-path', dest='nm', help='nm path')
    parser.add_option('-g', '--gdb-path', dest='gdb', help='gdb path')
    parser.add_option('-j', '--objdump-path', dest='objdump', help='objdump path')
    parser.add_option('-a', '--auto-dump', dest='autodump',
                      help='Auto find ram dumps from the path')
    parser.add_option('-o', '--outdir', dest='outdir', help='Output directory')
    parser.add_option('-s', '--t32launcher', action='store_true',
                      dest='t32launcher', help='Create T32 simulator launcher', default=False)
    parser.add_option('', '--t32-host-system', dest='t32_host_system',
                      metavar='HOST', choices=('Linux', 'Windows'),
                      help='T32 host system (for launcher script generation). Supported choices: "Linux", "Windows". Defaults to the system ramparse.py is running on.')
    parser.add_option('-x', '--everything', action='store_true',
                      dest='everything', help='Output everything (may be slow')
    parser.add_option('-f', '--output-file', dest='outfile',
                      help='Name of file to save output')
    parser.add_option('', '--stdout', action='store_true',
                      dest='stdout', help='Dump to stdout instead of the file')
    parser.add_option('', '--phys-offset', type='int',
                      dest='phys_offset', help='use custom phys offset')
    parser.add_option('', '--kaslr-offset', type='int',
                      dest='kaslr_offset',
                      help='Offset for address space layout randomization')
    parser.add_option('', '--page-offset', type='int',
                      dest='page_offset', help='use custom page offset')
    parser.add_option('', '--force-hardware',
                      dest='force_hardware', help='Force the hardware detection')
    parser.add_option(
        '', '--force-version', type='int', dest='force_hardware_version',
        help='Force the hardware detection to a specific hardware version')
    parser.add_option('-l', '--kernel-exception-level', type='int',
                      dest='currentEL', help='Current exception level for kernel')
    parser.add_option('', '--parse-qdss', action='store_true',
                      dest='qdss', help='Parse QDSS (deprecated)')
    parser.add_option('', '--64-bit', action='store_true', dest='arm64',
                      help='Parse dumps as 64-bit dumps (default)')
    parser.add_option('', '--32-bit', action='store_true', dest='arm32',
                      help='Parse dumps as 32-bit dumps')
    parser.add_option('', '--shell', action='store_true',
                      help='Run an interactive python interpreter with the ramdump loaded')
    parser.add_option('', '--classic-shell', action='store_true',
                      help='Like --shell, but forces the use of the classic python shell, even if ipython is installed')
    parser.add_option('', '--qtf', action='store_true', dest='ftrace_format',
                      help='Use QTF tool to parse and save QDSS trace data')
    parser.add_option('', '--ftrace-formats', action='store_true', dest='ftrace_format',
                      help='Extracts formats.txt used for generating ftrace')
    parser.add_option('', '--qtf-path', dest='qtf_path',
                      help='QTF tool executable')
    parser.add_option('', '--skip-qdss-bin', action='store_true',
                      dest='skip_qdss_bin', help='Skip QDSS ETF and ETR '
                      'binary data parsing from debug image (may save time '
                      'if large ETM and ETR buffers are present)')
    parser.add_option('', '--eval',
                      help='Evaluate some python code directly, or from stdin if "-" is passed. The "dump" variable will be available, as it is with the --shell option.')  # noqa
    parser.add_option('', '--wlan', dest='wlan', help='wlan.ko path')
    parser.add_option('', '--minidump', action='store_true', dest='minidump',
                      help='Parse minidump')
    # Adding option for reduced dump
    parser.add_option('', '--reduceddump', action='store_true', dest='reduceddump',
                  help='Parse reduceddump')
    parser.add_option('', '--svm', default='', dest='svm',action='store',type="string",
                      help='Parse svm')
    parser.add_option('', '--ram-elf', dest='ram_elf_addr',
                      help='pass ap_minidump.elf generated by crashscope')
    parser.add_option('-m', '--mod_path', action='append', dest='mod_path_list', help='Symbol path to all loadable module ".ko.unstripped" files. Files will be searched for recursively in sub-directories. Multiple paths can be provided using -m <path> -m <path> ...')
    parser.add_option('', '--timeout', dest='timeout', help='symbol path to all loadable modules')
    parser.add_option('', '--dump_mod_sym_tbl', action='store_true', dest='dump_module_symbol_table',
                      help='dump all symbols within a module whose symbol file is available.', default=False)
    parser.add_option('', '--dump_krnl_sym_tbl', action='store_true', dest='dump_kernel_symbol_table',
                      help='dump all symbols within the Linux kernel', default=False)
    parser.add_option('', '--dump_mod_kallsyms', action='store_true', dest='dump_module_kallsyms',
                      help='dump all symbols retrieved from kallsyms of a specific module', default=False)
    parser.add_option('', '--dump_glb_sym_tbl', action='store_true', dest='dump_global_symbol_table',
                      help='dump all symbols within the global symbol lookup table', default=False)
    parser.add_option('', '--hyp', dest='hyp', help='elf file containing hypervisor symbol information. Required for --svm')
    parser.add_option('', '--host_vmlinux', dest='host_vmlinux', help='host vmlinux. Required for --svm')
    parser.add_option('', '--host_mod_path',  action='append', dest='host_mod_path_list', help='symbol path to all loadable modules. Required for --svm')

    parser.add_option('--dbg', '--debug', action='store_true', dest='debug',
                      help=textwrap.dedent("""\
                      Enable debug.
                      Stop ignoring exceptions from running a parser. Intended to be used with:
                      python -m pdb ramparse.py <args for ramparse.py>
                      """))
    parser.add_option('', '--ftrace-args', type='string', action='callback',
                          callback=get_ftrace_args, dest = 'ftrace_args',
                          help="""
                          Comma separated trace subsystems to capture. Works with --dump-ftrace parser option
                          trace_names = [
                                         "binder", "bootreceiver", "clock_reg",
                                         "kgsl-fence", "memory", "mmc", "rproc_qcom",
                                         "suspend", "ufs", "usb", "wifi"
                                         ]
                          """,
                          default=[])
    parser.add_option('--fs', '--ftrace_buffer_size_kb', type='int', dest='ftrace_max_size',
                      help="""
                      This option indicates that ftrace trace buffer max size in KB.
                      It will be passed to ensure an early bailout from ftrace parser if size goes
                      beyond this specified value.
                      Example: --fs 4096
                      This specifies that max size is 4096 KB.
                      """)
    parser.add_option('', '--skip_TLB_Cache_parse', action='store_true', help='Skip parsing TLB Cache Dumps in parse_debug_image')
    parser.add_option('--iommu-pg-table-format', action='store', choices=['fastrpc', 'default'],
                      default='default')

    for p in parser_util.get_parsers():
        parser.add_option(p.shortopt or '',
                          p.longopt,
                          dest=p.cls.__name__,
                          help=p.desc,
                          action='store_true')

    (options, args) = parser.parse_args()

这里是对解析脚本携带的参数的解析逻辑。值得注意的是,这里使用了插件的思想。

动态插件注册

parser_util.py脚本实现了插件的自动注册以及加载原理

加载插件文件

    for p in parser_util.get_parsers():
        parser.add_option(p.shortopt or '',
                          p.longopt,
                          dest=p.cls.__name__,
                          help=p.desc,
                          action='store_true')

执行parser_util.get_parsers函数

def get_parsers():
    """Imports everyone under the ``parsers`` directory. It is expected that
    the parsers under the parsers directory will be a collection of
    classes that subclass RamParser and use the register_parser
    decorator to register themselves with the parser
    framework. Therefore, importing all the modules under ``parsers``
    should have the side-effect of populating the (internal to
    parser_util) _parsers list with the discovered parsers.

    Returns the list of ParserConfig instances built as a side-effect
    of the importing.

    """

    import_all_by_path('parsers')
    import_all_by_path(os.path.join('extensions','parsers'))
    return _parsers

def import_all_by_path(path):
    """Imports everyone under the given directory. It is expected that
    the modules under the given directory will register themselves with
    one of the decorators below.

    Note that the import is effectively a noop if the module has already
    been imported, so there's no harm in calling with the same path
    multiple times"""

    dir = os.path.join(os.path.dirname(__file__), path)
    if not os.path.isdir(dir):
        return

    package = path.replace(os.sep, '.')
    for f in sorted(glob.glob(os.path.join(dir, '*.py'))):
        modname_ext = os.path.basename(f)
        if modname_ext == '__init__.py':
            continue

        modname = os.path.splitext(modname_ext)[0]
        __import__(package + '.' + modname)

原理很简单,调用

    import_all_by_path('parsers')
    import_all_by_path(os.path.join('extensions','parsers'))

去检查parsers以及extensions/parsers目录下的py文件(排除__init__.py),然后使用import装载插件文件

注册插件

各个插件文件中通过register_parser注册插件

def register_parser(longopt, desc, shortopt=None, optional=False):
    """Decorator for registering a parser class.

    The class being decorated should inherit from the ``RamParser``
    class. By using this decorator your parser will automatically be hooked
    up to the command-line parsing code.

    This makes it very easy and clean to add a new parser:

      1. Drop a new file in the ``parsers/`` directory that defines a
         class that inherits from ``RamParser``
      2. Decorate your class with ``@register_parser``
      3. Define a ``parse`` method for your class

    All of the command line argument handling and invoking the parse
    method of your parser will then be handled automatically.

    Example::

       # file: parsers/my_banner.py
       @register_parser('--banner', 'Print the kernel banner')
       class BannerParser(RamParser):

           def parse(self):
               print(self.ramdump.read_cstring('linux_banner', 256, False))

    :param longopt: The longopt command line switch for this parser
    :param desc: A short description of the parser (also shown in the
        help-text associated with the longopt)
    :param shortopt: The shortopt command line switch for this parser.
        This should only be used for maintaining backwards compatibility
        with legacy parsers.  Otherwise shortopts are reserved for core
        parser options.
    :param optional: Indicates the parser is optional and should not be run
        with ``--everything``
    """
    def wrapper(cls):
        if cls in [p.cls for p in _parsers]:
            raise Exception(cls + ' is already registered!')
        _parsers.append(ParserConfig(cls, longopt, desc, shortopt, optional))
        return cls
    return wrapper

通过wrapper来创建ParserConfig

class ParserConfig(object):

    """Class to encapsulate a RamParser its desired setup (command-line
    options, etc)."""

    def __init__(self, cls, longopt, desc, shortopt, optional):
        self.cls = cls
        self.longopt = longopt
        self.desc = desc
        self.shortopt = shortopt
        self.optional = optional

插件的基类

class RamParser(object):

    """Base class for implementing ramdump parsers. New parsers should inherit
    from this class and define a ``parse`` method.

    Interesting properties that will be set for usage in derived
    classes:

    - ramdump:: The RamDump instance being parsed

    """

    def __init__(self, ramdump):
        self.ramdump = ramdump

    def parse(self):
        raise NotImplementedError

    def parse_param(self):
        '''
        This function provide an interface to pass a parameter to sub-parser
        eg:
        --coredump pid=1 log_level=DEBUG then coredump parser could get pid and log_level by user set
        --mounts pid=1 dump mount info of process with pid = 1
        --mounts proc=surfaceflinger  dump mount info of surfaceflinger
        '''
        param = {}
        for arg in sys.argv:
            if "=" in arg:
                key, val = arg.split('=')
                param[key] = val
        return param

定义的基类,被插件类继承

例如:


@register_parser('--dump-ftrace', 'extract ftrace by iterate the ring buffer page',optional=True)
class FtraceParser(RamParser):

    def __init__(self, *args):
        super(FtraceParser, self).__init__(*args)
        self.format_event_map = OrderedDict()
        self.format_event_field_map = OrderedDict()
        self.event_call = 'struct trace_event_call'

配置GNU工具

    gdb_path = options.gdb
    nm_path = options.nm
    objdump_path = options.objdump

    try:
        import local_settings
        try:
            gdb_ndk_path = None
            if options.arm64:
                gdb_path = gdb_path or local_settings.gdb64_path
                if hasattr(local_settings, 'gdb64_ndk_path'):
                    gdb_ndk_path = local_settings.gdb64_ndk_path
                nm_path = nm_path or local_settings.nm64_path
                objdump_path = objdump_path or local_settings.objdump64_path
            else:
                gdb_path = gdb_path or local_settings.gdb_path
                nm_path = nm_path or local_settings.nm_path
                objdump_path = objdump_path or local_settings.objdump_path
        except AttributeError as e:
            print_out_str("local_settings.py looks bogus. Please see README.txt")
            missing_attr = re.sub(".*has no attribute '(.*)'", '\\1', e.message)
            print_out_str("Specifically, looks like you're missing `%s'\n" % missing_attr)
            print_out_str("Full message: %s" % e.message)
            sys.exit(1)
    except ImportError:
        cross_compile = os.environ.get('CROSS_COMPILE')
        if cross_compile is not None:
            gdb_path = gdb_path or cross_compile+"gdb"
            nm_path = nm_path or cross_compile+"nm"

    if gdb_path is None or nm_path is None:
        print_out_str("!!! Incorrect path for toolchain specified.")
        print_out_str("!!! Please see the README for instructions on setting up local_settings.py or CROSS_COMPILE")
        sys.exit(1)

    print_out_str("Using gdb path {0}".format(gdb_path))
    print_out_str("Using nm path {0}".format(nm_path))

    if not os.path.exists(gdb_path):
        print_out_str("!!! gdb_path {0} does not exist! Check your settings!".format(gdb_path))
        sys.exit(1)

    if not os.access(gdb_path, os.X_OK):
        print_out_str("!!! No execute permissions on gdb path {0}".format(gdb_path))
        print_out_str("!!! Please check the path settings")
        print_out_str("!!! If this tool is being run from a shared location, contact the maintainer")
        sys.exit(1)

    if not os.path.exists(nm_path):
        print_out_str("!!! nm_path {0} does not exist! Check your settings!".format(nm_path))
        sys.exit(1)

    if not os.access(nm_path, os.X_OK):
        print_out_str("!!! No execute permissions on nm path {0}".format(nm_path))
        print_out_str("!!! Please check the path settings")
        print_out_str("!!! If this tool is being run from a shared location, contact the maintainer")
        sys.exit(1)

配置gdb/nm/objdump工具路径,可以通过参数传参,也可以通过配置local_settings.py来配置

RamDump结构体初始化

RamDump(options, nm_path, gdb_path, objdump_path,gdb_ndk_path)

关于这个结构体下篇文章继续

打印kernel cmdline

    if not options.minidump:
        if not dump.print_command_line():
            print_out_str('!!! Error printing saved command line.')
            print_out_str('!!! The vmlinux is probably wrong for the ramdumps')
            print_out_str('!!! Exiting now...')
            sys.exit(1)

    def print_command_line(self):
        command_addr = self.address_of('saved_command_line')   # 读取保存cmdline的全局变量saved_command_line的地址
        if command_addr is not None:
            command_addr = self.read_word(command_addr)
            b = self.read_cstring(command_addr, 2048)  # 读取2048byte长度的strings
            if b is None:
                print_out_str('!!! could not read saved command line address')
                static_command_addr = self.address_of('static_command_line')
                if static_command_addr is not None:
                    static_command_addr = self.read_word(static_command_addr)
                    b = self.read_cstring(static_command_addr, 2048)
                    if b is None:
                        print_out_str('!!! could not read static command line address')
                        return False
                    else:
                        print_out_str('Satic Command Line: ' + b)
                        return True
                return False
            else:
                print_out_str('Saved Command Line: ' + b)   # 输出cmdline字符串
                return True
        else:
            print_out_str('!!! Could not lookup saved command line address')
            return False

插件模块的解析

    if options.minidump:
        parsers_to_run = [p for p in parser_util.get_parsers()
                          if getattr(options, p.cls.__name__)
                          or (options.everything and p.cls.__name__ in default_list)]
    else:
        # 通过遍历所有的插件
        parsers_to_run = [p for p in parser_util.get_parsers()
                      if getattr(options, p.cls.__name__)
                      or (options.everything and not p.optional)]

    for i,p in enumerate(parsers_to_run):
        # 根据条件过滤一些插件
        if options.everything:
            if p.cls.__name__ in everything_exclusion_list:
                continue
        if i == 0:
            sys.stderr.write("\n")
        if options.minidump:
            if p.cls.__name__ not in default_list:
                print("    [%d/%d] %s ... not supported in minidump \n" %
                                 (i + 1, len(parsers_to_run), p.longopt))
                continue

        with print_out_section(p.cls.__name__):
            try:
                if options.timeout:
                    try:
                        # 核心,调用func_timeout执行p.cls(dump).parse
                        func_timeout(int(options.timeout), p.cls(dump).parse)
                    except Exception as e:
                        print_out_str(e)
                else:
                    p.cls(dump).parse()
                after = time.time()
                print_out_str("end time {0} time cost {1} for {2}".format(after, (after - before), p.cls.__name__))
                sys.stdout.write("%fs" % (after - before))
                print_colored_message("SUCCESS!", "Green")
            except:
                # log exceptions and continue by default
                after = time.time()
                print_out_str("end time {0} time cost {1} for {2}".format(after, (after - before), p.cls.__name__))
                sys.stdout.write("%fs" % (after - before))
                if not options.debug:
                    print_out_str('!!! Exception while running {0}'.format(p.cls.__name__))
                    print_out_exception()
                    print_colored_message("FAILED", "Red")
                else:
                    raise

通过调用p.cls(dump).parse,调用插件的parse函数

比如:

这里需要特别注意的是:

    if options.minidump:
        parsers_to_run = [p for p in parser_util.get_parsers()
                          if getattr(options, p.cls.__name__)
                          or (options.everything and p.cls.__name__ in default_list)]
    else:
        # 通过遍历所有的插件
        parsers_to_run = [p for p in parser_util.get_parsers()
                      if getattr(options, p.cls.__name__)
                      or (options.everything and not p.optional)]

如果是解析minidump,设置了options.everything(也就是-x参数),是从default_list中执行插件解析

如果是解析fulldump,设置了options.everything,会默认解析插件的optional为false的所有插件

也就是说,如果你想-x解析想要的插件,只需要把对应插件的optional设为false即可

@register_parser('--slabinfo', 'print information about slabs', optional=True)
@register_parser('--dump-ftrace', 'extract ftrace by iterate the ring buffer page',optional=True)

创建trace32脚本

    if options.t32launcher or options.everything or options.minidump:
        dump.create_t32_launcher()

位于ramdump.py中的create_t32_launcher