dalvik是android中使用的虚拟机,基于寄存器,分析基于android4.2源代码。本篇主要分析的是dalvik中的解释器部分,源码位于/dalvik/vm,主要代码在interp和mterp文件夹下。
我们知道java能运行在各个平台在于它运行在虚拟机上,由虚拟机来与各个硬件平台打交道(所以说"跨平台"都是假的啰(⊙▽⊙))。则虚拟机中解释器(将字节码解释成机器码)代码必然是依赖于各平台的,且为了方便管理必定是可配置式模块化。怎么理解呢 ◔ ‸◔? 直接在android中进行分析。
.. | 21-Nov-2012 | 4 KiB | |
arm-vfp/ | 21-Nov-2012 | 4 KiB | |
armv5te/ | 21-Nov-2012 | 12 KiB | |
armv6/ | 21-Nov-2012 | 4 KiB | |
armv6t2/ | 21-Nov-2012 | 4 KiB | |
armv7-a/ | 21-Nov-2012 | 4 KiB | |
c/ | 21-Nov-2012 | 12 KiB | |
common/ | 21-Nov-2012 | 4 KiB | |
config-allstubs | 21-Nov-2012 | 1.4 KiB | |
config-armv5te | 21-Nov-2012 | 1.6 KiB | |
config-armv5te-vfp | 21-Nov-2012 | 3.2 KiB | |
config-armv7-a | 21-Nov-2012 | 5.2 KiB | |
config-armv7-a-neon | 21-Nov-2012 | 5.1 KiB | |
config-mips | 21-Nov-2012 | 1.7 KiB | |
config-portable | 21-Nov-2012 | 1.1 KiB | |
config-x86 | 21-Nov-2012 | 1.7 KiB | |
cstubs/ | 21-Nov-2012 | 4 KiB | |
gen-mterp.py | 21-Nov-2012 | 19.9 KiB | |
Makefile-mterp | 21-Nov-2012 | 1.8 KiB | |
mips/ | 21-Nov-2012 | 20 KiB | |
Mterp.cpp | 21-Nov-2012 | 3 KiB | |
Mterp.h | 21-Nov-2012 | 1.3 KiB | |
NOTES.txt | 21-Nov-2012 | 3.2 KiB | |
out/ | 21-Nov-2012 | 4 KiB | |
portable/ | 21-Nov-2012 | 4 KiB | |
README.txt | 21-Nov-2012 | 13 KiB | |
rebuild.sh | 21-Nov-2012 | 1.1 KiB | |
x86/ |
上面是mtero目录下代码分布,主要分析out/、gen-mterp.py、configxxx。先来看下gen-mterp.py是干什么吃的,我们知道要根据硬件平台来写解释器,当然解释器最好是能根据配置自动生成啰。
所谓模块化代码生成方法,就是说将解释器的实现划分成若干个模块,每一个模块都对应有一系列的输入文件(本身也是源代码文件),最后通过工具(一个Python脚本)将这些输入文件组装起来形成一个C语言文件或者汇编语言文件。这个最终得到的C语言文件或者汇编语言文件就是Dalvik虚拟机的解释器的实现文件。有了这种模块化代码生成方法之后,为某一个特定的平台生成优化过的解释器就是相当容易的:我们只需要为该平台的Dalvik虚拟机解释器的相关模块提供一个特殊版本的输入文件即可。也就是说,我们需要为每一个支持的平台提供一个配置文件,该配置文件描述了该平台的Dalvik虚拟机解释器的各个模块所要使用的输入文件。这种模块化代码生成方法不仅能避免手动编写解释器容易出错的问题,还能方便快速地将Dalvik虚拟机从一个平台移植到另外一个平台。 --by 老罗
ok,既然这个gen-mterp.py能帮我们自动生成解释器。那我们就去一探究竟吧~( ̄▽ ̄~)(~ ̄▽ ̄)~
<span style="font-size:18px;"># =========================================================================== # "main" code # Check args. if len(sys.argv) != 3: print "Usage: %s target-arch output-dir" % sys.argv[0] sys.exit(2) # target-arch要生成的模式:portable fast target_arch = sys.argv[1] # output-dir输出目录 output_dir = sys.argv[2] # Extract opcode list. # 提取dexopcode列表 opcodes = getOpcodeList() # Open config file. # 打开配置文件sys.ary[1] ...... # Open and prepare output files. # 打开输出文件sys.ary[2]/InterpC-sys.ary[1].cpp sys.ary[2]/InterpC-sys.ary[1].s 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) ...... 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)</span>
上面这么一大段代码究竟是干什么的呢,其实就做了2件事:
1 在out文件夹下创建arch对应的cpp和asm解释器代码(以InterpC-armv7-a为例(以下涉及到config不再重复),会在out下创建InterpC-armv7-a.cpp和InterpAsm-armv7-a.S;看到最后的一大串字符了,你打开out下每个文件都包含);
2 打开对应的arch配置(就是上面的一大段config-xxx文本),后面的代码就是解析这个配置文件;
3 打开/dalvik/libdex/DexOpcodes.h 头文件,将 DEFINE_GOTO_TABLE(指令字符串)提取组成键值对(根据指令字符可到序列号)中,这个在后面构成汇编解释器时会用到
再来看gen-mterp.py下相关的代码
# Process the config file. # 处理配置文件 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": # Set handler_size_bytes,二进制位数 setHandlerSize(tokens) elif tokens[0] == "import": # 将import文件copy到输出文本:c_fp或asm_fp importFile(tokens) elif tokens[0] == "asm-stub": # copy asm-stub文本到asm-stub-txt setAsmStub(tokens) elif tokens[0] == "asm-alt-stub": # Record location of default alt stub setAsmAltStub(tokens) elif tokens[0] == "op-start": # in_op_start = 1 且设置default_op_dir = op-start opStart(tokens) ...... elif tokens[0] == "op": # 设置opcode指令的文件地址 组成键值对 opEntry(tokens) elif tokens[0] == "handler-style": # Set interpreter style setHandlerStyle(tokens) ......
上面的for ...in...语句就是在逐行解析config文本,我们只挑选重要解释:
handler-style:设置解释器的类型
handler-size:提到这个是因为在它需要跟 handler-style相关联,必须先设置style的值,这就关系到config文本的编写:
handler-style computed-goto
handler-size 64 先handler-style后handler-size
import:直接将import的cpp和s文本写入到InterpC-armv7-a.cpp(对于cpp来说这就是全部的工作)和InterpAsm-armv7-a.S
op-start:开始填写指令解释符,主要是设置in_op_start = 1,供下面OP 判断状态使用
op:根据文本设置对应的opcode_locations值:op OP_ADD_DOUBLE_2ADDR armv6t2-->opcode_locations[OP_ADD_DOUBLE_2ADDR] = armv6t2 ,存储的是opcode的解释代码的文件目录,不理解看下面
op-end:首先opcodes[index]去除opcode,然后opcode_locations[opcode]得到opcode的解释代码的文件目录Dstr,最后利用Dstr/opcode.s得到具体opcode解释代码的asm文本并将其写入InterpAsm-armv7-a.S。
对于op-start,op,op-end举例说明:op OP_MOVE armv6t2——>op指令解析“op_mov”指令文件夹为"armv6t2",构成对应的指令解释文本为armv6t2/op_mov.s
最后提几点:
本篇讲解的是dalvik解释器部分,而解释器的工作是把java代码翻译机器码。在dalvik虚拟机中执行函数由CallStaticVoidMethod来执行,但最终是通过dvmCallMethodV(请重点关注它,很重要哎;xposed框架就是基于此来实现的):
void dvmCallMethodV(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, va_list args) { ...... if (dvmIsNativeMethod(method)) { TRACE_METHOD_ENTER(self, method); /* * Because we leave no space for local variables, "curFrame" points * directly at the method arguments. */ (*method->nativeFunc)(self->curFrame, pResult, method, self); TRACE_METHOD_EXIT(self, method); } else { dvmInterpret(self, method, pResult); } ...... }
看上面红色代码,先判断method是否为native,是则执行其curFrame(其实是native code);否则去执行dvmInterpret。dvmInterpret是解释器入口,在interp.cpp文件中,会根据指令来调用上面编译生成的解释代码。
参考资料:
1 生成dalvik解释器原文件的脚本:gen-mterp.py
版权声明:本文为博主原创文章,未经博主允许不得转载。