Dalvik 虚拟机编译脚本 和 源代码树
本文简单的分析Dalvik虚拟机源码(dalvik/vm)的结构以及编译脚本(*.mk)
编译脚本
Dalvik源码目录结构并不复杂。其编译脚本也很简单。主要有以下几个文件组成:
dalvik/vm/Android.mk
dalvik/vm/ReconfigureDvm.mk
dalvik/vm/Dvm.mk
和android系统里其他的模块类似,dalvik也是以Android.mk作为顶层编译配置文件或者入口。它的内容如下所示:
https://github.com/android/platform_dalvik/blob/master/vm/Android.mk
我们从头分析一下。首次按是注释说明:
#
# Android.mk for Dalvik VM.
#
# This makefile builds both for host and target, and so the very large
# swath of common definitions are factored out into a separate file to
# minimize duplication.
#
# If you enable or disable optional features here (or in Dvm.mk),
# rebuild the VM with:
#
# make clean-libdvm clean-libdvm_assert clean-libdvm_sv clean-libdvm_interp
# make -j4 libdvm
#
他告诉我们,该编译文件把dalvik编程成两部分,宿主机和目标机。对多数典型的配置下,宿主机就是我们的Linux编译服务器。而目标机就是我们的移动设备。 为了减少重复,宿主机和目标机都需要的编译配置被放到单独的文件里。单独的文件就是后面要讲道德(ReconfigureDvm.mk和Dvm.mk)。
同时,它也该出了在我们修改了dalvik源码后,怎么从头编译一个干净的实现出来。后面我们会动手实践。
LOCAL_PATH:= $(call my-dir)
和多数模块一样,接下来把dalvik源码路径赋值给LOCAL_PATH变量。以方便后面使用。这里LOCAL_PATH应该就是 dalvik/vm.
#
# Build for the target (device).
#
ifeq ($(TARGET_CPU_SMP),true)
target_smp_flag := -DANDROID_SMP=1
else
target_smp_flag := -DANDROID_SMP=0
endif
host_smp_flag := -DANDROID_SMP=1
# Build the installed version (libdvm.so) first
include $(LOCAL_PATH)/ReconfigureDvm.mk
# Overwrite default settings
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := libdvm
LOCAL_CFLAGS += $(target_smp_flag)
include $(BUILD_SHARED_LIBRARY)
首先为目标机编译。首先是根据目标机是否支持SMP,设置变量target_smp_flag。后面我们会看到该变量被用来传给编译器选项。而宿主机上现在绝大多数都是支持SMP的,所以就直接复制是指为支持。
紧接着就调用脚本ReconfigureDvm.mk,从名字我们不难猜出改脚本是用来为dvm编译初始化编译环境的。后面我们会看到它的主要内容。
下面接着就是现实dvm在宿主机上最终会被编译的目标了。这是一个共享库。名字叫libdvm。我们可以在编译好的机器上找到他
out/target/xxx/libs/libdvm.so
# If WITH_JIT is configured, build multiple versions of libdvm.so to facilitate
# correctness/performance bugs triage
ifeq ($(WITH_JIT),true)
# Derivation #1
# Enable assert and JIT tuning
include $(LOCAL_PATH)/ReconfigureDvm.mk
# Enable assertions and JIT-tuning
LOCAL_CFLAGS += -UNDEBUG -DDEBUG=1 -DLOG_NDEBUG=1 -DWITH_DALVIK_ASSERT \
-DWITH_JIT_TUNING $(target_smp_flag)
LOCAL_MODULE := libdvm_assert
include $(BUILD_SHARED_LIBRARY)
# Derivation #2
# Enable assert and self-verification
include $(LOCAL_PATH)/ReconfigureDvm.mk
# Enable assertions and JIT self-verification
LOCAL_CFLAGS += -UNDEBUG -DDEBUG=1 -DLOG_NDEBUG=1 -DWITH_DALVIK_ASSERT \
-DWITH_SELF_VERIFICATION $(target_smp_flag)
LOCAL_MODULE := libdvm_sv
include $(BUILD_SHARED_LIBRARY)
# Derivation #3
# Compile out the JIT
WITH_JIT := false
include $(LOCAL_PATH)/ReconfigureDvm.mk
LOCAL_CFLAGS += $(target_smp_flag)
LOCAL_MODULE := libdvm_interp
include $(BUILD_SHARED_LIBRARY)
endif
这一部分是为JIT特有 的。当系统支持JIT编译器是,dvm会编译三个额外的目标共享库,用于支持dvm的开发特性。他们分别是
libdvm_assert 用来打开dvm源码中的断言(assert)。我们在后面的分析文章中会看到,dvm实现中大量使用断言来增加运行时的检查。当打开这些断言是,任何断言检查失败都将直接导致dvm异常退出。我们从trace file就能够找到那个断言失败,从而检查出可能存在的bug。在最终发布版本中,断言会被关闭,哪些断言相当于空语句。因而不会再最终发布版本中是dvm异常退出。
libdvm_sv 除了代开断言外,还带开了自我检查能力,用来在某些重要时刻检查当前dvm状态没有异常。我们在后面的文章里会详细分析dvm到底做了哪些自我检查。
最后一个版本 libdvm_interp 用来关掉JIT,而编译出一个纯解释器实现的dvm实例。这样子我们就可以检查打开JIT后行为有没有出现和解释器实现的dvm有行为差异。
我们可以在如下目录里找到这三个为调试生成的dvm共享库:
out/target/xxx/symbol/libs/
最后的内容是用来为宿主机编译的。所有的内容包裹在如下代码里:
#
# Build for the host.
#
ifeq ($(WITH_HOST_DALVIK),true)
...
endif
首先是清空本地变量内容:
include $(CLEAR_VARS)
然后是根据编译环境设置三个编译变量:
# Variables used in the included Dvm.mk.
dvm_os := $(HOST_OS)
dvm_arch := $(HOST_ARCH)
# Note: HOST_ARCH_VARIANT isn't defined.
dvm_arch_variant := $(HOST_ARCH)
这三个变量最终会传给编译器。它们会引入平台特有的行为。 由于宿主机不必支持JIT,所以将它设置为false。
WITH_JIT := false
include $(LOCAL_PATH)/Dvm.mk
Dvm.mk我们后面会接着分析。
接下来根据目标平台,有选择性的引入编译需要的库:
LOCAL_SHARED_LIBRARIES += libcrypto libssl libicuuc libicui18n
LOCAL_LDLIBS := -lpthread -ldl
ifeq ($(HOST_OS),linux)
# need this for clock_gettime() in profiling
LOCAL_LDLIBS += -lrt
endif
# Build as a WHOLE static library so dependencies are available at link
# time. When building this target as a regular static library, certain
# dependencies like expat are not found by the linker.
LOCAL_WHOLE_STATIC_LIBRARIES += libexpat libcutils libdex liblog libnativehelper libz
# The libffi from the source tree should never be used by host builds.
# The recommendation is that host builds should always either
# have sufficient custom code so that libffi isn't needed at all,
# or they should use the platform's provided libffi. So, if the common
# build rules decided to include it, axe it back out here.
ifneq (,$(findstring libffi,$(LOCAL_SHARED_LIBRARIES)))
LOCAL_SHARED_LIBRARIES := \
$(patsubst libffi, ,$(LOCAL_SHARED_LIBRARIES))
endif
最后告诉编译系统,dvm在宿主机上的编译结果有两个:
LOCAL_CFLAGS += $(host_smp_flag)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := libdvm
include $(BUILD_HOST_SHARED_LIBRARY)
# Copy the dalvik shell script to the host's bin directory
include $(CLEAR_VARS)
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE := dalvik
include $(BUILD_SYSTEM)/base_rules.mk
$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/dalvik | $(ACP)
@echo "Copy: $(PRIVATE_MODULE) ($@)"
$(copy-file-to-new-target)
$(hide) chmod 755 $@
它们分别是 dvm共享库libdvm.so 和可执行文件dalvikvm。我们编译后的目录里能找到它们:
out/host/xxx/libs/libdvm.so
out/host/xxx/bin/dalvikvm
前面我们看到,dvm在目标机上编译的结果可能有好几个,最终的共享库libdvm.so,以及几个开发使用的共享库libsdvm_xx.so。编译这些目标是我们需要首先清理当前的编译环境,以排除编译前一个目标多带来的副作用。这个工作单独放到编译脚本ReconfigureDvm.mk,以减少重复。ReconfigureDvm.mk内容如下:
include $(CLEAR_VARS)
# Variables used in the included Dvm.mk.
dvm_os := $(TARGET_OS)
dvm_arch := $(TARGET_ARCH)
dvm_arch_variant := $(TARGET_ARCH_VARIANT)
# for now, disable x86-atom variant
ifeq ($(dvm_arch_variant),x86-atom)
dvm_arch_variant := x86
endif
include $(LOCAL_PATH)/Dvm.mk
LOCAL_SHARED_LIBRARIES += liblog libcutils libnativehelper libz libdl
LOCAL_STATIC_LIBRARIES += libdex
LOCAL_C_INCLUDES += external/stlport/stlport bionic/ bionic/libstdc++/include
LOCAL_SHARED_LIBRARIES += libstlport
# Don't install on any build by default
LOCAL_MODULE_TAGS := optional
它的内容也不出意外。清除本地变量,设置dvm变量,引入dvm需要的库。最后设置LOCAL_MODULE_TAGS为optional来告诉编译系统除非显式指明(include dalvik/vm/android.mk),否则不会编译dvm。
最后一个文件dvm.mk设置了宿主机和目标机都需要的定义。其内容也很直观。完整的内容如下:
https://github.com/android/platform_dalvik/blob/master/vm/Dvm.mk
首先是设置编译器选项:
#
# Compiler defines.
#
LOCAL_CFLAGS += -fstrict-aliasing -Wstrict-aliasing=2 -fno-align-jumps
LOCAL_CFLAGS += -Wall -Wextra -Wno-unused-parameter
LOCAL_CFLAGS += -DARCH_VARIANT=\"$(dvm_arch_variant)\"
接着判断编译时有没有指定DEBUG_DALVIK_VM:
#
# Optional features. These may impact the size or performance of the VM.
#
# Make a debugging version when building the simulator (if not told
# otherwise) and when explicitly asked.
dvm_make_debug_vm := false
ifneq ($(strip $(DEBUG_DALVIK_VM)),)
dvm_make_debug_vm := $(DEBUG_DALVIK_VM)
endif
如果指定了该选项,则打开额外的编译选项,否则什么都不多做:
ifeq ($(dvm_make_debug_vm),true)
#
# "Debug" profile:
# - debugger enabled
# - profiling enabled
# - tracked-reference verification enabled
# - allocation limits enabled
# - GDB helpers enabled
# - LOGV
# - assert()
#
LOCAL_CFLAGS += -DWITH_INSTR_CHECKS
LOCAL_CFLAGS += -DWITH_EXTRA_OBJECT_VALIDATION
LOCAL_CFLAGS += -DWITH_TRACKREF_CHECKS
LOCAL_CFLAGS += -DWITH_EXTRA_GC_CHECKS=1
#LOCAL_CFLAGS += -DCHECK_MUTEX
LOCAL_CFLAGS += -DDVM_SHOW_EXCEPTION=3
# add some extra stuff to make it easier to examine with GDB
LOCAL_CFLAGS += -DEASY_GDB
# overall config may be for a "release" build, so reconfigure these
LOCAL_CFLAGS += -UNDEBUG -DDEBUG=1 -DLOG_NDEBUG=1 -DWITH_DALVIK_ASSERT
else # !dvm_make_debug_vm
#
# "Performance" profile:
# - all development features disabled
# - compiler optimizations enabled (redundant for "release" builds)
# - (debugging and profiling still enabled)
#
#LOCAL_CFLAGS += -DNDEBUG -DLOG_NDEBUG=1
# "-O2" is redundant for device (release) but useful for sim (debug)
#LOCAL_CFLAGS += -O2 -Winline
#LOCAL_CFLAGS += -DWITH_EXTRA_OBJECT_VALIDATION
LOCAL_CFLAGS += -DDVM_SHOW_EXCEPTION=1
# if you want to try with assertions on the device, add:
#LOCAL_CFLAGS += -UNDEBUG -DDEBUG=1 -DLOG_NDEBUG=1 -DWITH_DALVIK_ASSERT
endif # !dvm_make_debug_vm
紧接着是该文件的大头,引入编译源文件。其中包括 所有平台都会使用到的源文件,如 AllocTracker.cpp等。为支持JIT特有的文件,如compiler/Compiler.cpp等。其中以下部分特别需要我们关注:
ifeq ($(WITH_COPYING_GC),true)
LOCAL_CFLAGS += -DWITH_COPYING_GC
LOCAL_SRC_FILES += \
alloc/Copying.cpp.arm
else
LOCAL_SRC_FILES += \
alloc/HeapSource.cpp \
alloc/MarkSweep.cpp.arm
endif
这是在指定垃圾收集器的类型。当在编译选项中指定WITH_COPYING_GC后就使用拷贝垃圾收集器,否则就是用标记-清除垃圾收集器。我们后面会在单独的章节里介绍dvm垃圾收集。
至此编译脚本的解析全部完成。dvm源代码结构简单,其编译脚本也很清晰。
源码目录树
下面是dvm源码目录树。后面章节会分别介绍其中最重要的部分:
https://github.com/android/platform_dalvik/tree/master/vm