1. 程式人生 > >Android 差分包製作流程分析

Android 差分包製作流程分析

整包與差分包生成流程

差分包生成指令

make otapackage 將編譯生成的(xxx專案為例

out/target/product/xxxxxxx/full_xxx_hxxxx-target_files-1527715386.zip

此時生成的是base.zip

在程式碼中做一些修改,產生一些差異第二次make otapackage將編譯生成的

out/target/product/xxxxxxx/full_xxx_hxxxx-target_files-1533195243.zip

 此時生成的是target.zip

兩者之間的差分包生成  ./build/tools/releasetools/ota_from_target_files

 -i base.zip target.zip update.zip

注:-i指定製作差分包,update.zip  就是升級用的差分包,這個指令碼要在Android原始碼的根目錄下執行。


/build/tools/releasetools/ota_from_target_files.py

  -v  (--verify)

      Remount and verify the checksums of the files written to the

      system and vendor (if used) partitions.  Incremental builds only.

  -i  (--incremental_from)  <file>

      Generate an incremental OTA using the given target-files zip as

      the starting build.

  -t  (--worker_threads) <int>

      Specifies the number of worker-threads that will be used when

      generating patches for incremental updates (defaults to 3).

 ......................

全包WriteFullOTAPackage函式主要功能

從ota_from_target_files.py指令碼中WriteFullOTAPackage()和WriteBlockIncrementalOTAPackage這兩個函式(分別用來生成全包和差分包)實現主要功能。

WriteFullOTAPackage將整包所需要的檔案從差分資源包中讀出並寫入到整包中。

script = edify_generator.EdifyGenerator(target_api_version, target_info)

edify_generator物件,其FormatPartition、UnpackPackageDir等方法分別是向指令碼檔案update-script中寫入格式化分割槽、解壓包等指令。

.......

同時,它還會向整包中的META-INFO/com/google/android/updater-script檔案中寫入一些操作命令。而update-binary則是一個二進位制檔案,相當於一個指令碼直譯器,能夠識別updater-script中描述的操作。

差分包WriteBlockIncrementalOTAPackage函式主要功能

   Boot分割槽相關

def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):在函式中

  .......

  source_boot = common.GetBootableImage(

      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",

      OPTIONS.source_info_dict)

  target_boot = common.GetBootableImage(

      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")

  updating_boot = (not OPTIONS.two_step and

                   (source_boot.data != target_boot.data))

 在之前已經得到了source target_file.zip中的boot.img和dest target_file.zip的boot.img資料分別為source_boot 與 target_boot,這裡只是判斷source_boot 與 target_boot是否相同來決定是否需要升級boot分割槽

System資料來源

............

  system_src = GetImage("system", OPTIONS.source_tmp)

  system_tgt = GetImage("system", OPTIONS.target_tmp)

分別從source target_file.zip和dest target_file.zip中獲取system資料,為後面生成system.new.dat、system.patch.dat、system.transfer.list三個檔案提供資料來源

生成差分包system dat,list檔案

........

  system_diff = common.BlockDifference("system", system_tgt, system_src,

                                       check_first_block,

                                       version=blockimgdiff_version,

                                       disable_imgdiff=disable_imgdiff)

生成差分包中的system.new.dat、system.patch.dat、system.transfer.list三個檔案 

   ..........

d = common.Difference(target_boot, source_boot)

當boot.img有變化時生成boot.img.p檔案,在updater-script指令碼中對應下面命令:

apply_patch("EMMC:/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/boot:8306944:eec9f25c26fa112a037e15b265445ae142c5cca6:8308992:9bfb4708a965363ec53cd8b15f8a0d640f490dbc",

            "-", 9bfb4708a965363ec53cd8b15f8a0d640f490dbc, 8308992,

            eec9f25c26fa112a037e15b265445ae142c5cca6,

            package_extract_file("patch/boot.img.p"))

校驗system分割槽

...............

# Verify the existing partitions.

system_diff.WriteVerifyScript(script, touched_blocks_only=True)

校驗升級前的system分割槽是否為基準版本,如果不是的話當然無法升級,因為system.new.dat、system.patch.dat、system.transfer.list是基於基準版本生成的。在updater-script指令碼中對應下面命令

if (range_sha1("/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system",”156,1,280,281,575,1256........”) == "faaa340bde5fccec0374da568463286d9454ae5b" || block_image_verify("/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat")) then

ui_print("Verified system image...");

生成升級system命令

.............

  system_diff.WriteScript(script, output_zip,

                          progress=0.8 if vendor_diff else 0.9)

生成升級system的命令,在updater-script指令碼中對應下面命令:

block_image_update("/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") ||

  abort("E1001: Failed to update system image.");

對應recovery中的BlockImageUpdateFn函式 

update-binary

........

script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)

把updater可執行程式放進升級包中,重新命名為update-binary。update-binary是完成升級的程式,負責解析updater-script指令碼中的命令並執行對應的C函式

元資料

.........

FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)

把元資料輸出到升級包的META-INF/com/android/metadata中

差分包目錄

差分升級包就是比較現存基礎包與原來的基礎包的差異而生成的,即該OTA包有特定的應用背景(用於兩個差分包之間)用差分包升級不會格式化system分割槽,只是對其中部分儲存段的內容進行重寫。升級過程中,升級指令碼(開啟該升級包)會檢測fingerprint,確保該升級包被正確應用。fingerprint這個屬性存在於build.prop,可通過adb shell進入根路徑,通過cat build.prop檢視這個屬性(或getprop)。內容在差分包的指令碼META-INF/com/google/android/updater-script中。

getprop("ro.rlk.projectname") == "x572_h5312_c1_in" || abort("E3004: This package is for \"x572_h5312_c1_in\" devices; this is a \"" + getprop("ro.rlk.projectname") + "\".");

ui_print("Source: XXX/HXXX/XXX-XXX:8.1.0/O11019/XXX-XXX-O-IN-180531V42:user/release-keys");

ui_print("Target: XXX/HXXX/XXX-XXX:8.1.0/O11019/XXX-XXX-O-IN-180802V69:user/release-keys");

ui_print("Verifying current system...");

getprop("ro.rlk.fingerprint") == "XXX/HXXX/XXX-XXX:8.1.0/O11019/XXX-XXX-O-IN-180531V42:user/release-keys" ||

getprop("ro.rlk.fingerprint") == "XXX/HXXX/XXX-XXX:8.1.0/O11019/XXX-XXX-O-IN-180802V69:user/release-keys" ||

    abort("E3001: Package expects build fingerprint of XXX/HXXX/XXX-XXX:8.1.0/O11019/XXX-HXXX-O-IN-180531V42:user/release-keys or XXX/HXXX/XXX-XXX:8.1.0/O11019/XXX-HXXX-O-IN-180802V69:user/release-keys; this device has " + getprop("ro.rlk.fingerprint") + ".");

apply_patch_check("EMMC:/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/boot:8306944:eec9f25c26fa112a037e15b265445ae142c5cca6:8308992:9bfb4708a965363ec53cd8b15f8a0d640f490dbc") || abort("E3005: \"EMMC:/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/boot:8306944:eec9f25c26fa112a037e15b265445ae142c5cca6:8308992:9bfb4708a965363ec53cd8b15f8a0d640f490dbc\" has unexpected contents.");

apply_patch_space(81530880) || abort("E3006: Not enough free space on /cache to apply patches.");

ui_print("Verified system image...");

差分包解壓後主要包含了如下資訊,如下截圖是一個差分包的目錄結構:

META-INF 該目錄升級包的升級指令碼

system.new.dat (升級包新資料)

system.new.dat檔案實際上是由system.transfer.list描述的一個稀疏陣列,這個過程的主要目的是降低ota.zip的大小,將system.img轉換成為稀疏陣列描述。
system.patch.dat(升級包中用於patch的資料)’
system.transfer.list(升級命令執行列表)

system.transfer.list

system.transfer.list是由build/tools/releasetools/blockimgdiff.py生成的,如下是檔案頭部的生成資訊

    out.insert(0, "%d\n" % (self.version,))   # format version number

    out.insert(1, "%d\n" % (total,))

    # v3+: the number of stash slots is unused.

    out.insert(2, "0\n")

out.insert(3, str(max_stashed_blocks) + "\n")

升級引數命令解釋(此部分摘自網路)

整個升級過程中的資料操作,包括資料轉移(move)、臨時儲存(stash)、差分目標資料寫入(bsdiff/imgdiff)、新資料寫入(new)、資料刪除(free/erase)是一個極為複雜且嚴格依照順序執行的過程,有的命令是上下相關甚至是一環扣著一環的。這些命令的執行順序是在生成升級包時即已確定。

在一個system.transfer.list檔案中:

4

787044

0

19905

erase 10,197764,228864,263267,294400,654312,654345,1051592,1080832,1081858,1113600

第一行1表示該transfer檔案的版本為4;

第二行表示new命令總共要寫入787044個block;

第三行表示同時並存的stash檔案數;

第四行表示最大tash檔案所佔用的block空間數;

第五行表示刪除的range是從197764到1113600,10表示range的區間描述數目是10個數值,即197764,228864,263267,294400,654312,654345,1051592,1080832,1081858,1113600;


上述引數之後,就是具體的操作命令,逐條仔細分析(相同的命令將略過):

move

1.   move3b8219cff7a8035c5cfe4e82a5e718c32f7439e8 2,289578,289582 4 2,287350,287354

格式:"cmdname" + "source hash"  + "tg block pos" +"sourceblock num" + "source block pos"

解析:將源block 287350至block 287354(共4 block,hash值為3b8219cff7a8035c5cfe4e82a5e718c32f7439e8)的資料移動至目標block 289578至block 289582的空間。

2.   move1e100337aa3a13fd57fdd76ff3923aaff3c3c597 2,285947,288845 2898 2,283719,286617

本命令執行格式及目標與命令1相同,但由於源塊區間(283719,286617)與目標塊區間(285947,288845)產生重疊,所以在命令執行的過程中會先將讀出的源資料臨時儲存於cache目錄下檔名為1e100337aa3a13fd57fdd76ff3923aaff3c3c597的stash檔案中,然後再將stash的資料讀出寫入目標塊區間,寫完之後再刪除這個stash 檔案。

3.   movec182ec1c52cb74fd5eb8c9ae87df62d5778d91c4 2,229351,229363 12 -c182ec1c52cb74fd5eb8c9ae87df62d5778d91c4:2,0,12

格式:"cmdname" + "source hash"  + "tg block pos" + "sourceblocknum" + "source stash file";

解析:將儲存於cache目錄下檔名為c182ec1c52cb74fd5eb8c9ae87df62d5778d91c4的stash檔案中從0至12 block的資料讀出,寫入目標塊區間(229351,229363)。

free

4.   freec182ec1c52cb74fd5eb8c9ae87df62d5778d91c4

解析: 刪除儲存於cache/reovery目錄下指定的stash檔案;

bsdiff

5.   bsdiff 0 3169086 ffc645d03fa4f56e72fefa872b792a76c26f9bb44bc0c9ddf123d54a04c800824c0951873e195ef1 2,295583,311859 16290 2,295583,311873

格式:"cmdname" +"patch offset" + "patch length" + "source hash" +"target hash" + "tg block pos" + "sourceblocknum" + "source block pos"

解析:讀取源block區間資料(block 295583至block 311873,hash值為 ffc645d03fa4f56e72fefa872b792a76c26f9bb4),與system.patch.dat檔案裡偏移量為0,長度為3169086的資料進行bsdiff差分演算法運算,生成的新資料儲存至目標塊區間blokc 295583至block 311859(hash值為4bc0c9ddf123d54a04c800824c0951873e195ef1)。

6.   bsdiff 30447439 8190624a0bae2be2c5c40d562a7f3d26e7d7ef3433296da9fb7c288322db82365bcafbe1371b3441a444 2,210082,210263 181 -624a0bae2be2c5c40d562a7f3d26e7d7ef343329:2,0,181

格式:"cmdname" +"patch offset" + "patch length" + "source hash" +"target hash" + "tg block pos" + "stash hash" +"stash range"

解析:讀取stash檔案624a0bae2be2c5c40d562a7f3d26e7d7ef343329資料(block 0至block 181,hash值為624a0bae2be2c5c40d562a7f3d26e7d7ef343329),與system.patch.dat檔案裡偏移量為30447439,長度為8190位元組的資料進行bsdiff差分演算法運算,生成的新資料儲存至目標塊區間block 210082至block 210263(hash值為6da9fb7c288322db82365bcafbe1371b3441a444)。

imgdiff

7.   imgdiff 6249859 22259bc3809a88c9f9b80b40fc23aeb5646333440717af4f900ef6c78fa7fca2ff6c2870aa88ec0247c7 2,155835,156013 178 2,153161,153339

解析:與命令6格式及執行流程一致,只是呼叫的差分演算法為imgdiff演算法。

stash

8.   stash 087da7ef6fac4bd7f31f19b428fead8067e4ac218,287287,287288,287417,287420,287444,287445,287457,287458

格式:"cmdname"+ "stash hash id" + "source range"

解析:將讀取到的源塊資料(從287287至287288,287417至287420,287444至287445,287457至287458),並確認其hash值為087da7ef6fac4bd7f31f19b428fead8067e4ac21後,儲存於cache目錄下檔名為087da7ef6fac4bd7f31f19b428fead8067e4ac21的stash檔案。

new

9.   new 2,633274,634729

解析:system.new.dat檔案中的資料被system.transfer.list檔案中的new命令按順序讀取(634729-633274)*4096個位元組,offset為前(n-1)new命令讀取的位元組總數;讀取到的資料儲存於block 633274至block 634729中。

erase

10. erase 6,32770,32929,32931,33439,65535,65536

解析:刪除block 32770至32929,block32931至33439,block 65535至65536的資料。

zero

11 .zero 4,1114498,1114620,1133015,1133016

解析:zero [rangeset]將目標分割槽的range使用0填充,需要填充0的block塊範圍總數:總共4個範圍,【0-1114498】【1114498-1114620】,【1114620-1133015】,【1133015-1133016】

列印每個差分檔案大小

以xxx的target zip

full_xxxx_hxxxx-target_files-1533195243/IMAGES/system.map其中內容是有diff相關的檔案路徑及塊區域

.............

/system/xbin/tcpdump 1047561-1047824

/system/vendor/ueventd.rc 1015803-1015804

/system/vendor/thh/soter.raw 1049084-1051079

/system/vendor/res/sound/testpattern1.wav 1015762-1015801

/system/vendor/res/sound/ringtone.wav 1047092-1047560

/system/vendor/res/images/lcd_test_02.png 1046641-1047091 

.............

會與XXX-HXXXE-O-IN-180531V42-180802V69_20180802173249

差分包中system.transfer.list patch

bsdiff 0 8110984 8ca22be96f18677d4d0867f68fa477c77b0bdde2 b15c4c039087cd9288998a0b5b7f995c78f50140 2,124693,128782 4712 2,124693,129405

 //通過這兩個值屬於system.map 檔案中diff檔案目錄的塊區間來匹配上,更改檔案的patch大小

bsdiff 8110984 256 5a057126ef0cb595ab425bf618a3eb5520b8f1e9 1c1dbf59201cd2c9e0462ad3a3850c4eacd34f1c 2,967806,967808 2 2,706180,706182

bsdiff 8111240 266 fe97fd30a0fc42d0534d064b5c1c367d270cf2f0 82b3ed14ff51ed22324c6d55f362b21965e0fe2e 2,968647,968649 2 2,707021,707023

目前在blockimgdiff.py中在生成system.transfer.list檔案之前新增相關輸出

        print("%s %10d %10d %7s %s (from %s) %d " % (

                  xf.style, xf.patch_start, xf.patch_len,

                  xf.tgt_name if xf.tgt_name == xf.src_name else (

                      xf.tgt_name + " (from " + xf.src_name + ")"),

                  str(xf.tgt_ranges), str(xf.src_ranges),xf.src_ranges.size()*4 * 1024))

xf.style差分型別

xf.patch_start patch起始位置

xf.patch_len patch檔案大小

xf.src_ranges.size()*4 * 1024)  被寫入位元組

bsdiff      0       8110984  /system/app/Chrome/Chrome.apk-2 124693-128781 (from 124693-129404) 19300352

bsdiff    8110984    256    /system/vendor/operator/app/TouchPal_ThaiPack/oat/arm64/TouchPal_ThaiPack.odex-cropped 967806-967807 (from 706180-706181) 8192

bsdiff    8111240   266     /system/vendor/operator/app/TouchPal_UrduPack/oat/arm64/TouchPal_UrduPack.odex-cropped 968647-968648 (from 707021-707022) 8192

bsdiff    8111506   2702    /system/vendor/operator/app/instagram/oat/arm/instagram.vdex 1043130-1046640 (from 877522-881032) 14381056

bsdiff    8114208  21259875 /system/vendor/operator/app/facebook_messenger/oat/arm/facebook_messenger.vdex-0 1016316-1030144 (from 740774-753204) 50917376

.......

這樣就可以檢視是比較詳細的檢視每個差分檔案的改動大小,比較方便的排查差分更新之後哪些檔案增長多。