一、.ota_from_target_files.py分析
if __name__ == '__main__':
try:
# common.CloseInheritedPipes()是用於在macOS環境下關閉文件描述符,
# 通過platform.system() != "Darwin"判斷是否是MacOS
common.CloseInheritedPipes()
main(sys.argv[1:])
except common.ExternalError:
logger.exception("\n ERROR:\n")
sys.exit(1)
finally:
common.Cleanup()
common.CloseInheritedPipes()是用於在macOS環境下關閉文件描述符,之後執行ota_from_target_files的主體部分代碼,最後common.Cleanup()清理垃圾文件。
以下先說明common.CloseInheritedPipes()和common.Cleanup()的代碼,因爲它們非常簡單。
def main(argv):
# 此處代碼在common.ParseOptions傳入
def option_handler(o, a):
if o in ("-k", "--package_key"):
OPTIONS.package_key = a
# 製作差量包
elif o in ("-i", "--incremental_from"):
OPTIONS.incremental_source = a
elif o == "--full_radio":
OPTIONS.full_radio = True
elif o == "--full_bootloader":
OPTIONS.full_bootloader = True
# 清除用戶數據
elif o == "--wipe_user_data":
OPTIONS.wipe_user_data = True
elif o in ("-n", "--no_prereq"):
OPTIONS.omit_prereq = True
elif o == "--downgrade":
OPTIONS.downgrade = True
OPTIONS.wipe_user_data = True
elif o == "--override_timestamp":
OPTIONS.downgrade = True
elif o in ("-o", "--oem_settings"):
OPTIONS.oem_source = a.split(',')
elif o == "--oem_no_mount":
OPTIONS.oem_no_mount = True
elif o in ("-e", "--extra_script"):
OPTIONS.extra_script = a
elif o in ("-t", "--worker_threads"):
if a.isdigit():
OPTIONS.worker_threads = int(a)
else:
raise ValueError("Cannot parse value %r for option %r - only "
"integers are allowed." % (a, o))
elif o in ("-2", "--two_step"):
OPTIONS.two_step = True
elif o == "--include_secondary":
OPTIONS.include_secondary = True
elif o == "--no_signing":
OPTIONS.no_signing = True
elif o == "--verify":
OPTIONS.verify = True
elif o == "--block":
OPTIONS.block_based = True
elif o in ("-b", "--binary"):
OPTIONS.updater_binary = a
elif o == "--stash_threshold":
try:
OPTIONS.stash_threshold = float(a)
except ValueError:
raise ValueError("Cannot parse value %r for option %r - expecting "
"a float" % (a, o))
elif o == "--log_diff":
OPTIONS.log_diff = a
elif o == "--payload_signer":
OPTIONS.payload_signer = a
elif o == "--payload_signer_args":
OPTIONS.payload_signer_args = shlex.split(a)
elif o == "--payload_signer_key_size":
OPTIONS.payload_signer_key_size = a
elif o == "--extracted_input_target_files":
OPTIONS.extracted_input = a
elif o == "--skip_postinstall":
OPTIONS.skip_postinstall = True
elif o == "--retrofit_dynamic_partitions":
OPTIONS.retrofit_dynamic_partitions = True
elif o == "--skip_compatibility_check":
OPTIONS.skip_compatibility_check = True
elif o == "--output_metadata_path":
OPTIONS.output_metadata_path = a
else:
return False
return True
# 解析argv(傳入的)參數,並返回所有不是標記的參數。__doc__指需要調用的模塊,extra_opts和extra_long_opts都是標記,由傳入者定義,它們將會交給option_handler進行處理。
# 這一步完成之後,生成OTA腳本的所有配置應當都準備就緒了,存儲在common.OPTIONS中,當前腳本有一個指向common.OPTIONS的引用,名爲common.OPTIONS
args = common.ParseOptions(argv, __doc__,
extra_opts="b:k:i:d:ne:t:a:2o:",
extra_long_opts=[
.....
# 如果之前處理後獲得的參數不是兩個,輸出本腳本使用說明並退出程序
if len(args) != 2:
common.Usage(__doc__)
sys.exit(1)
# 初始化日誌模塊
common.InitLogging()
分析如下:
主函數main是python的入口函數,我們從main函數開始看,大概看一下main函數(腳本最後)裏的流程就能知道腳本的執行過程了。
① 在main函數的開頭,首先將用戶設定的option選項存入OPTIONS變量中,它是一個python中的類。緊接着判斷有沒有額外的腳本,如果有就讀入到OPTIONS變量中。
② 解壓縮輸入的zip包,即我們在上文生成的原始zip包。然後判斷是否用到device-specific extensions(設備擴展)如果用到,隨即讀入到OPTIONS變量中。
③ 判斷是否簽名,然後判斷是否有新內容的增量源,有的話就解壓該增量源包放入一個臨時變量中(source_zip)。自此,所有的準備工作已完畢,隨即會調用該 腳本中最主要的函數WriteFullOTAPackage(input_zip,output_zip)
④ WriteFullOTAPackage函數的處理過程是先獲得腳本的生成器。默認格式是edify。然後獲得metadata元數據,此數據來至於Android的一些環境變量。然後獲得設備配置參數比如api函數的版本。然後判斷是否忽略時間戳。
⑤ WriteFullOTAPackage函數做完準備工作後就開始生成升級用的腳本文件(updater-script)了。生成腳本文件後將上一步獲得的metadata元數據寫入到輸出包out_zip。
⑥至此一個完整的update.zip升級包就生成了。生成位置在:out/target/product/tcc8800/trinket.zip。將升級包拷貝到SD卡中就可以用來升級了。
四、 Android OTA增量包update.zip的生成
1.2代碼中的方法分析
顯示進度條 (寫入update_script) script.ShowProgress(增加百分比,持續時間)edify_generator.py
顯示文字之類的 script.Print -> ui_print
顯示進度條 script.ShowProgress -> show_progress
解壓(寫入)到指定區域 script.WriteRawImage -> package_extract_file
卸載所有分區 script.UnmountAll -> unmount
將另一個腳本的內容附加到當前腳本 script.AppendScript
二、update.zip升級包的製作
2.1 update.zip包的目錄結構
|----boot.img
|----system/
|----recovery/
`|----recovery-from-boot.p
`|----etc/
`|----install-recovery.sh
|---META-INF/
`|CERT.RSA
`|CERT.SF
`|MANIFEST.MF
`|----com/
`|----google/
`|----android/
`|----update-binary
`|----updater-script
`|----android/
`|----metadata
2.2 update.zip包目錄結構詳解
以上是我們用命令make otapackage 製作的update.zip包的標準目錄結構。
2.2.1、boot.img是更新boot分區所需要的文件。這個boot.img主要包括kernel+ramdisk。
2.2.2、system/目錄的內容在升級後會放在系統的system分區。主要用來更新系統的一些應用或則應用會用到的一些庫等等。可以將Android源碼編譯out/target/product/trinket/system/中的所有文件拷貝到這個目錄來代替。
2.2.3、recovery/目錄中的recovery-from-boot.p是boot.img和recovery.img的補丁(patch),主要用來更新recovery分區,其中etc/目錄下的install-recovery.sh是更新腳本。
2.2.4、update-binary是一個二進制文件,相當於一個腳本解釋器,能夠識別updater-script中描述的操作。該文件在Android源碼編譯後out/target/product/trinket/system bin/updater生成,可將updater重命名爲update-binary得到。
該文件在具體的更新包中的名字由源碼中bootable/recovery/install.c中的宏ASSUMED_UPDATE_BINARY_NAME的值而定。
2.2.5、updater-script:此文件是一個腳本文件,具體描述了更新過程。我們可以根據具體情況編寫該腳本來適應我們的具體需求。該文件的命名由源碼中bootable/recovery/updater/updater.c文件中的宏SCRIPT_NAME的值而定。
2.2.6、 metadata文件是描述設備信息及環境變量的元數據。主要包括一些編譯選項,簽名公鑰,時間戳以及設備型號等。
2.2.7、我們還可以在包中添加userdata目錄,來更新系統中的用戶數據部分。這部分內容在更新後會存放在系統的/data目錄下。
2.2.8、update.zip包的簽名:update.zip更新包在製作完成後需要對其簽名,否則在升級時會出現認證失敗的錯誤提示。而且簽名要使用和目標板一致的加密公鑰。加密公鑰及加密需要的三個文件在Android源碼編譯後生成的具體路徑爲:
out/host/linux-x86/framework/signapk.jar
build/target/product/security/releasekey.x509.pem
build/target/product/security/releasekey.pk8 。
我們用命令make otapackage製作生成的update.zip包是已簽過名的,如果自己做update.zip包時必須手動對其簽名。
具體的加密方法:$ java -Xmx2048m -Djava.library.path="out/host/linux-x86/lib64" -jar out/host/linux-x86/framework/signapk.jar –w build/target/product/security/releasekey.x509.pem build/target/product/security/releasekey.pk8 update.zip update_signed.zip
以上命令在update.zip包所在的路徑下執行,其中signapk.jar releasekey.x509.pem以及releasekey.pk8文件的引用使用絕對路徑。update.zip 是我們已經打好的包,update_signed.zip包是命令執行完生成的已經簽過名的包。
2.2.9、MANIFEST.MF:這個manifest文件定義了與包的組成結構相關的數據。類似Android應用的mainfest.xml文件。
2.2.10、CERT.RSA:與簽名文件相關聯的簽名程序塊文件,它存儲了用於簽名JAR文件的公共簽名。
2.2.11、CERT.SF:這是JAR文件的簽名文件,其中前綴CERT代表簽名者。
另外,在具體升級時,對update.zip包檢查時大致會分三步:①檢驗SF文件與RSA文件是否匹配。②檢驗MANIFEST.MF與簽名文件中的digest是否一致。③檢驗包中的文件與MANIFEST中所描述的是否一致
2.3 Android升級包的生成過程分析
在源碼根目錄下執行make otapackage命令生成update.zip包主要分爲兩步,第一步是根據Makefile執行編譯生成一個update原包(zip格式)。第二步是運行一個python腳本,並以上一步準備的zip包作爲輸入,最終生成我們需要的升級包。下面進一步分析這兩個過程。
第一步:編譯Makefile。對應的Makefile文件所在位置:build/core/Makefile。開始會生成一個zip包,這個包最後會用來製作OTA package 或者filesystem image。先將這部分的對應的Makefile貼出來如下:
# -----------------------------------------------------------------
# A zip of the directories that map to the target filesystem.
# This zip can be used to create an OTA package or filesystem image
# as a post-build step.
#
name := $(TARGET_PRODUCT)
ifeq ($(TARGET_BUILD_TYPE),debug)
name := $(name)_debug
endif
name := $(name)-target_files-$(FILE_NAME_TAG)
intermediates := $(call intermediates-dir-for,PACKAGING,target_files)
BUILT_TARGET_FILES_PACKAGE := $(intermediates)/$(name).zip
$(BUILT_TARGET_FILES_PACKAGE): intermediates := $(intermediates)
$(BUILT_TARGET_FILES_PACKAGE): \
zip_root := $(intermediates)/$(name)
# $(1): Directory to copy
# $(2): Location to copy it to
# The "ls -A" is to prevent "acp s/* d" from failing if s is empty.
define package_files-copy-root
if [ -d "$(strip $(1))" -a "$$(ls -A $(1))" ]; then \
mkdir -p $(2) && \
$(ACP) -rd $(strip $(1))/* $(2); \
fi
endef
built_ota_tools := \
$(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \
$(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \
$(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \
$(call intermediates-dir-for,EXECUTABLES,updater)/updater
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_OTA_TOOLS := $(built_ota_tools)
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_RECOVERY_API_VERSION := $(RECOVERY_API_VERSION)
ifeq ($(TARGET_RELEASETOOLS_EXTENSIONS),)
# default to common dir for device vendor
$(BUILT_TARGET_FILES_PACKAGE): tool_extensions := $(TARGET_DEVICE_DIR)/../common
else
$(BUILT_TARGET_FILES_PACKAGE): tool_extensions := $(TARGET_RELEASETOOLS_EXTENSIONS)
endif
# Depending on the various images guarantees that the underlying
# directories are up-to-date.
$(BUILT_TARGET_FILES_PACKAGE): \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RADIOIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_SYSTEMIMAGE) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET) \
$(built_ota_tools) \
$(APKCERTS_FILE) \
$(HOST_OUT_EXECUTABLES)/fs_config \
| $(ACP)
@echo "Package target files: $@"
$(hide) rm -rf $@ $(zip_root)
$(hide) mkdir -p $(dir $@) $(zip_root)
@# Components of the recovery image
$(hide) mkdir -p $(zip_root)/RECOVERY
$(hide) $(call package_files-copy-root, \
$(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)
ifdef INSTALLED_KERNEL_TARGET
$(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel
endif
ifdef INSTALLED_2NDBOOTLOADER_TARGET
$(hide) $(ACP) \
$(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second
endif
ifdef BOARD_KERNEL_CMDLINE
$(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/RECOVERY/cmdline
endif
ifdef BOARD_KERNEL_BASE
$(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base
endif
ifdef BOARD_KERNEL_PAGESIZE
$(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/RECOVERY/pagesize
endif
@# Components of the boot image
$(hide) mkdir -p $(zip_root)/BOOT
$(hide) $(call package_files-copy-root, \
$(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)
ifdef INSTALLED_KERNEL_TARGET
$(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel
endif
ifdef INSTALLED_2NDBOOTLOADER_TARGET
$(hide) $(ACP) \
$(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second
endif
ifdef BOARD_KERNEL_CMDLINE
$(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline
endif
ifdef BOARD_KERNEL_BASE
$(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base
endif
ifdef BOARD_KERNEL_PAGESIZE
$(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/BOOT/pagesize
endif
$(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),\
mkdir -p $(zip_root)/RADIO; \
$(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));)
@# Contents of the system image
$(hide) $(call package_files-copy-root, \
$(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
@# Contents of the data image
$(hide) $(call package_files-copy-root, \
$(TARGET_OUT_DATA),$(zip_root)/DATA)
@# Extra contents of the OTA package
$(hide) mkdir -p $(zip_root)/OTA/bin
$(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/
$(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/
@# Files that do not end up in any images, but are necessary to
@# build them.
$(hide) mkdir -p $(zip_root)/META
$(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt
$(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt
$(hide) echo "recovery_api_version=$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/misc_info.txt
ifdef BOARD_FLASH_BLOCK_SIZE
$(hide) echo "blocksize=$(BOARD_FLASH_BLOCK_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
ifdef BOARD_BOOTIMAGE_PARTITION_SIZE
$(hide) echo "boot_size=$(BOARD_BOOTIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
ifdef BOARD_RECOVERYIMAGE_PARTITION_SIZE
$(hide) echo "recovery_size=$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
ifdef BOARD_SYSTEMIMAGE_PARTITION_SIZE
$(hide) echo "system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
ifdef BOARD_USERDATAIMAGE_PARTITION_SIZE
$(hide) echo "userdata_size=$(BOARD_USERDATAIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
$(hide) echo "tool_extensions=$(tool_extensions)" >> $(zip_root)/META/misc_info.txt
ifdef mkyaffs2_extra_flags
$(hide) echo "mkyaffs2_extra_flags=$(mkyaffs2_extra_flags)" >> $(zip_root)/META/misc_info.txt
endif
@# Zip everything up, preserving symlinks
$(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
@# Run fs_config on all the system files in the zip, and save the output
$(hide) zipinfo -1 $@ | awk -F/ 'BEGIN { OFS="/" } /^SYSTEM\// {$$1 = "system"; print}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/filesystem_config.txt
$(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/filesystem_config.txt)
target-files-package: $(BUILT_TARGET_FILES_PACKAGE)
ifneq ($(TARGET_SIMULATOR),true)
ifneq ($(TARGET_PRODUCT),sdk)
ifneq ($(TARGET_DEVICE),generic)
ifneq ($(TARGET_NO_KERNEL),true)
ifneq ($(recovery_fstab),)
第一步:創建一個root_zip根目錄,並依次在此目錄下創建所需要的如下其他目錄
①創建RECOVERY目錄,並填充該目錄的內容,包括kernel的鏡像和recovery根文件系統的鏡像。此目錄最終用於生成recovery.img。
②創建並填充BOOT目錄。包含kernel和cmdline以及pagesize大小等,該目錄最終用來生成boot.img。
③向SYSTEM目錄填充system image。
④向DATA填充data image。
⑤用於生成OTA package包所需要的額外的內容。主要包括一些bin命令。
⑥創建META目錄並向該目錄下添加一些文本文件,如apkcerts.txt(描述apk文件用到的認證證書),misc_info.txt(描述Flash內存的塊大小以及boot、recovery、system、userdata等分區的大小信息)。
⑦使用保留連接選項壓縮我們在上面獲得的root_zip目錄。
⑧使用fs_config(build/tools/fs_config)配置上面的zip包內所有的系統文件(system/下各目錄、文件)的權限屬主等信息。fs_config包含了一個頭文件#include“private/android_filesystem_config.h”。在這個頭文件中以硬編碼的方式設定了system目錄下各文件的權限、屬主。執行完配置後會將配置後的信息以文本方式輸出 到META/filesystem_config.txt中。並再一次zip壓縮成我們最終需要的原始包。第二步:上面的zip包只是一個編譯過程中生成的原始包。
這個原始zip包在實際的編譯過程中有兩個作用,一是用來生成OTA update升級包,二是用來生成系統鏡像。在編譯過程中若生成OTA update升級包時會調用一個名爲ota_from_target_files的python腳本,位置在/build/tools/releasetools/ota_from_target_files。這個腳本的作用是以第一步生成的zip原始包作爲輸入,最終生成可用的OTA升級zip包。
㈠ 首先看一下這個腳本開始部分的幫助文檔。
Usage: ota_from_target_files [flags] input_target_files output_ota_package
-b 過時的。
-k 簽名所使用的密鑰
-i 生成增量OTA包時使用此選項。後面我們會用到這個選項來生成OTA增量包。
-w 是否清除userdata分區
-n 在升級時是否不檢查時間戳,缺省要檢查,即缺省情況下只能基於舊版本升級。
-e 是否有額外運行的腳本
-m 執行過程中生成腳本(updater-script)所需要的格式,目前有兩種即amend和edify。對應上兩種版本升級時會採用不同的解釋器。缺省會同時生成兩種格式的腳 本。
-p 定義腳本用到的一些可執行文件的路徑。
-s 定義額外運行腳本的路徑。
-x 定義額外運行的腳本可能用的鍵值對。
-v 執行過程中打印出執行的命令。
-h 命令幫助
三、Recovery服務的核心方法 install_package
和Recovery服務中的wipe_data、wipe_cache不同,install_package()是升級update.zip特有的一部分,也是最核心的部分。在這一步才真正開始對我們的update.zip包進行處理。下面就開始分析這一部分。
Android系統進行升級的時候,有兩種途徑:
一種是通過接口傳遞升級包路徑自動升級(Android系統SD卡升級),升級完之後系統自動重啓。
另一種是手動進入recovery模式下,選擇升級包進行升級,升級完成之後停留在recovery界面,需要手動選擇重啓。
前者多用於手機廠商的客戶端在線升級,後者多用於開發和測試人員。但不管哪種,原理都是一樣的,都要在recovery模式下進行升級。
下面介紹的是升級包保存在cache目錄下,且升級包路徑保存在/cache/recovery/command中的方式(升級包的存放路徑,從BCB或者/cache/recovery/command裏面解析得到的)。
重啓進入升級主要流程:
- 系統重啓進入Recovery模式。讀取BCB的command,讀取到”boot-recovery”後,加載recovery.img,啓動recovery。
- 在install.cpp進行升級操作
- try_update_binary執行升級腳本
- 調用finish_recovery方法,清除BCB信息,重啓
1、系統重啓進入Recovery模式
系統重啓時會判斷/cache/recovery目錄下是否有command文件,如果存在就進入recovery模式,否則就正常啓動。
進入到Recovery模式下,將執行recovery.cpp的main函數,下面貼出關鍵代碼片段:
static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
...
ui->SetProgressType(RecoveryUI::EMPTY);
size_t chosen_item = ui->ShowMenu(
...
// handled in the switch statement below.
Device::BuiltinAction chosen_action =
(chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT))
? Device::REBOOT
: device->InvokeMenuItem(chosen_item);
switch (chosen_action) {
case Device::NO_ACTION:
break;
.....
case Device::APPLY_ADB_SIDELOAD:
case Device::APPLY_SDCARD:
case Device::ENTER_RESCUE: {
save_current_log = true;
bool adb = true;
Device::BuiltinAction reboot_action;
if (chosen_action == Device::ENTER_RESCUE) {
// Switch to graphics screen.
ui->ShowText(false);
status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action);
} else if (chosen_action == Device::APPLY_ADB_SIDELOAD) {
status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
} else {
adb = false;
int required_battery_level;
if(is_battery_ok(&required_battery_level)){
status = ApplyFromSdcard(device, ui);
....
...
}
對上述函數的流程分析
1.bootable/recovery/recovery.cpp ShowMenu顯示recovery各種菜單顯示
InvokeMenuItem 表示選中的具體哪個item
2.bootable/recovery/recoveryui/device.cpp
Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) {
return g_menu_actions[menu_position].second;
}
static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
{ "Reboot system now", Device::REBOOT },
{ "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
{ "Enter fastboot", Device::ENTER_FASTBOOT },
{ "Apply update from ADB", Device::APPLY_ADB_SIDELOAD },
{ "Apply update from SD card", Device::APPLY_SDCARD },
{ "Wipe data/factory reset", Device::WIPE_DATA },
{ "Wipe cache partition", Device::WIPE_CACHE },
{ "Mount /system", Device::MOUNT_SYSTEM },
{ "View recovery logs", Device::VIEW_RECOVERY_LOGS },
{ "Run graphics test", Device::RUN_GRAPHICS_TEST },
{ "Run locale test", Device::RUN_LOCALE_TEST },
{ "Enter rescue", Device::ENTER_RESCUE },
{ "Power off", Device::SHUTDOWN },
};
我們一般選擇從sdcard選擇升級,故選擇Apply update from SD card 對應的device type是APPLY_SDCARD
走到ApplyFromSdcard方法,其中ApplyFromSdcard是在bootable/recovery/install/fuse_sdcard_install.cpp中定義的 代碼如下:
int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
......... result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui);
break;
}
其中最爲關鍵的方法就是install_package(bootable/recovery/install/install.cpp) 最終執行的是
really_install_package
static int really_install_package(const std::string& path, bool* wipe_cache, bool ...
if (needs_mount) {
if (path[0] == '@') {
ensure_path_mounted(path.substr(1));
} else {
ensure_path_mounted(path);
}
}
// Verify package.驗證簽名
if (!verify_package(package.get(), ui)) {
...
}
// Try to open the package.打開升級包
ZipArchiveHandle zip = package->GetZipArchiveHandle();
....
// Additionally verify the compatibility of the package if it's a fresh install.
if (retry_count == 0 && !verify_package_compatibility(zip)) {
.....
. // 執行升級腳本文件,開始升級
int result =
try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, .
}
try_update_binary執行升級腳本
// If the package contains an update binary, extract it and run it.
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
std::vector<std::string>* log_buffer, int retry_count,
int* max_temperature, RecoveryUI* ui) {
std::map<std::string, std::string> metadata;
if (!ReadMetadataFromPackage(zip, &metadata)) {
LOG(ERROR) << "Failed to parse metadata in the zip file";
return INSTALL_CORRUPT;
}
bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
// Verifies against the metadata in the package first.
if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
check_status != 0) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
return check_status;
}
.......
std::vector<std::string> args;
if (int update_status =
is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
: SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
......
return INSTALL_SUCCESS;
}
通過獲取設備ro.build.ab_update信息獲得 是否爲AB動態分區 如果是AB分區升級(update_engine)進入CheckPackageMetadata 判斷當前版本信息(版本號 系列號 fingerprint等)與目標版本信息是否匹配 否則fail
最終AB分區調用SetUpAbUpdateCommands 非AB分區升級調用SetUpNonAbUpdateCommands
其中SetUpAbUpdateCommands 其實就是調用update_engine完成升級
*cmd = {
"/system/bin/update_engine_sideload",
"--payload=file://" + package,
android::base::StringPrintf("--offset=%ld", payload_offset),
"--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
android::base::StringPrintf("--status_fd=%d", status_fd),
};
OTA升級成功,清空misc分區(BCB置零),並將保存到內存系統的升級日誌/tmp/recovery.log保存到/cache/recovery/last_log。重啓設備進入Main System,升級完成。
四 recovery升級過程中log調試方法
有客戶反饋不知道如何調試recovery,在這裏介紹下recovery的調試方法。
1. 如何在recovery模式使用adb
在recovery模式下,init程序加載的rc文件是bootable/recovery/etc/init.rc。
service adbd /sbin/adbd recovery
disabled
# Always start adbd on userdebug and eng builds
on property:ro.debuggable=1
write /sys/class/android_usb/android0/enable 1
start adbd
這裏可以看到如果是在userdebug或者eng編譯模式,adbd服務是啓動的。執行adb shell時提示不存在system/bin/sh,因爲這個時候recovery/root/system爲空,並沒有sh程序可執行。
雖然adb shell不能執行,但adb的其他很多命令都是能夠使用的。
adb devices
adb push/pull
2. 如何增加log
在recovery的代碼中能看到有兩種方式添加的打印信息:printf和UI->Print。
printf輸出到stdout好理解,UI->Print調用screen_ui的print函數,將信息顯示在屏幕上。
void ScreenRecoveryUI::Print(const char *fmt, ...)
{
char buf[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, 256, fmt, ap);
va_end(ap);
fputs(buf, stdout);
除了顯示在屏幕上,也將信息輸出到了stdout標準輸出。
3. 標準輸出信息在哪
int
main(int argc, char **argv) {
// If these fail, there's not really anywhere to complain...
freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
recovery.cpp的main函數中,最開始就將stdout的輸出信息保存在了/tmp/recovery.log文件中。也就是說我們需要查看的log信息都在這裏。
再看看recovery結束時做了什麼
static void
finish_recovery(const char *send_intent) {
// Copy logs to cache so the system can find out what happened.
copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
chmod(LOG_FILE, 0600);
chown(LOG_FILE, 1000, 1000); // system user
chmod(LAST_LOG_FILE, 0640);
chmod(LAST_INSTALL_FILE, 0644);
將"/tmp/recovery.log"拷貝到了"/cache/recovery/last_log"。
4. 如何查看log
1)如果能使用adb,在recovery模式下就能使用adb pull出log信息
adb pull /tmp/recovery.log .\
2)即使adb服務不能使用,前一次recovery的log也會保存到cache/recovery目錄下,在reboot正常進入系統後pull 出來查看。
adb pull /cache/recovery/last_log .\
cache/recovery/last_install保存的是最後一次更新的OTA包。
eng 版本 如何在recovery mode下抓取LOG
[SOLUTION]
1、在recovery mode下,升級動作之後 adb pull /tmp/recovery.log
如果是KK之前版本:
2、在nomal mode下 adb pull /cache/recovery/last_log
如果是KK版本:
2、在nomal mode下 adb pull /cache/recovery/last_log_r
此兩種方法均可
如果是user版本:
In recovery mode
目前沒有辦法在user版本也看到recovery.log,目前的辦法是
直接用eng版本的recovery.img替換user版本的recovery.img,然後抓取log。
Reboot to normal mode
在user版本也會產生/cache/recovery/last_log,但是可能會不能用adb pull出來!目前的辦法是做完recovery,reboot到normal mode後,重新燒boot.img,用eng版本的boot.img替換user 版本的boot.img,然後將log pull出來!