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
相关推荐
风和先行10 分钟前
adb 命令查看设备存储占用情况
android·adb
AaVictory.1 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰2 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶2 小时前
Android——网络请求
android
干一行,爱一行2 小时前
android camera data -> surface 显示
android
断墨先生2 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员4 小时前
PHP常量
android·ide·android studio
萌面小侠Plus5 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农5 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶5 小时前
Android——多线程、线程通信、handler机制
android