前言
2014年6月25日,Android L-5.0 正式亮相于召开的谷歌I/O大会,Android L 改动幅度较大,谷歌将直接删除Dalvik,代替它的是传闻已久的ART。
安装后:data/data放应用私有数据,data/app放apk、lib、oat(vdex\odex\art 文件魔数为vdex、elf、art)
date/dalvik-cache放虚拟机执行码的(vdex\oat\art)
旧的优化叫dexopt,现安装时调用/system/bin/dex2oat(8.1.0下存在)编译
无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的,但是前者对应的是一个dey文件(表示这是一个优化过的dex),后者对应的是一个oat文件(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)。
应用程序的安装发生在两个时机,第一个时机是系统启动的时候,第二个时机系统启动完成后用户自行安装的时候。在第一个时机中,系统除了会对/system/app和/data/app目录下的所有APK进行dex字节码到本地机器码的翻译之外,还会对/system/framework目录下的APK或者JAR文件,以及这些APK所引用的外部JAR,进行dex字节码到本地机器码的翻译。这些都挂载在存储中了。
在ART中,打包在APK里面的Dex字节码是通过LLVM翻译成本地机器指令的–现在的加固也需要研究这个。
oat结构:
加了俩个段oatdata和oatexec,分别用来储存原来打包在APK里面的dex文件和翻译这个dex文件里面的类方法得到本地机器指令。
动态段(dymanic section,用于以数组指向动态连接器所需信息,该段在程序头表与节头表中都有指向)中添加了三个符号oatdata、oatexec和oatlastword分别用来描述oatdata和oatexec段加段到内存后的起止地址。
在oatdata段中,包含了两个重要的信息,一个信息是原来的classes.dex文件的完整内容,另一个信息引导ART找到classes.dex文件里面的类方法所对应的本地机器指令,这些本地机器指令就保存在oatexec段中。
可见oat中含有完整的dex文件。
我们在classes.dex文件中有一个类A,那么当我们知道类A的名字后,就可以通过保存在oatdata段的dex文件得到类A的所有信息,比如它的父类、成员变量和成员函数等。另一方面,类A在oatdata段中有一个对应的OatClass结构体。这个OatClass结构体描述了类A的每一个方法所对应的本地机器指令在oatexec段的位置。也就是说,当我们知道一个类及其某一个方法的名字(签名)之后,就可以通过oatdata段的dex文件内容和OatClass结构体找到其在oatexec段的本地机器指令,这样就可以执行这个类方法了。
art加载运行壳时是加载优化后的oat代码,但壳用classloader加载dex时会为其生成oat文件,可能会破坏处理的dex,hook掉可以运行dex但会慢,所以为了提升速度不会阻止dex优化为oat,也就是说原dex尽力保留了,因此采用大块的native化进行加固!
art虚拟机的启动
先记住一张图…要不然看代码经常会忘了在哪…..
接下来按着这个图上的标号分析
1.main
这个就是之前分析过的http://xn–74q78i15hxv3arigm4e.cn/2018/09/21/android8-0-0%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-zygote%E7%9A%84%E5%90%AF%E5%8A%A8/
中init进程所调用的android启动函数
2.start
之前的runtime.start
3.jni_invocation.Init
加载libart.so导出其接口
4.AppRuntime.startVm
startVm(&mJavaVM, &env, zygote)
经过参数设置调用JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs)
JNI_CreateJavaVM
就是之前虚拟机导出的三个接口之一
5/6.JNI_CreateJavaVM
经过一层JniInvocation类内调用后,8.0的实现位于art\runtime\java_vm_ext.cc
|
|
作用:
- 参数以key-value形式保存于options向量
- 用Runtime类的静态成员函数create创建art虚拟机,属于该进程
- 获取当前Runtime对象实例,并调用start启动
- 获取JNIEnv与JavaVm返回。
7.Runtime.Create
Runtime::Create(options, ignore_unrecognized)
Runtime类的静态成员函数Create
在art\runtime\runtime.cc
|
|
创建唯一的Runtime对象:Runtime::instance_实例,进行初始化。
注意该对象属于进程,线程私有空间有限:栈以及系统提供的线程信息区域TLS,代码以及堆是共享的,因此类中指向的全局变量进程唯一。
Runtime构造函数:在art\runtime\runtime.cc
|
|
为基本属性的初始化
8.Runtime.init
instance_->Init(std::move(runtime_options))
在art\runtime\runtime.cc
|
|
classlinker = new ClassLinker(interntable);
class_linker主要是解析一些系统java类,比如classloader等系统资源load到内存,作为之后加载其他java类的基础,这里只是简单说明下,涉及到art的几个很重要的结构,Class和ArtMethod,DexCache,在boot.oat可执行文件的结构中,每一个Java Class对应一个OatClass,OatClass中保存分别有dex class和本地机器码的地址,在解释执行模式下,每一个方法的调用是通过DexCache来中转的,DexCache,根据method找到指定的ArtMethod,这里class_link主要负责协调管理。
此处实现在分析art运行时再开
9.Heap.Heap
看一下堆的构造Heap类——art虚拟机的堆
位于:art\runtime\gc\heap.cc
|
|
image_file_name描述image文件路径 默认为/system/framework/arm64/boot.art
用space::ImageSpace::LoadBootImage加载到boot_imagespaces中,之后用Heap类的成员函数AddSpace加入堆空间
注意android7之后被分为多个文件:boot-
LoadBootImage在\art\runtime\gc\space\image_space.cc
|
|
检查:boot.art、dalvik-cache是否存在该镜像文件,没有就创建。最后加载进boot_image_spaces中
Heap::AddSpace
位于art\runtime\gc\heap.cc
|
|
Heap类中分别用成员变量continuousspaces和discontinuousspaces分别保存整个堆中的连续空间和非连续空间,这里讲space加载进heap
10.JavaVMExt.Create
Runtime::Init的JavaVMExt::Create
实现在art\runtime\java_vm_ext.cc
|
|
初始化JavaVMExt继承自JavaVM,functions为一函数指针结构体,存有JavaVM的进程相关方法。
该对象存有Runtime并唯一。
11.Thread.Startup
Thread::Startup()
在/art/runtime/thread.cc
|
|
Thread类是art虚拟机管理线程的类,里面jvm创建与native创建的线程都有表示,是一一对应的。因为jvm的线程调度是由操作系统实现的
12.Thread.Attach
Thread* self = Thread::Attach(“main”, false, nullptr, false);
位于art/runtime/thread.cc
|
|
创建Thread实例,调用init函数,传入该进程的ThreadList和JavaVM。
同时在init中创建该线程的JNIEnvExt对象。
JNIEnvExt类的构造函数将父类JNIEnv的成员变量functions初始化为全局变量gJniNativeInterface。也就是说,JNI函数表实际是由全局变量gJniNativeInterface来描述的。其中均是jni的静态函数。
16.Runtime.Start
bool started = runtime->Start();//启动虚拟机
art\runtime\runtime.cc
|
|
- 初始化基本类class_class ,即java.lang.Class类,这是最基础的类;
- InitNativeMethods(): 将java.lang下面最基础的类的JNI接口进行初始化;
- InitZygote:这是zygote程序在调用时,需要做的事情。app_main.cpp文件不仅是zygote的主程序,也是安卓的am命令行的主程序,故此这里做了区分;
- StartDemonThreads,调用java.lang.Daemons.start,启动一组线程,它们的作用是,执行GC、调用finalize方法、 处理ReferenceQueue等一系列工作。
17.AndroidRuntime.startReg
|
|
注册android系统依赖的本地jni方法
18.jni方法
调用之前注册好的本地方法
后记
基本启动过程如上….由于老罗的版本有些久远…art的c++源码庞大复杂…..经过一段时间的摸索仍在雾中。art加载执行部分无法自行分析懂,因此接下来从apk安装、运行角度分析。让逆向相关流程更为明确。
老罗的分析:https://blog.csdn.net/luoshengyang/article/details/39256813
虚拟机本质还是一个c++程序。它解释自定义的执行文件格式。