Dalvik虚拟机启动全程解析0x07: startReg

本文试图记录Dalvik虚拟机启动时全过程。

0x01:启动点

我们知道,Android基于linux,linux的第一个进程为init,它启动别的进程,在Android中,它启动了zygote:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

从此, 代码从linux的世界进入到了Android的世界。

在上面可以看到,启动的脚本是/system/bin/app_process,在后面的分析中,我们发现,这个就是Dalvik虚拟机的启动点。app_process除了在init.rc中启动,也可以通过命令执行,比如am命令就是调用了app_process:

#!/system/bin/sh
#
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"

0x02:app_process

既然确定了启动点,我们就来跟踪一下,为了简单,我们就分析init.rc的调用场景。

app_process的源码是app_main.cpp,其main函数主要做了几个事情:

  1. 参数解析,通过源码,我们可以看到,启动虚拟机可以有很多很多的参数
  2. 设置相关环境变量
  3. 调用runtime.start,这个是真正启动虚拟机的地方,其参数为:com.android.internal.os.ZygoteInit,表明在启动虚拟机后,开始执行ZygoteInit来做Zygote的初始化。对于Zygote的初始化及后续的事情,本文不关心,我们还是集中于虚拟机的启动。

0x03:runtime.start

源码见AndroidRuntime.cpp

AndroidRuntime::start中,主要做了几件事:

  1. startVm,启动虚拟机
  2. onVmCreated 执行启动后的回调
  3. startReg,注册Andorid系统相关的native函数
  4. 调用参数里面指定的类的main

重点是startvmstartReg,继续跟踪。

0x04:startVm

源码同样在AndroidRuntime.cpp

AndroidRuntime::startVm中,主要做了几件事:

  1. 大段的参数解析,解析到了JavaVMInitArgs
  2. JNI_CreateJavaVM,函数里面需要设置pJavaVM, pEnv指针,是为了后续的销毁虚拟机等操作。

0x05:JNI_CreateJavaVM

源码见Jni.cpp

JNI_CreateJavaVM中,主要做了几件事情:

  1. 根据参数,填充了gDvm中的相关参数 关于gDvm:它的定义在Globals.h,其作用是保存许许多多的全局变量,在整个虚拟机中使用。
  2. 初始化了JavaVMExt、JNIEnvExt等指针,这些分别对应的JNI中的JavaVM、JNIEnv,在JNI调用中必不可少。
  3. dvmStartup 继续启动,重点是这个

0x06:dvmStartup

源码见Init.cpp

dvmStartup中,主要做了几件事:

  1. 继续解析参数,填充gDvm中的相关参数,参数是真多啊,说明虚拟机的可配置项还是挺多的。
  2. dvmCheckAsmConstants,检查mterp interpreter用到的若干常量
  3. dvmQuasiAtomicsStartup, 初始化原子操作的mutex
  4. dvmGcStartup,初始化metex,初始化GC堆区
  5. dvmThreadStartup,初始化线程相关的环境
  6. dvmInlineNativeStartup,分配InlineNative相关的内存,将gDvm.inlinedMethods指向这块内存,所谓InlineNative就是指一些Java方法,使用了效率更高的C函数实现,执行这些方法时,直接调用到这些C函数,不用在执行字节码,和inLine有异曲同工之妙。
  7. dvmRegisterMapStartup,分配MapStats的内存,将gDvm.registerMapStats指向这块内存,MapStats应该是和RegisterMap相关。
  8. bool dvmInstanceofStartup(), 分配instanceofCache的内存,将gDvm.instanceofCache指向这块内存,目测这块内存会在instanceof操作时使用。
  9. dvmClassStartup,比较复杂,分别说: 9.1. 创建了一个Hash表,用来存放所有加载的类,将gDvm.loadedClasses指向这个Hash表 9.2. 分配InitiatingLoaderList的内存,将gDvm.initiatingLoaderList指向这块内存,InitiatingLoaderList在寻找加载的类的时候会用到 9.3. createInitialClasses,构建Ljava/lang/Class;的类对象,和所有的原类型的类对象:void,boolean,byte,short,char,int,long,float,double,至此,我们就可以使用这些类了。 9.4. processClassPath,加载bootClassPath下的所有的jar(其实是dex)到gDvm.loadedClasses中,bootClassPath为:

至此,Android framework相关的类也被加载了。

  1. dvmFindRequiredClassesAndMembers,主要是将相关常用到的类、属性、构造器、方法等,直接关联到gDvm的相关指针上,省得每次都要去Hash表里面查。
  2. dvmStringInternStartup,创建了internedStrings和literalStrings的Hash表,作用暂时不明。
  3. dvmNativeStartup,创建用于保存sharedLib(也就是so)的Hash表,将gDvm.nativeLibs指向此表。以后每个加载的so的相关信息都会被保存到这个表中,其中一个作用当然是避免重复加载。
  4. dvmInternalNativeStartup, 遍历很多类,设置这些类的classDescriptorHash,这些类如下:

这些类来自于libcore中,对应的c实现在dalvik下。

  1. dvmJniStartup,初始化了jni的全局引用表和全局弱引用表。(关于JNI,以后单独写一篇文章)
  2. dvmProfilingStartup, 貌似和跟踪功能相关的初始化
  3. dvmCreateInlineSubsTable,这里才将InlineNative类的Native方法和c实现建立了关联表,将gDvm.inlineSubs指向关联表。
  4. dvmValidateBoxClasses,校验盒类对象,简单校验对象的属性只有一个:即value的定义。
  5. dvmPrepMainForJni,创建了一个假的栈帧,将JNIEnv设置到当前的Thread中
  6. dvmInitClass(gDvm.classJavaLangClass),初始化Class类对象
  7. registerSystemNatives, 将Class中的getDex注册到JNI中,加载libjavacore.so和libnativehelper.so
  8. dvmCreateStockExceptions, 创建一些固定的Exception对象,并关联到gDvm
  9. dvmPrepMainThread, 创建相关的线程对象,将当前线程命令为main线程
  10. dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0 简单校验线程相关的local引用表当前是空的
  11. dvmDebuggerStartup, 初始化调试相关的东西,比如断点什么的
  12. dvmGcStartupClasses, 调用Ljava/lang/Daemons;的start方法,启动三个和GC相关的线程:
    public static void start() {
        ReferenceQueueDaemon.INSTANCE.start();
        FinalizerDaemon.INSTANCE.start();
        FinalizerWatchdogDaemon.INSTANCE.start();
    }
  1. initZygote,挂在相关的文件系统,设置进程属性等。
  2. 最后dvmCheckException,检查是否有错。

至此dvmStartup分析完毕。总结一下就是,分配相关内存,建立相关的联系,初始化相关环境,启动相关的线程。(相关这个词真是好用)

此时执行完0x03:runtime.start中中的第一步:startVm,第二步是一个回调,实现为空,继续分析第三步:startReg

0x07: startReg

源码见AndroidRuntime.cpp

AndroidRuntime::startReg中,主要做了几件事:

  1. register_jni_procs,将Android framework中所有的native方法注册到JNI中,这些类在framework.jar和framework2.jar中,c逻辑在libandroid_runtime.so中

至此,虚拟机的启动完成了,之后就会执行com.android.internal.os.ZygoteInitmain方法,开始在虚拟机中对Zygote进行初始化操作了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第36章 RL-TCPnet之FTP服务器

本章节为大家讲解RL-TCPnet的FTP服务器应用,学习本章节前,务必要优先学习第35章的FTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

9900
来自专栏Android先生

Android中极简的js与java的交互库-SimpleJavaJsBridge

最近接触android中js与java交互的东西很多,当然它们之间的交互方式有几种,但是我觉得这几种交互方式都存在一定的不足,这是我决定编写SimpleJava...

16430
来自专栏牛肉圆粉不加葱

Spark Task 的执行流程② - 创建、分发 Task

task 的创建本应该放在分配 tasks 给 executors一文中进行介绍,但由于创建的过程与分发及之后的反序列化执行关系紧密,我把这一部分内容挪到了本文...

10410
来自专栏大学生计算机视觉学习DeepLearning

c++ 网络编程(五)TCP/IP LINUX下 socket编程 多种I/O函数 -以及readv和writev函数用法

原文链接:https://www.cnblogs.com/DOMLX/p/9614056.html

23150
来自专栏YoungGy

R包简单教程

R包,类似C、Python中库的概念,指包含特定领域的函数、数据、文档等的集合。通过调用包,可以直接使用包中现成的数据、函数等,使开发方便快捷高效。

374100
来自专栏idba

socket 编程初探

一 简介 socket是两个应用程序进行通信的管道,这两个应用程序可以在同一台机器上,也可以位于两台不同的机器上,相同的网络或者不同网络之间的。Pyth...

13740
来自专栏Golang语言社区

《GO IN ACTION》读后记录:GO的并发与并行

一、使用goroutine来运行程序 1. Go的并发与并行 Go的并发能力,是指让某个函数独立于其他函数运行的能力。当为一个函数创建goroutine时,该函...

47770
来自专栏同步博客

Memcache存储机制与指令汇总

  memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

10920
来自专栏DOTNET

【翻译】MongoDB指南/CRUD操作(二)

【原文地址】https://docs.mongodb.com/manual/ MongoDB CRUD操作(二) 主要内容: 更新文档,删除文档,批量写操作,S...

37980
来自专栏用户2442861的专栏

java数据库操作 (附带数据库连接池的代码)

本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188

43220

扫码关注云+社区

领取腾讯云代金券