TWRP Recovery 編譯適配教程

TWRP Recovery的強悍,使得它成爲了刷機領域當之無愧的首選。很多設備刷機的第一步,正是選擇一款適合的TWRP,然後刷上去。目前,多個品牌的熱門機型都有官方適配了,且一些開發者也給官方未覆蓋的機型適配了自己的非官方版本。

然而,開發者們並不是萬能的,總有那麼一些機型,並沒有哪一位開發者前來適配。在這樣的情境下,你是願意癡癡地等,等到哪位大神有時間做適配,還是馬上動手豐衣足食呢?

當然要自己動手啦!

事實上,TWRP的適配並沒有想象中的那麼難。理論上只需在Android的源代碼中進行,準備好必要的文件,運行編譯命令,即可完成適配。下面筆者就來結合自己的經驗,一步步講解如何適配TWRP Recovery。

配置要求

編譯TWRP和編譯Android一樣,都是相當吃系統資源的工作,因此必須確保你電腦的配置足夠。運行環境只能是Linux發行版[1],下文以Ubuntu 18.04爲例。

項目 要求
操作系統 64位Linux發行版,推薦Ubuntu 18.04
磁盤空間 至少30GB。Android源碼相當吃磁盤空間
內存 至少4GB,推薦8GB及以上

第一步:準備編譯環境

(一)安裝必要的軟件包

TWRP的編譯,需要一系列軟件包支持。在Ubuntu下,使用apt命令即可一次就安裝好:

# 更新軟件源
sudo apt update
# 安裝軟件包
sudo apt install git-core gnupg flex bison gperf build-essential \
zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \
lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache \
libgl1-mesa-dev libxml2-utils xsltproc unzip
# 安裝OpenJDK
sudo apt install openjdk-8-jdk

(二)配置ccache

ccache是一個緩存工具,它通過將編譯產生的中間文件(預處理得到的代碼、輸出文件*.o等)緩存起來,待到下次編譯同樣源文件時直接複製而不是重新生成,以此來提高編譯效率。最直接的好處,就是在make clean之後,重新編譯的速度能夠快不少。

~/.bashrc的尾部加上以下語句,啓用ccacheccache默認存放在用戶目錄下(~/.ccache),可以更改環境變量CCACHE_DIR,以設置到其他磁盤分區。

# 啓用ccache
export USE_CCACHE=1
# 改變ccache緩存路徑
export CCACHE_DIR=/mnt/seagate_drive/.ccache

然後重啓終端,或運行source ~/bashrc,使上述語句生效。

另外,可以設置ccache緩存所佔磁盤空間的大小:

ccache -M 50G

第二步:下載Android源代碼

編譯TWRP離不開Android源代碼,因爲它依賴Android源碼中的組件。推薦使用OmniROM,它與TWRP的開發團隊TeamWin有官方合作關係,由OmniROM持有TWRP的最新源代碼

(一)下載repo

repo是谷歌開發的軟件倉庫管理工具,使用Python 2.7編寫,用於批量管理由Git組織的源代碼。使用以下的命令,把repo下載到被PATH所包含的/usr/bin目錄中:

sudo curl https://storage.googleapis.com/git-repo-downloads/repo > /usr/bin/repo
sudo chmod +x /usr/bin/repo

(二)下載OmniROM源代碼

首先在磁盤中新建一個專門的目錄,用於存放OmniROM源代碼,然後使用repo init初始化源代碼倉庫。

mkdir omni8
cd omni8
repo init -u git://github.com/omnirom/android.git -b android-8.1

-b參數指定你需要的Android版本。一般編譯3.0.x系列版本用android-6.0即可,但是更新的版本則需要android-7.0及更高。筆者強烈建議只選擇最新的TWRP版本——3.2.3-0,因此對應地,使用android-8.1android-9.0

初始化完成後,我們開始下載:

repo sync

根據網絡狀況和電腦性能,整個過程會需要幾個小時甚至半天以上的時間,耐心等待即可。下載完成後,omni8目錄中就會多出包括.repo在內的很多文件夾。

(三)小貼士

  • 可以使用-j參數多開下載進程,適當提高下載效率。

    repo sync -j8
    
  • 如果下載過程中發生錯誤,可以加上兩個參數,讓repo遇到錯誤仍然繼續下載。-f使得遇到網絡錯誤時仍然繼續,--force-sync使得遇到衝突時仍然繼續。

    repo sync -f --force-sync
    
  • 如果磁盤空間不足,不妨考慮Minimal Manifest for TWRP,它只包含了編譯TWRP所需的最少組件。地址在這裏:https://github.com/minimal-manifest-twrp

第三步:下載TWRP源代碼

一般地,OmniROM源碼樹並未包含TWRP的源碼,默認下載的是AOSP的Recovery。因此,我們需要手動下載TWRP源碼,並將其添加到repo的倉庫配置文件(manifest)中。TWRP的源碼位於https://github.com/omnirom/android_bootable_recovery

omni8目錄中,首先刪掉AOSP的Recovery源碼:

rm -rf bootable/recovery

然後克隆TWRP的Recovery源碼:

git clone https://github.com/omnirom/android_bootable_recovery bootable/recovery

隨後,刪除AOSP Recovery對應的manifest項目。打開omni8/.repo/manifests/default.xml,進行如下修改:

-  <project path="bootable/recovery" name="platform/bootable/recovery" groups="pdk" />
+  <!-- project path="bootable/recovery" name="platform/bootable/recovery" groups="pdk" /> -->

再把TWRP加入manifest。打開omni8/.repo/manifests/omni-default.xml,在</manifest>節的最後進行如下修改:

   <project path="packages/apps/OmniSwitch" name="android_packages_apps_OmniSwitch" remote="omnirom" revision="android-8.1" />
   <project path="packages/apps/OpenDelta" name="android_packages_apps_OpenDelta" remote="omnirom" revision="android-8.1" />
   <project path="packages/apps/Phonograph" name="android_packages_apps_Phonograph" remote="omnirom" revision="android-8.1" />
+
+  <project name="android_bootable_recovery" path="bootable/recovery" remote="omnirom" revision="android-8.1" />
+  <project name="android_external_busybox" path="external/busybox" remote="omnirom" revision="android-8.1" />
 </manifest>

這樣,我們就可以編譯TWRP了;並且下一次我們也能通過repo sync,將TWRP一併更新。

第四步:收集配置文件

(一)配置文件的組成

編譯TWRP,離不開設備的配置文件。設備配置文件一般包括以下部分,以下所提及的路徑均以Android源碼根目錄爲參照:

  • 設備配置參數

    設備配置參數位於device目錄下,定義設備的一系列基本信息。它由一系列Makefile文件(*.mk)與設備特定的源代碼組成。

  • 內核源碼

    內核源碼位於kernel目錄下,會在Android編譯的同時一併編譯。值得注意的是,並不是所有的設備都有對應的源代碼,有些設備使用預編譯的內核(prebuilt kernel),一般位於配置參數目錄中。

  • 廠商配置參數

    廠商配置參數位於vendor目錄下,存放廠商特定的配置信息、預編譯的各種文件(可執行文件、運行庫等,通常不開源)等。相當一部分設備只需在編譯整個Android系統時才須用到廠商配置文件,編譯TWRP時不需要。

(二)在GitHub上搜索配置文件

怎樣獲得你設備的配置文件呢?去GitHub吧!GitHub上一般都會有各種設備的各類配置文件,善用搜索即可。例如,筆者的手機是華爲P6(型號爲P6-C00),那麼在GitHub中,使用以下關鍵字搜索上述三類配置文件:

  • 配置參數:device p6device huawei p6
  • 內核源碼:kernel p6kernel huawei p6
  • 廠商配置參數:vendor p6vendor huawei p6

值得注意的是,很多設備都有自己的代號,而開發者在GitHub上發佈配置文件時,往往只會用代號來表示設備。如,小米Max的代號是hydrogenhelium,三星Galaxy S5 國行雙卡的代號是kltechnduo,在這樣的情況下,你就不能用mimaxgalaxy s5G9008W爲關鍵字來搜索配置文件。要查找代號與設備的對應關係,你可以去魔趣下載頁面Lineage OS下載頁面Resurrection Remix下載頁面等開源ROM網站查詢之。

不同的開源ROM,所適用的配置文件往往各不相同,配置文件中支持的參數也往往各異。由於我們使用OmniROM作爲編譯TWRP的載體,因此最好能找到適用於OmniROM的配置文件——也就是配置參數目錄中帶有omni_<設備名>.mk的那一個。如果實在找不到,則請參閱下一步“修改配置文件”中的方法。

(三)把配置文件放到相應目錄下

在GitHub上找到一個可用的repository之後,直接git clone到相應位置即可。如何確認“相應位置”?其實很簡單。

設備配置參數目錄的規範是device/<廠商名>/<設備名>,內核源碼和廠商配置參數的路徑類似。例如,筆者手上華爲P6的配置文件目錄如下:

  • 設備配置參數:device/huawei/hwp6_u06
  • 內核源碼:kernel/huawei/hwp6_u06
  • 廠商配置參數:vendor/huawei

根據AOSP的規則,Android源碼目錄下的各個repository有固定的命名規範,這一規範就是將上述路徑的“/”換成“_”,因此也很容易猜出你找到的repository該放在哪個目錄。但是如果你碰到未按規範命名的repository,則必須按照上面的規則,推知你該放置的目標目錄。

第五步:修改配置文件

我們獲取到的現成的配置文件,不一定都是開箱即用的。它們在誕生之初,並不都是爲我們現成的這套Android源代碼設計的,能開箱即用的僅限於少數熱門機型,大部分配置文件只適用於不同版本的OmniROM,甚至其他的ROM——典型的如CyanogenMod。因此,修改配置文件,是適配TWRP的必修課。

(一)判斷配置文件是否能夠直接適用

如果你的設備幸運地爲TWRP官方所支持,那麼在修改配置文件上,你就不必花費太多功夫,開箱使用即可。可以在TWRP官網中查找你的機型

若不爲官方所支持,也不用灰心,可能會有開發者進行非官方適配。只需在GitHub你找到的設備參數repository中,查看是否有當前OmniROM版本對應的分支(branch)。理論上,適合於Android 7.0及以上的版本可直接套用於OmniROM 8.1。

(二)修改BoardConfig.mk

BoardConfig.mk是設備參數文件的組成部分,其中存放着不少與boot.img與Recovery編譯的參數。正確設置這些參數,是保證TWRP正常編譯的前提。Recovery和boot.img性質相同,均爲Android的啓動映像。

注:

  • 以下所有目錄均以Android源碼根目錄爲參照。
  • 由於排版限制,下面的設置未包括取值要求說明。寫着“是否”的爲布爾值,取值爲truefalse;其餘爲數字值或字符串值,可以不添加引號。

1. 內核打包參數

這些參數,控制着內核映像文件(kernel image)打包進入啓動映像的工作。它們一般都被開發者提前設置好,不需改動。啓動映像通過mkbootimg生成,它的源代碼位於system/core/mkbootimg中。

參數名 說明
BOARD_KERNEL_CMDLINE 內核的運行參數
BOARD_KERNEL_BASE 內核在啓動映像中的基址
BOARD_KERNEL_PAGESIZE 內核的頁面大小
BOARD_MKBOOTIMG_ARGS 需要傳遞給mkbootimg工具的額外參數
BOARD_BOOTIMAGE_PARTITION_SIZE 啓動分區的大小
BOARD_RECOVERYIMAGE_PARTITION_SIZE Recovery分區的大小
BOARD_CUSTOM_MKBOOTIMG 一些特殊的設備需要用專門的mkbootimg工具來生成啓動映像(如瑞芯微)。在這裏指定該工具的路徑。
BOARD_CUSTOM_BOOTIMG_MK 對於一些格式特殊的啓動鏡像,用戶可以自己編寫Makefile。在這裏指定自定義的Makefile文件路徑。

注意:BOARD_CUSTOM_MKBOOTIMGBOARD_CUSTOM_BOOTIMG_MK不再適用於Android 8.0及以上版本,添加該參數會導致報錯!

2. 內核編譯參數

各類第三方開源ROM的開發者都建議你自己編譯內核,而不是使用設備參數文件中預先編譯好的內核映像(prebuilt kernel image,預編譯內核)。這是因爲已編譯的內核映像無法修改,只適用於某個特定版本的系統,一旦放到一個新系統中就無法正常工作,甚至直接無法開機。如果你找到的設備參數文件提供了預編譯的內核,且你能夠找到內核源碼,請設置下面的選項。

參數名 說明
TARGET_KERNEL_SOURCE 指定內核源碼所在的目錄
TARGET_KERNEL_CONFIG 指定編譯內核使用的配置文件。
配置文件位於內核源碼arch/<系統平臺>/configs
BOARD_KERNEL_IMAGE_NAME 指定內核映像名。Android編譯系統根據它來查找內核映像[2]
編譯而成的內核映像位於內核源碼arch/<系統平臺>/boot
KERNEL_TOOLCHAIN 指定用於編譯內核的交叉工具鏈。有些設備比較特殊,使用Android源碼自帶的編譯器編譯的內核無法啓動,必須使用專用或舊版本的編譯器
TARGET_KERNEL_CROSS_COMPILE_PREFIX KERNEL_TOOLCHAIN配合使用,指定交叉工具鏈的前綴

但是,如果你實在無法找到內核源碼,你也可以指定下面的參數,以使用現有的內核(如從能正常運行的boot.imgrecovery.img中提取出來的內核,或設備參數文件提供者提供的內核)。不過不能保證在新版本的系統下正常使用!

參數名 說明
TARGET_PREBUILT_KERNEL 指定預編譯內核的路徑
TARGET_PREBUILT_RECOVERY_KERNEL 指定用於Recovery的預編譯內核路徑

注意:內核源碼與預編譯內核只能二選一,不能同時設置上面兩個表格中的所有參數!

3. Recovery相關選項

BoardConfig.mk中也包括了設置Recovery的若干選項,其中主要的參數如下所示。一般設備參數文件提供者都已經設置好了相應的參數。

參數名 說明
TARGET_RECOVERY_PIXEL_FORMAT 指定Recovery顯示的像素格式。不同的設備有不同的像素格式,常見的有RGB_8888RGB_565等,設置不當會引起花屏、黑屏等故障。
TARGET_RECOVERY_FSTAB 指定Recovery分區表信息文件(fstab)的路徑。該文件記載了可供掛載的分區信息,用戶可在Recovery中選擇是否掛載它們[3]
BOARD_RECOVERY_SWIPE 啓用滑動操作,在非觸屏Recovery中可以允許用戶上下滑動屏幕來移動高亮選項。一般啓用。[4]
DEVICE_RESOLUTION 指定設備的分辨率。
RECOVERY_GRAPHICS_USE_LINELENGTH Recovery圖形顯示時使用“行距”。具體作用筆者尚還不清楚,但是該選項若設置不當,會導致Recovery花屏。
BOARD_HAS_SDCARD_INTERNAL 設置設備是否有內置SD卡。現階段的新設備均擁有至少8GB的eMMC存儲,都將內置存儲的/data/media/0劃爲內置SD卡。
RECOVERY_SDCARD_ON_DATA 在Recovery中,確定SD卡位於data分區。現階段的新設備都將內置存儲的/data/media/0劃爲內置SD卡。與上面的BOARD_HAS_SDCARD_INTERNAL呼應。
TARGET_RECOVERY_INITRC 指定自己的init.rc路徑。init.rc是Android初始化程序init最主要的腳本,起到main()函數的作用。該選項允許用戶編寫自己的init.rc,以支持各種客製化的設備平臺。

注意:TARGET_RECOVERY_INITRC 僅適用於AOSP官方Recovery,以及Android 6.0之前的舊版本Recovery(如TWRP 2.x、ClockworkMod)。新版本的TWRP(≥3.0)會直接忽略該選項,只使用它提供的init.rc

4. TWRP專用選項

TWRP有專屬的一些選項,部分選項如下所示。

參數名 說明
TW_THEME 指定TWRP的主題。不同的主題決定TWRP顯示的不同樣式,包括分辨率、屏幕方向等。
默認可選的主題有:portrait_hdpiportrait_mdpilandscape_hdpilandscape_mdpiwatch_mdpi
必須設置,否則編譯過程中TWRP的編譯規則會報錯!
TW_CUSTOM_BATTERY_PATH 指定電池路徑。電池路徑爲內核系統目錄/sys中電池設備所在的路徑,TWRP訪問它以顯示電池電量。
例:華爲P6的路徑是/sys/devices/platform/bq_bci_battery.1/power_supply/Battery
TW_BRIGHTNESS_PATH 指定亮度路徑。亮度路徑爲內核系統目錄/sys中屏幕調節文件所在的路徑,TWRP編輯它以更改屏幕亮度。
例:華爲P6的路徑是/sys/devices/platform/k3_fb.1/leds/lcd_backlight0/brightness
TW_DEFAULT_BRIGHTNESS 指定默認亮度。取值範圍爲[0,255]
TW_MAX_BRIGHTNESS 指定最大亮度。取值範圍爲[0,255]
TW_FLASH_FROM_STORAGE 該參數作用未知,可能僅適用於2.x版本。在3.2.3-0版本中已經失效。
TW_EXTERNAL_STORAGE_PATH 指定外部存儲器的掛載路徑。
TW_EXTERNAL_STORAGE_MOUNT_POINT 指定外部存儲器的掛載點。
TW_DEFAULT_EXTERNAL_STORAGE 指定是否將默認存儲器設爲外置存儲。在3.2.3-0版本中已經失效。
TW_EXCLUDE_SUPERSU 指定是否不包含SuperSU。包含了SuperSU的TWRP會在每次重啓時提示用戶Root手機。
TW_INCLUDE_NTFS_3G 指定是否包含NTFS-3G模塊,以支持NTFS分區。
TW_IGNORE_MISC_WIPE_DATA 指定是否忽略從Bootloader傳遞而來的清除data分區的指令。這裏的misc分區存放了Bootloader傳遞給啓動映像(bootrecovery)的指令,可以控制它們啓動的行爲。
TW_EXTRA_LANGUAGES 指定是否增加額外的語言。額外的語言包括中文、日本語等。默認情況下TWRP只會包含英語與若干歐洲語言(如德語、法語、俄語、丹麥語等)。

5. 加密相關選項

現今能購買到的手機,大多都已對data分區進行了加密,要想在系統中讀取data分區,必須有一個解密的過程。官方系統(包括Recovery)的啓動就包含了解密過程;而TWRP要想讀取data分區,則必須設置好下面的選項,幷包含用於解密的其他組件。

筆者知道的加密方案有兩種:高通的QSEECOM加密,與華爲的專用文件系統強制加密(基於F2FS)。其中只有前者受到TWRP廣大開發者的支持,TWRP的很多大神都已給自己負責的機型加入了高通的加密組件。具體給你的高通處理器機型增加加密功能的方法,筆者會擇日寫上教程。(給小米Max的官方TWRP適配高通解密組件的開發者,就是我!)

參數名 說明
TW_INCLUDE_CRYPTO 指定TWRP是否包含加密組件,並啓用加密解密功能
TARGET_HW_DISK_ENCRYPTION 指定設備是否包含硬件加密功能。現階段啓用加密的設備,一般都是硬件加密
TARGET_KEYMASTER_WAIT_FOR_QSEE 對於高通方案,指定在Recovery啓動時是否等待高通加密服務程序qseecomd完成解密。必須開啓,否則TWRP的解密功能形同虛設

6. 調試相關選項

TWRP支持logcat調試功能,可以如同在Android系統裏一樣讀取logcat日誌。

參數名 說明
TWRP_INCLUDE_LOGCAT 指定是否在TWRP中包含logcat
TARGET_USES_LOGD 指定是否在TWRP中啓用日誌服務logd

(三)對非OmniROM配置文件的修改

並不是所有的設備都擁有適用於OmniROM的配置文件,因此還需將適用於其他ROM的配置文件進行一番修改。這種情況通常出現在年代略微久遠的老設備上,它們往往只有CyanogenMod 4.x等老版本ROM的配置文件。不過,修改過程並不複雜。

1. 明確設備參數文件Makefile的調用鏈

Makefile(*.mk)的調用,存在一個鏈的關係。這條調用鏈的起點是Android.mk——Android代碼樹中每個模塊的入口文件,也就是“第一道門”;另一個重要的文件是AndroidProducts.mk,它是設備參數文件的入口,也就是“第二道門”

Android.mk

一般地,Android.mk內容如下。它的作用,就是與編譯系統對接,調用當前目錄下所有的Makefile,其中包括AndroidProducts.mkBoardConfig.mk等。

LOCAL_PATH := $(call my-dir)

ifeq ($(TARGET_DEVICE),berkeley)
include $(call all-makefiles-under,$(LOCAL_PATH))
endif
AndroidProducts.mk

AndroidProducts.mk用於將設備特定的Makefile(product Makefile)包含進來,將這類Makefile傳遞給變量PRODUCT_MAKEFILES即可。一般這樣的Makefile,就是下一節將要介紹的“ROM特定的Makefile”。

PRODUCT_MAKEFILES := \
    $(LOCAL_DIR)/omni_berkeley.mk
總的調用關係

根據上方對兩個Makefile的分析,我們不難得知下面的調用關係:

  1. Android.mk調用設備參數文件目錄下所有的Makefile,包括AndroidProducts.mk

  2. AndroidProducts.mk調用設備特定的Makefile,如上例的omni_berkeley.mk

  3. omni_berkeley.mk定義目標設備。

2. 修改ROM特定的Makefile

每個ROM的設備參數文件都有一個ROM特定的專有Makefile,它包含設備的基本信息,起到當之無愧的“門戶”作用。常見的如下所示:

  • OmniROM: omni_<設備名>.mk
  • CyanogenMod: cm.mk
  • 魔趣:mokee.mk

儘管文件名不同,但它們實際上大同小異。只需將名字統一重命名爲omni_<設備名>.mk即可。注意設備名的準確性,否則編譯系統會報錯找不到文件並中止。

重命名之後,我們來分析一下這個Makefile。它可以分爲三個部分——繼承部分設備定義部分用戶自定義部分

一個示例的專有Makefile如下所示(設備爲華爲榮耀10 View,省略開頭的Apache 2.0協議內容):

#
# This file is the build configuration for a full Android
# build for grouper hardware. This cleanly combines a set of
# device-specific aspects (drivers) with a device-agnostic
# product configuration (apps).
#

# Sample: This is where we'd set a backup provider if we had one
# $(call inherit-product, device/sample/products/backup_overlay.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)

# Get the prebuilt list of APNs
$(call inherit-product, vendor/omni/config/gsm.mk)

# Inherit from the common Open Source product configuration
$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base_telephony.mk)

#treble
$(call inherit-product, $(SRC_TARGET_DIR)/product/treble_common.mk)

# must be before including omni part
TARGET_BOOTANIMATION_SIZE := 1080p

# Inherit from our custom product configuration
$(call inherit-product, vendor/omni/config/common.mk)

# Inherit from hardware-specific part of the product configuration
$(call inherit-product, device/huawei/berkeley/device.mk)

PRODUCT_PROPERTY_OVERRIDES += ro.hardware.nfc_nci=nqx.default

ALLOW_MISSING_DEPENDENCIES := true

DEVICE_PACKAGE_OVERLAYS += device/huawei/berkeley/overlay

# Discard inherited values and use our own instead.
PRODUCT_NAME := omni_berkeley
PRODUCT_DEVICE := berkeley
PRODUCT_BRAND := Huawei
PRODUCT_MODEL := Honor View 10

TARGET_VENDOR := huawei
繼承部分

繼承部分,指的是上述源文件中調用call inherit-product函數的部分,使得當前設備配置文件繼承其他的配置文件。被繼承的,包括ROM提供的通用(generic)配置文件(位於build/make/target目錄中),與設備專門的配置文件(通常在設備參數文件目錄下,以device_<設備名>.mkdevice.mk爲文件名)。

編寫時,一般只需把其他設備的代碼搬來用即可,不過需要注意區分32位和64位。

設備定義部分

設備定義部分是整個設備配置文件的核心,是編譯系統查找設備參數文件的依據。包括以下變量,缺一不可

變量名 說明
PRODUCT_NAME 產品名,通常格式爲omni_<設備名>
必須填寫,編譯系統根據這個來爲你配置編譯環境!
PRODUCT_DEVICE 設備名。這是核心參數!
PRODUCT_BRAND 品牌名
PRODUCT_MODEL 設備型號。會顯示在系統設置的“關於設備”中
TARGET_VENDOR 廠商名
用戶自定義部分

以上兩類代碼之外的其他代碼,就屬於用戶自定義的代碼了。可以放置其他的參數。

第六步:開始編譯

配置文件修改完成後,我們就可以立刻開始編譯了。

在Android源碼根目錄下,首先初始化編譯環境:

source build/envsetup.sh

然後,運行lunch命令,選擇編譯目標。也可以直接運行lunch <下面菜單中的一個編譯目標>

$ lunch

You're building on Linux

Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_mips-eng
     4. aosp_mips64-eng
     5. aosp_x86-eng
     6. aosp_x86_64-eng
     7. omni_berkeley-user
     8. omni_berkeley-userdebug
     9. omni_berkeley-eng
     10. omni_hwp6_u06-userdebug
     11. omni_hwp6_u06-eng
     12. omni_kenzo-userdebug
     13. omni_emulator-userdebug

Which would you like? [aosp_arm-eng] 10

最後,開始編譯TWRP:

make recoveryimage

編譯而成的Recovery爲out/target/product/<設備名>/recovery.img,直接使用fastboot等工具刷入即可。

第七步:調試查錯,修改代碼

適配TWRP永遠都不會是一個一蹴而就的事情,這就意味着你不可能一次就成功。潛在的各種錯誤,會潛伏在編譯過程、運行過程乃至啓動成功後的每一個角落,你要做的,就是隨時查錯。

(一)編譯時出錯

編譯過程中出錯,是最容易排查的。一言以蔽之,就是——“發現一個錯誤,解決一個錯誤”。所有的錯誤,都會在終端輸出中顯示出來,你只需要觀察錯誤的輸出,然後根據它的提示解決即可。自Android 7.0起引入的ninja構建工具,會在出錯時輸出產生錯誤的命令,以“FAILED:”前綴標明之,只需在出錯時搜索“FAILED:”,即可快速定位出錯點。

(二)運行前出錯

成功通過編譯後,得到的Recovery很可能不能正常啓動,表現爲自動重啓、黑屏等。這個時候,最直接的查錯辦法,就是拿到內核日誌

1. 獲取內核日誌的方法

一般來說,內核只要出現了panic,就會“想方設法”把崩潰時的內核日誌記錄下來。不同的平臺、不同的內核,有不同的獲取內核日誌的方法。大致梳理如下。

  • 高通:內核日誌存儲在/proc/last_kmsg中。
  • 瑞芯微(如RK3188):與高通相同。
  • 海思早期芯片(如K3V2):存儲在內核參數CONFIG_APANIC_PLABEL所制定的分區中,一般是splash

只需在正常啓動的系統中(或將另一個正常啓動的老Recovery刷入boot分區)用cat命令讀取它們即可。

2. init阻礙內核日誌記錄的坑爹設計

一些造成panic的啓動故障發生於Android初始化程序init的運行過程中。然而, init有一個很坑爹的設計,就是在使用調試方式構建的ROM中,若遇到panic則自動重啓進入Bootloader。官方說法是“有利於調試”,但是如此重啓卻會導致內核無法轉存日誌。因此有必要魔改掉這個功能。

打開init所在的目錄system/core/init,應用如下git diff補丁即可。

diff --git a/init/Android.bp b/init/Android.bp
index 45cf327f8..8a16fb2d2 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -14,6 +14,9 @@
 // limitations under the License.
 //
 
+// AnClark modify: I want to read logs. If handles panic as rebooting into bootloader, I won't get any logs!
+// Force setting DREBOOT_BOOTLOADER_ON_PANIC=0 on product_variables. 
+
 cc_defaults {
     name: "init_defaults",
     cpp_std: "experimental",
@@ -41,7 +44,7 @@ cc_defaults {
                 "-UALLOW_PERMISSIVE_SELINUX",
                 "-DALLOW_PERMISSIVE_SELINUX=1",
                 "-UREBOOT_BOOTLOADER_ON_PANIC",
-                "-DREBOOT_BOOTLOADER_ON_PANIC=1",
+                "-DREBOOT_BOOTLOADER_ON_PANIC=0",
                 "-UWORLD_WRITABLE_KMSG",
                 "-DWORLD_WRITABLE_KMSG=1",
                 "-UDUMP_ON_UMOUNT_FAILURE",
diff --git a/init/Android.mk b/init/Android.mk
index f1fe5168b..d28b4e489 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -5,10 +5,12 @@ LOCAL_PATH:= $(call my-dir)
 # --
 
 ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
+# AnClark modify: I want to read logs. If handles panic as rebooting into bootloader, I won't get any logs!
+# Force setting DREBOOT_BOOTLOADER_ON_PANIC=0 on product_variables. 
 init_options += \
     -DALLOW_LOCAL_PROP_OVERRIDE=1 \
     -DALLOW_PERMISSIVE_SELINUX=1 \
-    -DREBOOT_BOOTLOADER_ON_PANIC=1 \
+    -DREBOOT_BOOTLOADER_ON_PANIC=0 \
     -DDUMP_ON_UMOUNT_FAILURE=1
 else
 init_options += \
diff --git a/init/util.cpp b/init/util.cpp
index fdcb22d1c..a468082af 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -370,11 +370,19 @@ bool expand_props(const std::string& src, std::string* dst) {
     return true;
 }
 
+// AnClark MODIFY: Use abort() instead of rebooting into BL to trigger panic.
+/**
 void panic() {
     LOG(ERROR) << "panic: rebooting to bootloader";
     // Do not queue "shutdown" trigger since we want to shutdown immediately
     DoReboot(ANDROID_RB_RESTART2, "reboot", "bootloader", false);
 }
+**/
+
+void panic() {
+   LOG(ERROR) << "android::init::panic() invoked. Abort init to trigger kernel panic!";
+   abort();
+}
 
 static std::string init_android_dt_dir() {
     // Use the standard procfs-based path by default

應用補丁的方法:將上述代碼保存爲system/core/init/no_reboot_into_bootloader.diff,然後在system/core/init目錄中運行以下命令:

patch -p2 < no_reboot_into_bootloader.diff

(三)功能測試與Bug修復

如果你成功地過五關斬六將,久違的TWRP啓動畫面出現在你設備的屏幕上了,那麼衷心地祝賀你!接下來你要做的,就是更進一步,檢查TWRP的各項功能,捕捉可能影響使用的Bug。

一般地,在TWRP中,你可以進行以下單元測試:

  • 掛載是否正常

    進入主菜單的“Mount”(掛載),點擊每個分區的選項,檢查是否能夠正常掛載。

  • 解密是否正常

    對於高通設備,觀察解密是否正常的最直接方法,是看日誌中是否有解密失敗(decryption failed)的提示,以及“Mount”菜單中的data分區是否無法掛載。

  • 是否能連接adb

    連接電腦,運行adb devices是否能檢測到設備處在Recovery狀態下,運行adb shell是否能進入設備上的終端,並擁有Root權限(提示符爲“#”)。

  • 是否能連接MTP

    連接電腦,檢查是否有一個MTP設備出現在“此電腦”(Windows)或文件管理器邊欄(Ubuntu)中。如未出現,在“Mount”中點擊“啓用MTP(Enable MTP)”。

  • 是否支持OTG

    2014年起的大部分主流機型都支持OTG。你可以插入一根OTG數據線,然後在TWRP終端中運行lsusb,看看是否檢測到一個USB設備。此時再插入一個新設備,再看看lsusb的輸出裏是否多出了另外一個設備。如果插入的是U盤,還可以檢查/dev/block/下是否多出了塊設備sdxx爲任意一個小寫英文字母)。

  • 是否能備份/恢復系統

    測試一下系統是否能正常備份和恢復,檢查可選的備份分區是否包含必要的分區(systemdatabootrecovery)。

拓展閱讀


  1. 實際上Mac OS X也可以。

  2. 一般地,ARM平臺爲zImage,x86平臺爲bzImage,NXP(FreeScale)平臺爲uImage

  3. 僅限TWRP、LineageOS Recovery等第三方Recovery。AOSP官方的Recovery不支持自由掛載分區。

  4. 參考自:andorid recovery源碼分析 - 木馬男孩 - 博客園

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章