找到了以前寫(xiě)的老文檔,09年初寫(xiě)的,不管內容怎樣,貼出來(lái)曬曬~
—————————————————————-
Android Recovery
Android Recovery: 功能簡(jiǎn)介
Android支持Recovery模式。在某些操作之后,系統會(huì )自動(dòng)重啟并進(jìn)入到Recovery模式,用戶(hù)按組合鍵開(kāi)機(HOME+POWER),也可進(jìn)入Recovery模式。該模式提供如下功能:
1、擦除用戶(hù)數據
恢復系統到出廠(chǎng)模式,即擦除用戶(hù)數據和緩存數據。
2、系統升級
系統升級的概念比較廣,包括系統文件的升級、恢復損害的系統數據、firmware的升級,以及應用軟件的維護,甚至影音文件的下載。系統升級需要使用特定的升級包,Android使用OTA[1]升級包,其初衷在于可以發(fā)揮廣域無(wú)線(xiàn)通信鏈路的優(yōu)勢,如3G。
升級方式有兩種:
1、在線(xiàn)升級
利用無(wú)線(xiàn)通信網(wǎng)絡(luò ),系統自動(dòng)連接更新源,查看有無(wú)升級包、下載OTA升級包,然后給出提示,發(fā)起升級過(guò)程,如下左圖。感覺(jué)有點(diǎn)類(lèi)似Windows XP的系統更新,只不過(guò)升級的時(shí)候,Android系統會(huì )重啟系統進(jìn)入Recovery模式。另外Android的升級內容很廣泛,比如可以通過(guò)這種方式安裝應用程序。T-Mobile已經(jīng)提供了這種服務(wù),如升級服務(wù)器以OTA無(wú)線(xiàn)方式向G1終端發(fā)送Android平臺RC33升級包,傳輸媒介可以是3G網(wǎng)絡(luò )、Wi-Fi或GPRS。
2、離線(xiàn)升級
可以將下載到的OTA包放在SD卡里,通過(guò)離線(xiàn)方式升級,如下右圖所示。這種升級方式比較靈活,不用花費無(wú)線(xiàn)流量。這樣一來(lái),使用自己制作的OTA進(jìn)行升級也成為可能。事實(shí)上,G1就是用這種方式進(jìn)行刷機的,比如更新radio firmware以支持某個(gè)頻段。
Android: 分區結構
在分析Recovery工作流程之前,我們先了解一下Android文件系統的分區結構。下表是android/bootable/recovery/root.c中提得到的結構:
| Name | Device | Partition name | Mount point | File system |
| BOOT | g_mtd_device | Boot | NULL | g_raw |
| CACHE | g_mtd_device | Cache | /cache | yaffs2 |
| DATA | g_mtd_device | Userdata | /data | yaffs2 |
| MISC | g_mtd_device | Misc | NULL | g_raw |
| PACKAGE | NULL | NULL | NULL | g_package_file |
| RECOVERY | g_mtd_device | Recovery | / | g_raw |
| SDCARD | /dev/block/mmcblk0p1 | NULL | /sdcard | Vfat |
| SYSTEM | g_mtd_device | System | /system | yaffs2 |
| TMP | NULL | NULL | /tmp | NULL |
Root file system layout
模擬器環(huán)境下adb shell里的mount輸出:
# mount
……
/dev/block/mtdblock0 /system yaffs2 ro 0 0
/dev/block/mtdblock1 /data yaffs2 rw,nosuid,nodev 0 0
/dev/block/mtdblock2 /cache yaffs2 rw,nosuid,nodev 0 0
綜上,MTD中有如下分區:
BOOT: boot.img,Linux kernel (within normal ramdisk)
MISC: bootloader message struct
RECOVERY: recovery.img,Linux kernel (within recovery ramdisk)
SYSTEM: system.img
DATA: userdata.img
CACHE: some cache files
有幾點(diǎn)說(shuō)明:
1、一般來(lái)講,主板上還有用于存儲bootloader的可擦寫(xiě)存儲設備。若具備通信能力,還要存儲radio firmware,這兩部分的更新由Recovery協(xié)助Bootloader完成,沒(méi)有代碼證明一定存在NAND flash上。
2、RECOVERY分區無(wú)文件系統,存放二進(jìn)制image。
3、SYSTEM中有recovery.img的備份:/system/recovery.img,initrc中有如下代碼:
service flash_recovery /system/bin/flash_image recovery system/recovery.img
oneshot
每次啟動(dòng),flash_image程序,會(huì )檢查recovery分區中image的header,如果與備份的recovery.img不符,就會(huì )把備份寫(xiě)到RECOVERY分區。這樣做是為了應對RECOVERY分區遭到破壞。當然,我們也可以更換這個(gè)備份,這樣也會(huì )將其寫(xiě)到RECOVERY。事實(shí)上,處于安全及版權考慮,OTA是有簽名的(其實(shí)就是JAR包),Recovery對簽名有要求,所以只能進(jìn)行被允許的升級,此時(shí)的破解思路就是更換一個(gè)不檢查簽名的Recovery程序,方法就是設法更換/system/recovery.img。
Android Recovery: 三個(gè)部分、兩個(gè)接口
Recovery的工作需要整個(gè)軟件平臺的配合,從架構角度看,有三個(gè)部分:
1、Main system:用boot.img啟動(dòng)的Linux系統,Android的正常工作模式。
2、Recovery:用recovery.img啟動(dòng)的Linux系統,主要是運行Recovery程序。
3、Bootloader:除了加載、啟動(dòng)系統,還會(huì )通過(guò)讀取flash的MISC分區獲得來(lái)自Main system和Recovery的消息,并以此決定做何種操作。
在Recovery的工作流程中,上述三個(gè)實(shí)體的通信必不可少。通信的接口有以下兩個(gè):
l CACHE分區中的三個(gè)文件:/cache/recovery/…
Recovery通過(guò)/cache/recovery里的文件與main system通信,有三個(gè)文件:
- /cache/recovery/command
Main system傳給Recovery的命令行,每一行有一個(gè)命令,支持以下幾種:
–send_intent=anystring write the text out to recovery/intent
–update_package=root:path verify install an OTA package file
–wipe_data erase user data (and cache), then reboot
–wipe_cache wipe cache (but not user data), then reboot
- /cache/recovery/log
Recovery的log輸出,在recovery運行過(guò)程中,stdout及stderr會(huì )重定位到/tmp/recovery.log文件,Recovery退出之前會(huì )將其轉儲到/cache/recovery/log中,也就是cache分區的recovery/log。
- /cache/recovery/intent
Recovery傳給Main system的信息
l BCB (bootloader control block)
struct bootloader_message {
char command[32];
char status[32];
char recovery[1024];
};
BCB是Bootloader與Recovery的通信接口,也是Bootloader與Main system的通信接口,存儲在flash中的MISC分區,占用三個(gè)page,各成員意義如下:
command:
當想要重啟進(jìn)入recovery模式,或升級radio/bootloader firmware時(shí),會(huì )更新這個(gè)域。當firmware更新完畢,為了啟動(dòng)后進(jìn)入recovery做最終的清除,bootloader還會(huì )修改它。
status:
update-radio或update-hboot完成后,bootloader會(huì )寫(xiě)入相應的信息,一般是一些狀態(tài)或執行結果。
recovery:
僅被Main system寫(xiě)入,用于向Recovery發(fā)送消息,必須以“recovery\n”開(kāi)頭,否則這個(gè)域的所有內容會(huì )被忽略。這一項的內容中“recovery/\n”以后的部分,是/cache/recovery/command支持的命令,可以認為這是在Recovery操作過(guò)程中,對命令操作的備份。Recovery也會(huì )更新這個(gè)域的信息,執行某操作前把該操作命令寫(xiě)到recovery域,并更新command域,操作完成后再清空recovery域及command域,這樣在進(jìn)入Main system之前,就能確保操作被執行。
如圖所示,Main system、Recovery與Bootloader通過(guò)上述接口通信,通信邏輯依不同的目的而不同,在后面介紹具體工作流程中還會(huì )詳細介紹。
從Main system進(jìn)入Recovery的方法
我們提到,從Main system進(jìn)入到Recovery,要修改MISC分區的數據并重啟,從而告訴Bootloader是用boot.img還是用recovery.img啟動(dòng)。
init.c里的wait_for_one_process函數中有如下代碼:
__reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, “recovery”);
一些關(guān)鍵的進(jìn)程運行異常,會(huì )重啟進(jìn)入recovery模式,這里用__reboot函數進(jìn)入recovery。跟蹤這個(gè)函數,由系統調用處理函數,到kernel_restart(char *cmd),最終調用machine_restart使用體系結構相關(guān)的代碼完成重啟。
Android中沒(méi)有給出如何處理“recovery”重啟。不過(guò)可以斷定,在重啟之前會(huì )向BCB中寫(xiě)入信息,以告知bootloader如何啟動(dòng),具體操作是這樣的:
向command域中寫(xiě)入“boot-recovery” // 此操作必做
向recovery域寫(xiě)入“recovery\n” // 此操作也可不做
這些操作很可能在kernel_restart(char *cmd)中完成,因為這一部分與體系結構無(wú)關(guān),如果要實(shí)現完整的Recovery,這部分工作是必須做的。
Bootloader得到進(jìn)入Recovery模式的指示,用recovery.img啟動(dòng),進(jìn)入Recovery模式,init.rc (bootable/recovery/etc/init.rc)的內容比Main system的要短的多,最重要的是把recovery程序作為服務(wù)啟動(dòng):
service recovery /sbin/recovery
Android Recovery: 總體流程
根據Recovery的initrc,kernel啟動(dòng)完成后,啟動(dòng)recovery服務(wù),這是一個(gè)C程序,入口在/bootable/recovery/recovery.c中,main函數結構清晰,主要流程如圖:
l get_args:首先調用get_args獲取參數,主要流程如下:
get_args不僅傳回獲取到的參數,還會(huì )將其寫(xiě)入BCB,這樣,一旦升級或擦除數據的過(guò)程中出現錯誤,重啟之后依然進(jìn)入Recovery并做相同操作。
l register_update_command,這是為update做準備工作,負責注冊update用的command & function,正是這些command & function組成了update用到的update_script:
先用commandInit(android/bootable/recovery/amend/command.c)初始化command symbol table,然后多次調用registerCommand及registerFunction注冊command及function。command相關(guān)的源代碼都在amend目錄中,語(yǔ)法的構建及解析使用Android已經(jīng)包含的Bison(Yacc)。
這里的command有15個(gè),見(jiàn)下表:
| Command Name | Argument Type | Command Handler |
| assert | CMD_ARGS_BOOLEAN | cmd_assert |
| delete | CMD_ARGS_WORDS | cmd_delete |
| delete_recursive | CMD_ARGS_WORDS | cmd_delete |
| copy_dir | CMD_ARGS_WORDS | cmd_copy_dir |
| run_program | CMD_ARGS_WORDS | cmd_run_program |
| set_perm | CMD_ARGS_WORDS | cmd_set_perm |
| set_perm_recursive | CMD_ARGS_WORDS | cmd_set_perm |
| show_progress | CMD_ARGS_WORDS | cmd_show_progress |
| symlink | CMD_ARGS_WORDS | cmd_symlink |
| format | CMD_ARGS_WORDS | cmd_format |
| write_radio_image | CMD_ARGS_WORDS | cmd_write_firmware_image |
| write_hboot_image | CMD_ARGS_WORDS | cmd_write_firmware_image |
| write_raw_image | CMD_ARGS_WORDS | cmd_write_raw_image |
| mark | CMD_ARGS_WORDS | cmd_mark |
| done | CMD_ARGS_WORDS | cmd_done |
CMD_ARGS_BOOLEAN表示該command后面接的參數是boolean值,即true或false,解析腳本時(shí)計算參數的邏輯值,然后傳給command handler,目前只有“assert”這個(gè)command用此類(lèi)型的參數。
CMD_ARGS_WORDS表示該command后面接的參數是字符,形如C程序啟動(dòng)時(shí)加的參數,解析腳本時(shí)把參數直接傳遞給command handler,比如“format BOOT:”,“BOOT:”會(huì )傳給cmd_format。
| Function Name | Function Handler |
| compatible_with | fn_compatible_with |
| update_forced | fn_update_forced |
| get_mark | fn_get_mark |
| hash_dir | fn_hash_dir |
| matches | fn_matches |
| concat | fn_concat |
| getprop | fn_getprop |
| file_contains | fn_file_contains |
function與command用同樣的處理框架,只不過(guò)function會(huì )產(chǎn)生返回值,目前見(jiàn)到的用法一般都是與assert一起使用,例如下面腳本:
assert getprop(“ro.bootloader”) == “0.95.0000″
先用getprop從properties中取得bootloader版本,然后再將比較后的boolean值傳給assert。
l prompt_and_wait:等待用戶(hù)輸入
首先打印文本信息。然后執行finish_recovery(NULL),這個(gè)函數后面介紹。然后進(jìn)入ui_wait_key等待用戶(hù)輸入,按下不同的組合鍵會(huì )有不同的動(dòng)作。對于鍵盤(pán)輸入,先到達input_thread函數(android/bootable/recovery/ui.c),在那里處理兩種組合鍵,其余才交給ui_wait_key處理:
| KEY | Funcion | Handler |
| Home + Back | reboot system now | ui_wait_key |
| Alt + S | apply sdcard:update.zip | ui_wait_key |
| Alt + W | wipe data/factory reset | ui_wait_key |
| Alt + L | toggle log text display | input_thread |
| Green + Menu + Red | reboot immediately | input_thread |
Home + Back:退出prompt_and_wait。
Alt + W或Alt + S,執行完install_package或erase_root后,若沒(méi)有激活log text display,那么,就會(huì )退出prompt_and_wait,否則繼續等待輸入。
Green + Menu + Red:立刻重啟,一般這樣還會(huì )進(jìn)入Recovery,因為BCB還沒(méi)有來(lái)得及清空。
l finish_recovery:離開(kāi)Recovery進(jìn)入Main system的必經(jīng)之路,流程如下:
intent內容作為參數傳進(jìn)來(lái),如果有intent需要告知Main system,將其寫(xiě)入/cache/recovery/intent;
將所有log信息轉儲到/cache/recovery/log文件,以供Main system讀??;
清除BCB,也就是告知Bootloader啟動(dòng)進(jìn)入Main system;
刪除/cache/recovery/command;
以上是整體流程中的幾個(gè)函數,關(guān)于安裝升級包、升級firmware等操作將在具體流程中介紹。
Android Recovery: Factory data reset流程
如果系統不穩定,可以嘗試恢復出廠(chǎng)設置,該操作會(huì )擦除DATA分區及CACHE分區,有兩種恢復方式,下面分別介紹:
l 通過(guò)Setting程序發(fā)起Facory data reset:
屏幕顯示如上圖,結合著(zhù)下面的通信圖,列出工作流程:
1、在應用程序Setting中選擇factory data reset
2、Main system向/cache/recovery/command寫(xiě)入”–wipe_data”
3、Main system重啟進(jìn)入recovery模式(方法:修改BCB)
4、Recovery向BCB寫(xiě)入”boot-recevory”和”recovery\n–wipe_data\n”
5、擦除DATA分區,里面是用戶(hù)數據,擦除CACHE分區
6、finish_recovery函數
7、重啟,回到Main system
第四個(gè)步驟,Recovery向BCB寫(xiě)入boot-recovery和–wipe_data,這是為了保證后面幾個(gè)步驟的完整執行。如,在擦除DATA分區或CACHE分區過(guò)程中,如果發(fā)生了重啟、關(guān)機等操作,導致沒(méi)有擦除成功,那么再次用常規方式開(kāi)機后,Bootloader會(huì )依據BCB的指示,引導進(jìn)入Recovery,并重新擦除這兩個(gè)分區。擦完DATA分區與CACHE分區后,調用finish_recovery,做返回Main system前最后的工作,最終要的是擦除BCB,即MISC分區。此后,用常規方式重新開(kāi)機,系統會(huì )進(jìn)入Main system。
閱讀Android的代碼,發(fā)現Setting通過(guò)RPC調用Checkin Service的masterClear()啟動(dòng)這個(gè)過(guò)程,然而在A(yíng)ndroid中并沒(méi)有找到masterClear()的實(shí)現,相關(guān)代碼需要在產(chǎn)品化的過(guò)程中加入。從ICheckinService.aidl的注釋可以了解到這個(gè)函數的作用:
/** Reboot into the recovery system and wipe all user data. */
代碼位置:
/packages/apps/Settings/src/com/android/settings/MasterClear.java
/frameworks/base/core/java/android/os/ICheckinService.aidl
l 通過(guò)HOME+POWER組合鍵進(jìn)入Recovery,再按ALT+W啟動(dòng)Factory data reset
過(guò)程比較簡(jiǎn)單,而且與上一種方式類(lèi)似,結合總體流程,步驟如下:
1、捕獲按鍵Alt + W。
2、擦除DATA分區、擦除CACHE分區。
3a、若激活了log顯示(ALT+L:toggle log text display),調用finish_recovery函數重啟,回到Main system。
3b、若沒(méi)有激活log顯示,繼續接收按鍵,可用HOME+BACK重啟回到Main system。
Android Recovery Update: 流程
l update.zip
update操作需要升級包,該升級包是文件名是*.zip,但觀(guān)察包內結構會(huì )發(fā)現其實(shí)就是JAR包,JAR包是具有特定目錄和文件結構的ZIP壓縮包,因此可以作為ZIP包解開(kāi):
MANIFEST.MF:這個(gè)manifest文件定義了與包相關(guān)數據。
XXX.SF:這是JAR文件的簽名文件,占位符xxx標識簽名者,如CERT。
XXX.DSA:與簽名文件相關(guān)聯(lián)的簽名程序塊文件,它存儲了用于簽名JAR文件的公共簽名。
在META-INF/com/google/android目錄下有update_script文件,內容就是update要做的操作,也就是前面提到過(guò)的command序列。
出于安全性及版本控制的考慮,JAR包要求必須有完整性以及合法性簽名??梢钥闯鲞@是Android確保安全的策略。JAR相關(guān)內容參見(jiàn)http://www.ibm.com/developerworks/cn/java/j-jar/,這里就不再詳細介紹。
l Main system部分
通過(guò)Android系統下載升級包并啟動(dòng)升級操作,需要上層應用Updater的支持,它是Java程序,代碼位置android/packages/apps/Updater。大致流程:
系統啟動(dòng)后,如果存在網(wǎng)絡(luò )連接,則檢查是否存在升級包;
如果存在升級包,則下載至/cache目錄;
調用Updater程序來(lái)提示是否升級;
如果Updater程序進(jìn)程不存在,則自動(dòng)啟動(dòng)此程序;
沒(méi)有在代碼中找到開(kāi)始升級后執行哪些操作。不過(guò)由recovery.c的注釋部分可以肯定一定需要重啟進(jìn)入Recovery,重啟前要更新/cache/recovery/command,以告知Recovery進(jìn)行升級:
–update_package=root:path
l update流程
update有兩種方式,第一種是上面提到的由Android啟動(dòng)的自動(dòng)update過(guò)程,升級包在cache/下,升級包的名字在/cache/recovery/command文件中指定。第二種是手動(dòng)進(jìn)入Recovery模式,然后輸入Alt + S,安裝/sdcard/update.zip升級包。兩種方式不同的只是安裝包的位置以及傳遞參數給Recovery的方法,update過(guò)程都是一樣的,工作流程如下圖所示:
- install_package @ android/bootable/recovery/install.c
得到安裝包信息,如“–update_package=CACHE:update.zip”,進(jìn)入install_package函數,流程如下左圖。mount安裝包所在的分區,然后打開(kāi)zip壓縮包,進(jìn)入handle_update_package開(kāi)始升級:
handle_update_package中,先對包進(jìn)行校驗,校驗過(guò)程分三步:
verifySignature: 檢驗SF文件與RSA文件的匹配
verifyManifest: 檢驗/META-INF/MANIFEST.MF與簽名文件中的digest是否一致
verifyArchive: 檢驗包中的文件與MANIFEST是否一致
接著(zhù)find_update_script從MANIFEST.MF找到update_script的位置,然后handle_update_script,如下圖,把內容讀到buffer后,對其進(jìn)行解析,分解成各個(gè)command(包括function)放在一個(gè)list中依次執行。
- maybe_install_firmware_update @ android/bootable/recovery/firmware.c
install_package成功后,調用maybe_install_firmware_update,這個(gè)函數處理firmware的更新。update firmware腳本是這樣的:
write_radio_image PACKAGE:radio.img
cmd_write_firmware_image處理write_radio_image這個(gè)命令,將image從壓縮包加載到RAM中,并調用remember_firmware_update更新update_type、update_data及update_length。這三個(gè)變量對于maybe_install_firmware_update是可見(jiàn)的,并由它們來(lái)判斷是否要安裝firmware。下面是主要流程:
如果升級涉及radio / hboot firmware (radio:基帶處理相關(guān),hboot:bootloader)
1、向BCB寫(xiě)入”boot-recovery”和”–wipe_cache”
……此后重啟系統,將進(jìn)入recovery并擦除CACHE分區
2、write_update_for_bootloader向raw CACHE分區寫(xiě)入image,CACHE分區的內容將被破壞。
3、向BCB寫(xiě)入”update-radio/hboot”和”–wipe_cache”
4、重啟,由Bootloader更新firmware
5、Bootloader向BCB寫(xiě)入”boot-recovery”,并保留BCB中recovery里的”–wipe_cache”
6、重啟,再次進(jìn)入Recovery,調用erase_root()擦除CACHE分區
7、finish_recovery()清除BCB
8、重啟,進(jìn)入main system
l Bootloader
每次啟動(dòng),Bootloader都會(huì )讀取位于MISC分區的bootloader_message,并檢查command區域以\0結尾,還要考慮flash存在壞塊的情況。然后根據讀取的命令,啟動(dòng)系統或者更新firmware。工作流程如下:
升級之后,無(wú)論升級成功是否,Bootloader都會(huì )進(jìn)入recovery完成最后的收尾工作,并帶著(zhù)status以告知是否成功。如果更新hboot(尚不知道為什么叫這個(gè)名字,不過(guò)可以確定它就是bootloader firmware),一旦失敗,若原有的bootloader遭到破壞,那么系統將不能boot。
為實(shí)現Android Recovery,還需要做什么?
實(shí)現Setting中Factory data reset
查看Updater工作流程,找到發(fā)起update的方法
實(shí)現__reboot(…..”recovery”)函數,連接Main system與Recovery
升級包的打包方法,以及JAR包簽名機制
實(shí)現Bootloader與Recovery及Main System的通信;
實(shí)現Bootloader的啟動(dòng)邏輯、firmware升級;
[1] OTA:Over The Air,一種手機等終端應用的“空中下載”技術(shù),利用這種技術(shù)用戶(hù)可以通過(guò)下載來(lái)修補終端的漏洞或升級某些功能







