本文简单的分析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源码后,怎么从头编译一个干净的实现出来。后面我们会动手实践。
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)
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
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 $@
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.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)\"
# # 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
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
下面是dvm源码目录树。后面章节会分别介绍其中最重要的部分:
https://github.com/android/platform_dalvik/tree/master/vm