【分析】生成dalvik解释器原文件的脚本:gen-mterp.py

源码参考:进入

路径:dalvik/vm/mterp/gen-mterp.py

----------------------------------------------------------------------------------------------------

概述

gen-mterp.py根据特定体系结构配置文件,生成dalvik解释器的C和汇编源码。

portable模式

portable模式的配置文件分析可以参考config-portable文件。

时序图


代码分析

首先进入main代码,解析命令行参数,然后提取opcode列表:
#
# ===========================================================================
# "main" code
#

# @param[in] target_arch 要生成的模式。
#            如:portable。输入portable时,程序就会去找同目录下的config-portable配置文件。
# @param[in] output_dir 输出目录。
#
# Check args.
#
if len(sys.argv) != 3:
    print "Usage: %s target-arch output-dir" % sys.argv[0]
    sys.exit(2)

target_arch = sys.argv[1]
output_dir = sys.argv[2]

# 提取 opcode 列表。
#
# Extract opcode list.
#
opcodes = getOpcodeList()
......

getOpcodeList()函数:
# 读取文件,获得goto表。
#
# Extract an ordered list of instructions from the VM sources.  We use the
# "goto table" definition macro, which has exactly kNumPackedOpcodes
# entries.
#
def getOpcodeList():
    opcodes = []
    # 打开定义opcode的文件。
    opcode_fp = open(interp_defs_file)
    # 利用正则表达式匹配行,如:H(OP_NOT)
    opcode_re = re.compile(r"^\s*H\(OP_(\w+)\),.*", re.DOTALL)
    for line in opcode_fp:
        match = opcode_re.match(line)
        if not match:
            continue    # 如果不匹配,continue
        # 如果匹配,则在opcodes列表中添加一条记录。
        opcodes.append("OP_" + match.group(1))
    opcode_fp.close()

    # 如果列表中元素的个数与kNumPackedOpcodes的值不相等,则抛出异常。
    if len(opcodes) != kNumPackedOpcodes:
        print "ERROR: found %d opcodes in Interp.h (expected %d)" \
                % (len(opcodes), kNumPackedOpcodes)
        raise SyntaxError, "bad opcode count"
    return opcodes

interp_defs_file的值在文件的开始处定义: interp_defs_file = "../../libdex/DexOpcodes.h"。打开 DexOpcodes.h 文件,读取匹配的行,它会匹配这个文件中下面以空格和’H’开头的行:
/*
 * Macro used to generate a computed goto table for use in implementing
 * an interpreter in C.
 */
#define DEFINE_GOTO_TABLE(_name) \
    static const void* _name[kNumPackedOpcodes] = {                      \
        /* BEGIN(libdex-goto-table); GENERATED AUTOMATICALLY BY opcode-gen */ \
        H(OP_NOP),                                                            \
        H(OP_MOVE),                                                           \
        H(OP_MOVE_FROM16),                                                    \
        H(OP_MOVE_16),                                                        \
        ......

匹配结束后,opcodes中的元素是这种格式的:OP_NOP、OP_MOVE……也就是说去掉了”H”和括号。


继续看main代码:
#
# 打开配置文件。
#
try:
    config_fp = open("config-%s" % target_arch)
except:
    print "Unable to open config file 'config-%s'" % target_arch
    sys.exit(1)

# 打开两个输出文件,一个是 C 文件,一个是汇编文件。
#
# 打开并准备输出文件。
#
try:
    c_fp = open("%s/InterpC-%s.cpp" % (output_dir, target_arch), "w")
    asm_fp = open("%s/InterpAsm-%s.S" % (output_dir, target_arch), "w")
except:
    print "Unable to open output files"
    print "Make sure directory '%s' exists and existing files are writable" \
            % output_dir
    # Ideally we'd remove the files to avoid confusing "make", but if they
    # failed to open we probably won't be able to remove them either.
    sys.exit(1)

print "Generating %s, %s" % (c_fp.name, asm_fp.name)

file_header = """/*
 * This file was generated automatically by gen-mterp.py for '%s'.
 *
 * --> DO NOT EDIT <--
 */

""" % (target_arch)

# 写文件头部。
c_fp.write(file_header)
asm_fp.write(file_header)
......


上面的代码中先打开了两个输出文件,一个是 C 文件,一个是汇编文件。然后将 file_header变量保存的内容写入文件。 
继续,处理配置文件:
#
# 处理配置文件。
#
failed = False
try:
    # 循环逐行读取配置文件内容。
    for line in config_fp:
        line = line.strip()         # remove CRLF, leading spaces
        tokens = line.split(' ')    # tokenize
        #print "%d: %s" % (len(tokens), tokens)
        if len(tokens[0]) == 0:
            #print "  blank"
            pass
        elif tokens[0][0] == '#':
            #print "  comment"
            pass
        else:
            if tokens[0] == "handler-size":
                setHandlerSize(tokens)
            elif tokens[0] == "import":
                importFile(tokens)  # 对配置文件中的import导入的文件进行解析,然后写入到输出文件。
            elif tokens[0] == "asm-stub":
                setAsmStub(tokens)
            elif tokens[0] == "asm-alt-stub":
                setAsmAltStub(tokens)
            elif tokens[0] == "op-start":
                opStart(tokens)
            elif tokens[0] == "op-end":
                opEnd(tokens)
            elif tokens[0] == "alt":
                altEntry(tokens)
            elif tokens[0] == "op":
                opEntry(tokens)
            elif tokens[0] == "handler-style":
                setHandlerStyle(tokens)
            elif tokens[0] == "alt-ops":
                genaltop(tokens)
            elif tokens[0] == "split-ops":
                splitops = True
            else:
                raise DataParseError, "unrecognized command '%s'" % tokens[0]
            if style == None:
                print "tokens[0] = %s" % tokens[0]
                raise DataParseError, "handler-style must be first command"
except DataParseError, err:
    print "Failed: " + str(err)
    # TODO: remove output files so "make" doesn't get confused
    failed = True
    c_fp.close()
    asm_fp.close()
    c_fp = asm_fp = None

config_fp.close()

#
# Done!
#
if c_fp:
    c_fp.close()
if asm_fp:
    asm_fp.close()

sys.exit(failed)

config-portable配置文件中有这么几个关键字: handler-styleimportop-startop-end,所以解析配置文件的函数我们只需要关注这些:
  • setHandlerStyle - handler-style
  • importFile - import
  • opStart - op-start
  • opEnd - op-end
setHandlerStyle函数设置全局变量 style,这个变量代表解释器的风格。通过配置文件得知,portable模式的解释器风格是 all-c,它构建出来的源码只有cpp文件。

importFile函数:
# 解析配置文件 -- 拷贝一个文件输出到C或asm文件中。
#
# Parse arch config file --
# Copy a file in to the C or asm output file.
#
def importFile(tokens):
    if len(tokens) != 2:
        raise DataParseError("import requires one argument")
    source = tokens[1]  # 源文件路径。据我查看配置文件发现,这个源文件路径都是相对路径。

    # 函数getGlobalSubDict()返回一个map,里面有两个key,分别为:handler_size_bits、handler_size_bits。
    # 这两个key对应的值是同名的全局变量。
    # 如果是portable模式,这两个值均会保持默认值:-1000。

    # appendSourceFile函数将导入的文件内容解析并写入到输出文件。
    # 当是portable模式时,只是把文件中的内容写入输出文件,并不需要解析。

    # 如果是portable模式,不可能导入".S"文件,即汇编文件。

    if source.endswith(".cpp"):
        appendSourceFile(tokens[1], getGlobalSubDict(), c_fp, None)
    elif source.endswith(".S"):
        appendSourceFile(tokens[1], getGlobalSubDict(), asm_fp, None)
    else:
        raise DataParseError("don't know how to import %s (expecting .cpp/.S)"
                % source)

opStart函数:
# 函数设置全局变量in_op_start的值为1。
# 全局变量default_op_dir则被设置为操作码所在的目录。
# 在portable模式下,操作码在c目录中。
#
# Parse arch config file --
# Start of opcode list.
#
def opStart(tokens):
    global in_op_start
    global default_op_dir
    if len(tokens) != 2:
        raise DataParseError("opStart takes a directory name argument")
    if in_op_start != 0:
        raise DataParseError("opStart can only be specified once")
    default_op_dir = tokens[1]  # 操作码所在目录
    in_op_start = 1

opEnd函数:
# 设置全局变量in_op_start的值为2。
#
# Parse arch config file --
# End of opcode list; emit instruction blocks.
#
def opEnd(tokens):
    global in_op_start
    if len(tokens) != 1:
        raise DataParseError("opEnd takes no arguments")
    if in_op_start != 1:
        raise DataParseError("opEnd must follow opStart, and only appear once")
    in_op_start = 2

    # 读取opcode文件并解析,然后输出到文件。
    loadAndEmitOpcodes()
    if splitops == False:   # 在portable模式下,splitops为false。
        if generate_alt_table:  # portable模式下,generate_alt_table为false。
            loadAndEmitAltOpcodes()
            if style == "jump-table":
                emitJmpTable("dvmAsmInstructionStart", label_prefix);
                emitJmpTable("dvmAsmAltInstructionStart", alt_label_prefix);

loadAndEmitOpcodes函数:
#
# Load and emit opcodes for all kNumPackedOpcodes instructions.
#
def loadAndEmitOpcodes():

    ......

    # 循环读取opcode文件并解析,然后输出到文件中。
    # 在portable模式下,只会调用loadAndEmitC函数,输出到 C 文件中。
    for i in xrange(kNumPackedOpcodes):
        op = opcodes[i]

        # portable模式下,opcode_locations应该是空的。
        if opcode_locations.has_key(op):
            location = opcode_locations[op]
        else:
            location = default_op_dir

        if location == "c": # portable模式下,这里我只关心loadAndEmitC函数。
            # 读取opcode文件并解析,然后输出到 C 文件中。
            loadAndEmitC(location, i)
            # portable模式下,asm_stub_text的长度应该为0。
            if len(asm_stub_text) == 0:
                need_dummy_start = True
        else:
            loadAndEmitAsm(location, i, sister_list)

    ......

    if style == "computed-goto":
        ......

loadAndEmitC函数:
# 读取opcode文件并解析,然后输出到 C 文件中。
# @param[in] location opcode文件所在目录。
# @param[in] opindex opcode在列表中的索引。
#
# Load a C fragment and emit it, then output an assembly stub.
#
def loadAndEmitC(location, opindex):
    # 根据opindex来获得opcode文件名,然后拼接出文件的路径。
    op = opcodes[opindex]
    source = "%s/%s.cpp" % (location, op)
    if verbose:
        print " emit %s --> C++" % source
    dict = getGlobalSubDict()
    # 更新列表中的键值。
    # op是opcode的名字,opindex是索引。
    dict.update({ "opcode":op, "opnum":opindex })

    # 读取原文件并解析,然后输出到c_fp文件中。
    appendSourceFile(source, dict, c_fp, None)

    # portable模式下,asm_stub_text的值应该为0。
    if len(asm_stub_text) != 0:
        emitAsmStub(asm_fp, dict)

appendSourceFile函数:
# 写portable风格的文件时,只是把配置文件中import导入的C文件,写入输出文件中。
#
# Append the file specified by "source" to the open "outfp".  Each line will
# be template-replaced using the substitution dictionary "dict".
#
# If the first line of the file starts with "%" it is taken as a directive.
# A "%include" line contains a filename and, optionally, a Python-style
# dictionary declaration with substitution strings.  (This is implemented
# with recursion.)
#
# If "sister_list" is provided, and we find a line that contains only "&",
# all subsequent lines from the file will be appended to sister_list instead
# of copied to the output.
#
# This may modify "dict".
#
def appendSourceFile(source, dict, outfp, sister_list):
    outfp.write("/* File: %s */\n" % source)    # "/* File: %s */" 用于说明这部分代码是从哪个文件拷贝出来的。
    infp = open(source, "r")    # 从源文件读取内容。
    in_sister = False
    for line in infp:
        ......

        # perform keyword substitution if a dictionary was provided
        if dict != None:
            templ = Template(line)
            try:
                subline = templ.substitute(dict)
            except KeyError, err:
                raise DataParseError("keyword substitution failed in %s: %s"
                        % (source, str(err)))
            except:
                print "ERROR: substitution failed: " + line
                raise
        else:
            subline = line

        ......

    # 写文件。
    outfp.write("\n")
    infp.close()


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值