Android 的recovery模式分析

Recovery Binary:

  Recovery Binary 是 Android 進入 Recovery 模式所運行的程序,實現了 Recovery 模式下的功能。它由目錄 bootable/recovery 下的源代碼編譯生成。頭文件 bootable/recovery/recovery_ui.h 定義了 Recovery UI 的接口,bootable/recovery/default_recovery_ui.c 是其默認實現,每個設備可以有自己不同的實現,然後通過變量 TARGET_RECOVERY_UI_LIB 來指定,否則使用默認實現。

# bootable/recovery/Android.mk

ifeq ($(TARGET_RECOVERY_UI_LIB),)
  LOCAL_SRC_FILES += default_recovery_ui.c
else
  LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
endif
  • Recovery Image:

  Recovery Image 的生成規則在文件 build/core/Makefile 中定義,具體分析如下:

# build/core/Makefile

# -----------------------------------------------------------------
# Recovery image

# If neither TARGET_NO_KERNEL nor TARGET_NO_RECOVERY are true
ifeq (,$(filter true, $(TARGET_NO_KERNEL) $(TARGET_NO_RECOVERY) $(BUILD_TINY_ANDROID)))

INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

recovery_initrc := $(call include-path-for, recovery)/etc/init.rc
recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system
recovery_ramdisk := $(PRODUCT_OUT)/ramdisk-recovery.img
recovery_build_prop := $(INSTALLED_BUILD_PROP_TARGET)
recovery_binary := $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery
recovery_resources_common := $(call include-path-for, recovery)/res
recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res))
recovery_resource_deps := $(shell find $(recovery_resources_common) \
  $(recovery_resources_private) -type f)
recovery_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery.fstab))
recovery_mmc_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery_mmc.fstab))

ifeq ($(recovery_resources_private),)
  $(info No private recovery resources for TARGET_DEVICE $(TARGET_DEVICE))
endif

ifeq ($(recovery_fstab),)
  $(info No recovery.fstab for TARGET_DEVICE $(TARGET_DEVICE))
endif

INTERNAL_RECOVERYIMAGE_ARGS := \
    $(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \
    --kernel $(recovery_kernel) \
    --ramdisk $(recovery_ramdisk)

# Assumes this has already been stripped
ifdef BOARD_KERNEL_CMDLINE
  INTERNAL_RECOVERYIMAGE_ARGS += --cmdline "$(BOARD_KERNEL_CMDLINE)"
endif
ifdef BOARD_KERNEL_BASE
  INTERNAL_RECOVERYIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
endif
BOARD_KERNEL_PAGESIZE := $(strip $(BOARD_KERNEL_PAGESIZE))
ifdef BOARD_KERNEL_PAGESIZE
  INTERNAL_RECOVERYIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
endif

INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
kernel: $(INSTALLED_BOOTIMAGE_TARGET)
.PHONY: kernel

# Keys authorized to sign OTA packages this build will accept.  The
# build always uses test-keys for this; release packaging tools will
# substitute other keys for this one.
OTA_PUBLIC_KEYS := $(SRC_TARGET_DIR)/product/security/testkey.x509.pem

# Generate a file containing the keys that will be read by the
# recovery binary.
RECOVERY_INSTALL_OTA_KEYS := \
    $(call intermediates-dir-for,PACKAGING,ota_keys)/keys
DUMPKEY_JAR := $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar
$(RECOVERY_INSTALL_OTA_KEYS): PRIVATE_OTA_PUBLIC_KEYS := $(OTA_PUBLIC_KEYS)
$(RECOVERY_INSTALL_OTA_KEYS): $(OTA_PUBLIC_KEYS) $(DUMPKEY_JAR)
    @echo "DumpPublicKey: $@ <= $(PRIVATE_OTA_PUBLIC_KEYS)"
    @rm -rf $@
    @mkdir -p $(dir $@)
    java -jar $(DUMPKEY_JAR) $(PRIVATE_OTA_PUBLIC_KEYS) > $@

$(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
        $(INSTALLED_RAMDISK_TARGET) \
        $(INSTALLED_BOOTIMAGE_TARGET) \
        $(recovery_binary) \
        $(recovery_initrc) $(recovery_kernel) \
        $(INSTALLED_2NDBOOTLOADER_TARGET) \
        $(recovery_build_prop) $(recovery_resource_deps) \
        $(recovery_fstab) \
        $(RECOVERY_INSTALL_OTA_KEYS)

    /* 以正常系統的根文件系統爲基礎構建 Recovery 的根文件系統 */
    @echo ----- Making recovery image ------
    rm -rf $(TARGET_RECOVERY_OUT)
    mkdir -p $(TARGET_RECOVERY_OUT)
    mkdir -p $(TARGET_RECOVERY_ROOT_OUT)
    mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc
    mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp
    echo Copying baseline ramdisk...
    cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT)

    /* 刪除所有的 Init 腳本,使用 Recovery 特定的 Init 腳本 */
    rm $(TARGET_RECOVERY_ROOT_OUT)/init*.rc
    echo Modifying ramdisk contents...
    cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/

    /* 添加 Recovery Binary */
    cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/

    /* 添加通用的和設備特定的 Recovery 資源 */
    cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/
    $(foreach item,$(recovery_resources_private), \
      cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)

    /* 添加設備特定的文件系統表 */
    $(foreach item,$(recovery_fstab), \
      cp -f $(item) $(TARGET_RECOVERY_ROOT_OUT)/etc/recovery.fstab)
    $(foreach item,$(recovery_mmc_fstab), \
      cp -f $(item) $(TARGET_RECOVERY_ROOT_OUT)/etc/recovery_mmc.fstab)

    /* 內嵌驗證簽名的公鑰 */
    cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys

    /* 生成 Recovery 模式的默認屬性文件 */
    cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) \
            > $(TARGET_RECOVERY_ROOT_OUT)/default.prop

    /* 生成 Recovery 的根文件系統 ramdisk-recovery.img */
    $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk)

    /* 把正常系統的內核跟 ramdisk-recovery.img 打包生成 Recovery Image */
    $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $@
    @echo ----- Made recovery image -------- $@

    /* 驗證生成的 Recovery Image 有沒有超出 Recovery 分區的大小 */
    $(hide) $(call assert-max-image-size,$@,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw)

else
INSTALLED_RECOVERYIMAGE_TARGET :=
endif

.PHONY: recoveryimage
recoveryimage: $(INSTALLED_RECOVERYIMAGE_TARGET)

  • Recovery Init Script:

  從上面的分析可以看出 recovery.img 和 boot.img 的區別不大,主要是 init 腳本不一樣,recovery 的 init 腳本相對簡單,系統起來後只運行 ueventd、recovery、adbd 三個服務。

# bootable/recovery/etc/init.rc

on early-init
    start ueventd

on init
    export PATH /sbin
    export ANDROID_ROOT /system
    export ANDROID_DATA /data
    export EXTERNAL_STORAGE /sdcard

    symlink /system/etc /etc

    mkdir /sdcard
    mkdir /system
    mkdir /data
    mkdir /cache
  
    mount /tmp /tmp tmpfs

on boot
    ifup lo
    hostname localhost
    domainname localdomain

    class_start default

service ueventd /sbin/ueventd
    critical

service recovery /sbin/recovery

service adbd /sbin/adbd recovery
    disabled

on property:persist.service.adb.enable=1
    start adbd

on property:persist.service.adb.enable=0
    stop adbd

  • Android <----> Recovery Binary <----> Bootloader:

  有時候 Android 需要不同的模式互相協助來完成一項任務,這樣不同模式之間就要有一種機制來交換信息。Recovery Binary 和 Bootloader 之間是通過 misc 分區來傳遞信息的,如果是 MTD 設備,則使用 misc 分區的第二個頁面,如果是塊設備,則使用 misc 分區的第一塊,交換的信息通過如下結構體封裝。Recovery Binary 和 Android 之間是通過 cache 分區下的如下幾個固定文件來傳遞信息的。

/* Recovery Binary <----> Bootloader */

struct bootloader_message {
    char command[32];
    char status[32];
    char recovery[1024];
};

/* Recovery Binary <----> Android */

/cache/recovery/command
/cache/recovery/intent
/cache/recovery/log
/cache/recovery/last_log

  • Updater Binary:

  Updater Binary 是 OTA package 的安裝程序,被打包到 OTA package 中一起發佈。Updater Binary 的源代碼位於目錄 bootable/recovery/updater 中。每個設備都可以爲 Updater Binary 添加自己特定的擴展,然後通過變量TARGET_RECOVERY_UPDATER_LIBS 和 TARGET_RECOVERY_UPDATER_EXTRA_LIBS 來指定。

# bootable/recovery/updater/Android.mk

LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) \
                          $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz
LOCAL_STATIC_LIBRARIES += libmincrypt libbz
LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..

# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
# named "Register_<libname>()".  Here we emit a little C function that
# gets #included by updater.c.  It calls all those registration
# functions.

# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
# These libs are also linked in with updater, but we don't try to call
# any sort of registration function for these.  Use this variable for
# any subsidiary static libraries required for your registered
# extension libs.

inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc

# During the first pass of reading the makefiles, we dump the list of
# extension libs to a temp file, then copy that to the ".list" file if
# it is different than the existing .list (if any).  The register.inc
# file then uses the .list as a prerequisite, so it is only rebuilt
# (and updater.o recompiled) when the list of extension libs changes.

junk := $(shell mkdir -p $(dir $(inc));\
            echo $(TARGET_RECOVERY_UPDATER_LIBS) > $(inc).temp;\
            diff -q $(inc).temp $(inc).list || cp -f $(inc).temp $(inc).list)

$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
$(inc) : $(inc).list
    $(hide) mkdir -p $(dir $@)
    $(hide) echo "" > $@
    $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@)
    $(hide) echo "void RegisterDeviceExtensions() {" >> $@
    $(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@)
    $(hide) echo "}" >> $@

$(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc)
LOCAL_C_INCLUDES += $(dir $(inc))

發佈了9 篇原創文章 · 獲贊 2 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章