android 14 apexd分析(1)apexd bootstrap

Apex的由来,我们都知道普通的apk我们可以通过应用商店playstore等进行更新,apex的引入是google希望也能通过playstore更新bin文件.so etc配置文件等类型文件. 这些文件的安装实际通过apexd来进行,现在我们来解析一下apexd, apexd的启动分为两个阶段,bootstrap和普通apexd启动,下面分析apexd bootstrap

  1. rc文件启动

/system/core/rootdir/init.rc#80

本阶段启动三个bootstrap apex,分别为com.android.i18n,com.android.runtime com.android.tzdata,在bootstrap 阶段主要就是提供 critical shared libraries

bash 复制代码
77     # Run apexd-bootstrap so that APEXes that provide critical libraries
78     # become available. Note that this is executed as exec_start to ensure that
79     # the libraries are available to the processes started after this statement.
80     exec_start apexd-bootstrap    
  1. main函数

/system/apex/apexd/apexd_main.cpp#118

cpp 复制代码
int main(int /*argc*/, char** argv) {
...
118    android::apex::SetConfig(android::apex::kDefaultConfig); → 全局config
...
151    if (has_subcommand) {
152      return HandleSubcommand(argv);    →  这里走 apexd --bootstrap
153    }

全局config 就是这些常量:

cpp 复制代码
61  static const ApexdConfig kDefaultConfig = {
62      kApexStatusSysprop,       →    kApexStatusSysprop = "apexd.status" apex的状态
63      kApexPackageBuiltinDirs,   →  所有apex文件所在的目录data、product、system、system_ext、vendor
64      kActiveApexPackagesDataDir,   →  kActiveApexPackagesDataDir = "/data/apex/active"
65      kApexDecompressedDir,         →    kApexDecompressedDir = "/data/apex/decompressed";
66      kOtaReservedDir,             →        kOtaReservedDir = "/data/apex/ota_reserved";
67      kApexHashTreeDir,           →   kApexHashTreeDir = "/data/apex/hashtree";
68      kStagedSessionsDir,        →   StagedSessionsDir = "/data/app-staging"
69      kMetadataSepolicyStagedDir,  →  kMetadataSepolicyStagedDir = "/metadata/sepolicy/staged";
70      kVmPayloadMetadataPartitionProp,  →  kVmPayloadMetadataPartitionProp = "apexd.payload_metadata.path"
71      "u:object_r:staging_data_file",  → staging_data_file的 file contexts
72  };
  1. OnBootstrap()
cpp 复制代码
/system/apex/apexd/apexd_main.cpp#38
36  int HandleSubcommand(char** argv) {
37    if (strcmp("--bootstrap", argv[1]) == 0) {
38      SetDefaultTag("apexd-bootstrap");
39      LOG(INFO) << "Bootstrap subcommand detected";
40      return android::apex::OnBootstrap();
41    }

/system/apex/apexd/apexd.cpp#2566
2566  int OnBootstrap() {
2567    ATRACE_NAME("OnBootstrap");
2568    auto time_started = boot_clock::now();
2569  
2570    ApexFileRepository& instance = ApexFileRepository::GetInstance(); → 创建个实例 啥也没做
2571    Result<void> status =
2572        instance.AddPreInstalledApex(gConfig->apex_built_in_dirs);  →  3.1 scan kApexPackageBuiltinDirs下的所有apex, 详见3.1.1
2573    if (!status.ok()) {
2574      LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
2575      return 1;
2576    }
2577  
2578    const auto& pre_installed_apexes = instance.GetPreInstalledApexFiles(); → 从全局变量中得到 pre_installed_store_
2579    int loop_device_cnt = pre_installed_apexes.size();
2580    // Find all bootstrap apexes
2581    std::vector<ApexFileRef> bootstrap_apexes;
2582    for (const auto& apex : pre_installed_apexes) {      →  遍历 所有apexfile
2583      if (IsBootstrapApex(apex.get())) {      →    判断是否是bootstrap apex,有三个com.android.i18n com.android.runtime com.android.tzdata
2584        LOG(INFO) << "Found bootstrap APEX " << apex.get().GetPath();
2585        bootstrap_apexes.push_back(apex);
2586        loop_device_cnt++;
2587      }
2588      if (apex.get().GetManifest().providesharedapexlibs()) {
2589        LOG(INFO) << "Found sharedlibs APEX " << apex.get().GetPath();
2590        // Sharedlis APEX might be mounted 2 times:
2591        //   * Pre-installed sharedlibs APEX will be mounted in OnStart
2592        //   * Updated sharedlibs APEX (if it exists) will be mounted in OnStart
2593        //
2594        // We already counted a loop device for one of these 2 mounts, need to add
2595        // 1 more.
2596        loop_device_cnt++;
2597      }
2598    }
.........
2613    if (auto res = loop::PreAllocateLoopDevices(loop_device_cnt); !res.ok()) { -----> 通过ioctl /dev/loop-control,按apex数量预先分配loop设备
2614      LOG(ERROR) << "Failed to pre-allocate loop devices : " << res.error();
2615    }
2616  
2617    DeviceMapper& dm = DeviceMapper::Instance();              -----> 创建 DeviceMapper实例,其实就是open(/dev/device-mapper)
2618    // Create empty dm device for each found APEX.
2619    // This is a boot time optimization that makes use of the fact that user space
2620    // paths will be created by ueventd before apexd is started, and hence
2621    // reducing the time to activate APEXEs on /data.
2622    // Note: since at this point we don't know which APEXes are updated, we are
2623    // optimistically creating a verity device for all of them. Once boot
2624    // finishes, apexd will clean up unused devices.
2625    // TODO(b/192241176): move to apexd_verity.{h,cpp}
2626    for (const auto& apex : pre_installed_apexes) {
2627      const std::string& name = apex.get().GetManifest().name();
2628      if (!dm.CreateEmptyDevice(name)) {                         -----> 按照apex的名字,所有apex创建空的dm device,ioctl(fd_, DM_DEV_CREATE, &io)
2629        LOG(ERROR) << "Failed to create empty device " << name;
2630      }
2631    }
2632  
2633    // Create directories for APEX shared libraries.
2634    auto sharedlibs_apex_dir = CreateSharedLibsApexDir();        -----> 创建目录  /apex/sharedlibs/lib{,64} for SharedLibs APEXes
2635    if (!sharedlibs_apex_dir.ok()) {
2636      LOG(ERROR) << sharedlibs_apex_dir.error();
2637      return 1;
2638    }
2639  
2640    // Now activate bootstrap apexes.
2641    auto ret =
2642        ActivateApexPackages(bootstrap_apexes, ActivationMode::kBootstrapMode);  ----> 真正的重点来了 bootstrap阶段的ActivateApexPackages(),见3.1.2
2643    if (!ret.ok()) {
2644      LOG(ERROR) << "Failed to activate bootstrap apex files : " << ret.error();
2645      return 1;
2646    }
2647  
2648    OnAllPackagesActivated(/*is_bootstrap=*/true);
2649    auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
2650      boot_clock::now() - time_started).count();
2651    LOG(INFO) << "OnBootstrap done, duration=" << time_elapsed;
2652    return 0;
2653  }

3.1.1 ScanBuiltInDir 遍历

cpp 复制代码
59  Result<void> ApexFileRepository::ScanBuiltInDir(const std::string& dir) {
60    LOG(INFO) << "Scanning " << dir << " for pre-installed ApexFiles";
.....
72    // TODO(b/179248390): scan parallelly if possible
73    for (const auto& file : *all_apex_files) {   →  遍历所有apex
74      LOG(INFO) << "Found pre-installed APEX " << file;
75      Result<ApexFile> apex_file = ApexFile::Open(file);     →  得到解析的ApexFile
.....
134      auto it = pre_installed_store_.find(name);
135      if (it == pre_installed_store_.end()) {
136        pre_installed_store_.emplace(name, std::move(*apex_file));  →  apex_file保存到全局变量中pre_installed_store_

普通apex解压如下:

cpp 复制代码
1 普通apex:
com.android.runtime-arm64$ ls
AndroidManifest.xml  apex_build_info.pb  apex_manifest.pb  apex_payload.img  apex_pubkey  assets  META-INF  resources.arsc

2 compressed apex解压如下:
compressed ape实际上是对origin_apex又包了一层
com.google.android.art_compressed$ ls
AndroidManifest.xml   apex_build_info.pb   apex_manifest.pb   apex_pubkey   META-INF   original_apex  'original_apex (1)'   stamp-cert-sha256

解压original_apex
com.google.android.art_compressed/original_apex (1)$ ls
AndroidManifest.xml  apex_build_info.pb  apex_manifest.pb  apex_payload.img  apex_pubkey  assets  META-INF  stamp-cert-sha256

关键文件是: apex_payload.img(可以直接挂载)  apex_pubkey(用于avb验证) apex_manifest.pb

主要解压以下内容,compressed apex只解压一层:

cpp 复制代码
/system/apex/apexd/apex_file.cpp#81
81  Result<ApexFile> ApexFile::Open(const std::string& path) {
82    std::optional<uint32_t> image_offset;
83    std::optional<size_t> image_size;
84    std::string manifest_content;
85    std::string pubkey;
86    std::optional<std::string> fs_type;
87    ZipEntry entry;
88  
89    unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
90    if (fd < 0) {
91      return ErrnoError() << "Failed to open package " << path << ": "
92                          << "I/O error";
93    }
94  
95    ZipArchiveHandle handle;
96    auto handle_guard =
97        android::base::make_scope_guard([&handle] { CloseArchive(handle); });
98    int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle,
99                            /*assume_ownership=*/false);
100    if (ret < 0) {
101      return Error() << "Failed to open package " << path << ": "
102                     << ErrorCodeString(ret);
103    }
104  
105    bool is_compressed = true;
106    ret = FindEntry(handle, kCompressedApexFilename, &entry);  ==> 常量为original_apex 只是check 一下是否是compressed apex啥也不做
107    if (ret < 0) {
108      is_compressed = false;
109    }
110  
111    if (!is_compressed) {                           
112      // Locate the mountable image within the zipfile and store offset and size.
113      ret = FindEntry(handle, kImageFilename, &entry);     ==> 不是compressed apex,解压apex_payload.img,获取文件系统类型 mount时使用
114      if (ret < 0) {
115        return Error() << "Could not find entry \"" << kImageFilename
116                       << "\" or \"" << kCompressedApexFilename
117                       << "\" in package " << path << ": "
118                       << ErrorCodeString(ret);
119      }
120      image_offset = entry.offset;
121      image_size = entry.uncompressed_length;
122  
123      auto fs_type_result = RetrieveFsType(fd, image_offset.value()); ==》 f2fs ext4 erofs
124      if (!fs_type_result.ok()) {
125        return Error() << "Failed to retrieve filesystem type for " << path
126                       << ": " << fs_type_result.error();
127      }
128      fs_type = std::move(*fs_type_result);
129    }
130  
131    ret = FindEntry(handle, kManifestFilenamePb, &entry);    ==>  解压apex_manifest.pb的内容保存起来
132    if (ret < 0) {
133      return Error() << "Could not find entry \"" << kManifestFilenamePb
134                     << "\" in package " << path << ": " << ErrorCodeString(ret);
135    }
136  
137    uint32_t length = entry.uncompressed_length;
138    manifest_content.resize(length, '\0');
139    ret = ExtractToMemory(handle, &entry,
140                          reinterpret_cast<uint8_t*>(&(manifest_content)[0]),
141                          length);
142    if (ret != 0) {
143      return Error() << "Failed to extract manifest from package " << path << ": "
144                     << ErrorCodeString(ret);
145    }
146  
147    ret = FindEntry(handle, kBundledPublicKeyFilename, &entry);  ==>  解压apex_pubkey的内容保存起来
148    if (ret >= 0) {
149      length = entry.uncompressed_length;
150      pubkey.resize(length, '\0');
151      ret = ExtractToMemory(handle, &entry,
152                            reinterpret_cast<uint8_t*>(&(pubkey)[0]), length);
153      if (ret != 0) {
154        return Error() << "Failed to extract public key from package " << path
155                       << ": " << ErrorCodeString(ret);
156      }
157    }
158  
159    Result<ApexManifest> manifest = ParseManifest(manifest_content);  
160    if (!manifest.ok()) {
161      return manifest.error();
162    }
163  
164    if (is_compressed && manifest->providesharedapexlibs()) {
165      return Error() << "Apex providing sharedlibs shouldn't be compressed";
166    }
167  
168    // b/179211712 the stored path should be the realpath, otherwise the path we
169    // get by scanning the directory would be different from the path we get
170    // by reading /proc/mounts, if the apex file is on a symlink dir.
171    std::string realpath;
172    if (!android::base::Realpath(path, &realpath)) {
173      return ErrnoError() << "can't get realpath of " << path;
174    }
175  
176    return ApexFile(realpath, image_offset, image_size, std::move(*manifest),  ==>  返回一个ApexFile
177                    pubkey, fs_type, is_compressed);
178  }

apex_manifest.pb的内容就是:

bash 复制代码
/system/apex/proto/apex_manifest.proto#17
syntax = "proto3";
18
19 package apex.proto;
20
21 option java_package = "com.android.apex";
22 option java_outer_classname = "Protos";
23
24 message ApexManifest {
25
26   // APEX Name. Note that this can be different from what PackageManager sees.
27   // This is used to identify an APEX and to mount under /apex directory.
28   string name = 1;
29
30   // Version Number
31   int64 version = 2;
32
33   // Pre Install Hook
34   string preInstallHook = 3;
35
36   // Post Install Hook
37   // This feature is not supported.
38   string postInstallHook = 4 [ deprecated = true ];
39
40   // Version Name
41   string versionName = 5;
42
43   // Signals whenever this APEX doesn't contain any executable code.
44   // If this field is set to true, then apexd will mount this apex
45   // with MS_NOEXEC flag.
46   bool noCode = 6;
47
48   // List of native libs which can be used by other apexes or system.
49   repeated string provideNativeLibs = 7;
50
51   // List of native libs which this apex uses from other apexes or system.
52   repeated string requireNativeLibs = 8;
53
54   // List of JNI libs.
55   // linkerconfig/libnativeloader use this field so that java libraries can
56   // load JNI libraries in the same apex.
57   // This is supposed to be filled by the build system with libraries which are
58   // marked as "is_jni: true" from the list of "native_shared_libs".
59   repeated string jniLibs = 9;
60
61   // List of libs required that are located in a shared libraries APEX.  The
62   // Android platform only checks whether this list is non-empty, and by default
63   // the Android build system never sets this. This field can be used when
64   // producing or processing an APEX using libraries in /apex/sharedlibs (see
65   // `provideSharedApexLibs` field) to store some information about the
66   // libraries.
67   repeated string requireSharedApexLibs = 10;
68
69   // Whether this APEX provides libraries to be shared with other APEXs. This
70   // causes libraries contained in the APEX to be made available under
71   // /apex/sharedlibs .
72   bool provideSharedApexLibs = 11;
73
74   message CompressedApexMetadata { ---> 只有compressed apex存在此字段,用于avb验证
75
76     // Valid only for compressed APEX. This field contains the root digest of
77     // the original_apex contained inside CAPEX.
78     string originalApexDigest = 1;
79   }
80
81   // Exists only for compressed APEX
82   CompressedApexMetadata capexMetadata = 12;
83
84   // Indicates that this APEX can be updated without rebooting device.
85   bool supportsRebootlessUpdate = 13;
86
87   // VNDK version for apexes depending on a specific version of VNDK libs.
88   string vndkVersion = 14;
89 }

3.1.2 bootstrap阶段的三个bootstrap apex的激活ActivateApexPackages()

cpp 复制代码
1810    std::vector<std::future<std::vector<Result<const ApexFile*>>>> futures;
1811    futures.reserve(worker_num);
1812    for (size_t i = 0; i < worker_num; i++) {
1813      futures.push_back(std::async(std::launch::async, ActivateApexWorker,  ------> 使用ActivateApexWorker 异步 activate apexes
1814                                   std::ref(mode), std::ref(apex_queue),
1815                                   std::ref(apex_queue_mutex)));
1816    }
cpp 复制代码
/system/apex/apexd/apexd.cpp#1744
1744  std::vector<Result<const ApexFile*>> ActivateApexWorker(
1745      ActivationMode mode, std::queue<const ApexFile*>& apex_queue,
1746      std::mutex& mutex) {
1747    ATRACE_NAME("ActivateApexWorker");
1748    std::vector<Result<const ApexFile*>> ret;
1749  
1750    while (true) {
1751      const ApexFile* apex;    
1752      { //为什么这写了个代码块,所有的线程操作同一个变量apex_queue,当然要加智能锁(代码块完事自动释放),多少个apex就开多少个线程,FIFO, pop
1753        std::lock_guard lock(mutex);  
1754        if (apex_queue.empty()) break;
1755        apex = apex_queue.front();
1756        apex_queue.pop();
1757      }
1758  
1759      std::string device_name;
1760      if (mode == ActivationMode::kBootMode) {
1761        device_name = apex->GetManifest().name();
1762      } else {
1763        device_name = GetPackageId(apex->GetManifest());
1764      }
1765      if (mode == ActivationMode::kOtaChrootMode) {
1766        device_name += ".chroot";
1767      }
1768      bool reuse_device = mode == ActivationMode::kBootMode;
1769      auto res = ActivatePackageImpl(*apex, device_name, reuse_device);  ----> 正式激活apex package
1770      if (!res.ok()) {
1771        ret.push_back(Error() << "Failed to activate " << apex->GetPath() << "("
1772                              << device_name << "): " << res.error());
1773      } else {
1774        ret.push_back({apex});
1775      }
1776    }
1777  
1778    return ret;
1779  }
cpp 复制代码
/system/apex/apexd/apexd.cpp?#1361
1361  Result<void> ActivatePackageImpl(const ApexFile& apex_file,
1362                                   const std::string& device_name,
1363                                   bool reuse_device) {
...................................................................
1419    const std::string& mount_point =
1420        apexd_private::GetPackageMountPoint(manifest);
1421  
1422    if (!version_found_mounted) {
1423      auto mount_status = MountPackage(apex_file, mount_point, device_name,  ------------------> 实际的mount动作 见第四节
1424                                       reuse_device, /*temp_mount=*/false);
1425      if (!mount_status.ok()) {
1426        return mount_status;
1427      }
1428    }



bind mount:
1419    const std::string& mount_point =
1420        apexd_private::GetPackageMountPoint(manifest);   -----------> /aepx/com.android.adbd@340912002
1421  
...........
1439      bool mounted_latest = false;
1440      // Bind mount the latest version to /apex/<package_name>, unless the
1441      // package provides shared libraries to other APEXs.
1442      if (is_newest_version) {
1443        const Result<void>& update_st = apexd_private::BindMount(
1444            apexd_private::GetActiveMountPoint(manifest), mount_point);  ----------> GetActiveMountPoint() /aepx/com.android.adbd  

例:
mount --bind  /aepx/com.android.adbd  /aepx/com.android.adbd@340912002

4 这里是把各个目录的apex mount到根目录的/apex下 顺便介绍下avb验证

cpp 复制代码
/system/apex/apexd/apexd.cpp#MountPackage

1151  Result<void> MountPackage(const ApexFile& apex, const std::string& mount_point,
1152                            const std::string& device_name, bool reuse_device,
1153                            bool temp_mount) {
1154    auto ret =
1155        MountPackageImpl(apex, mount_point, device_name,
1156                         GetHashTreeFileName(apex, /* is_new= */ false),
1157                         /* verify_image = */ false, reuse_device, temp_mount);


/system/apex/apexd/apexd.cpp#436
436  Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
437                                           const std::string& mount_point,
438                                           const std::string& device_name,
439                                           const std::string& hashtree_file,
440                                           bool verify_image, bool reuse_device,
441                                           bool temp_mount = false) {
.......................
498    auto& instance = ApexFileRepository::GetInstance();
499  
500    auto public_key = instance.GetPublicKey(apex.GetManifest().name()); ----------------> 获取avbpubkey
501    if (!public_key.ok()) {
502      return public_key.error();
503    }
504  
505    auto verity_data = apex.VerifyApexVerity(*public_key); -------> 验证apex_payload.img的vbmeta,这个类似于chain partition的验证,验证摘要签名再对比pubkey

......................
557      auto verity_table =
558          CreateVerityTable(*verity_data, loopback_device.name, hash_device, -----------> 创建hashtree
559                            /* restart_on_corruption = */ !verify_image);
560      Result<DmVerityDevice> verity_dev_res =
561          CreateVerityDevice(device_name, *verity_table, reuse_device);      -----------> 创建dm-verity
相关推荐
叽哥39 分钟前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
风往哪边走1 小时前
创建自定义语音录制View
android·前端
用户2018792831671 小时前
事件分发之“官僚主义”?或“绕圈”的艺术
android
用户2018792831671 小时前
Android事件分发为何喜欢“兜圈子”?不做个“敞亮人”!
android
Kapaseker3 小时前
你一定会喜欢的 Compose 形变动画
android
QuZhengRong3 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
zhangphil4 小时前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
android·kotlin
程序员码歌11 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
书弋江山12 小时前
flutter 跨平台编码库 protobuf 工具使用
android·flutter
来来走走15 小时前
Flutter开发 webview_flutter的基本使用
android·flutter