Skip to content

FirstStageMount

追踪 FirstStageMount 从 fstab、块设备和逻辑分区到 AVB hashtree、挂载和 system-as-root 切根的完整调用链。

基于android-17.0.0_r1
AndroidinitFirstStageMountAVB动态分区

FirstStageMount ​

本文面向已经读过 FirstStageInit,理解 mount(2)、uevent、device-mapper 和文件系统挂载基本概念的读者。上一篇只说明 FirstStageMain() 何时调用 DoCreateDevices() 与 DoFirstStageMount();本文把这两个调用交给的对象展开,回答一个更窄也更实用的问题:Android 17 如何把 fstab 中的 first_stage_mount 条目变成可挂载路径,并在挂载 /system 后完成根目录切换。

本文不把 AVB、dm-verity、动态分区描述成三个互相独立的“安全功能”。在真实调用链中,fstab 标志决定要等哪些块设备,逻辑分区决定 blk_device 是否改写成 /dev/block/dm-*,AVB hashtree 又可能再次改写它,最后 fs_mgr_do_mount_one() 才消费这个结果。本文不展开 vbmeta 签名格式、liblp 元数据布局或 snapshot COW 算法;这些是后续专题的边界。

读完后,你应能定位 FirstStageMount::Create()、解释 InitDevices() 为什么先找 super/vbmeta、判断某个 mount 失败是可忽略还是 fatal,并用源码测试和设备日志验证“设备没出现”“映射没创建”“校验没通过”“文件系统没挂上”这四种不同故障。

1. 对象边界 ​

FirstStageMount 不是一个只做 mount() 的工具类。它同时持有 fstab、super 分区路径、设备初始化器、AVB key 缓存和 vbmeta 分区列表:

相关源码:

  • system/core/init/first_stage_mount_android.cpp
  • system/core/init/first_stage_mount.cpp
  • system/core/init/block_dev_initializer.cpp
cpp
class FirstStageMount {
  protected:
    using Fstab = android::fs_mgr::Fstab;
    using FstabEntry = android::fs_mgr::FstabEntry;

    Fstab fstab_;
    std::string super_path_;
    std::string super_partition_name_;
    BlockDevInitializer block_dev_init_;
    std::map<std::string, std::vector<std::string>> preload_avb_key_blobs_;
    std::vector<std::string> vbmeta_partitions_;
    AvbUniquePtr avb_handle_;
};
状态所有者消费者生效时机
fstab_FirstStageMount::Create挂载循环、AVB对象创建后
super_path_InitDevicesliblp、snapshotsuper symlink 可解析后
preload_avb_key_blobs_PreloadAvbKeys独立 vbmeta 校验切到 /system 前
avb_handle_InitAvbHandlehashtree 设置首个 AVB 条目挂载时
/dev/block/dm-*BlockDevInitializerfs_mgr_do_mount_one映射或 hashtree 创建后

对象的直接消费者是 FirstStageMain,更深一层的消费者是 fs_mgr、device-mapper 和文件系统驱动。理解这些 owner 边界后,看到 blk_device 被改写时不会误以为 fstab 文件本身被修改。

2. 创建对象 ​

2.1 读取 fstab ​

Android 17 的 Android 实现使用 ReadDefaultFstab(),随后只保留标记为 first_stage_mount 的条目:

源码文件:system/core/init/first_stage_mount_android.cpp

cpp
Result<std::unique_ptr<FirstStageMount>> FirstStageMount::Create(
        const std::string& cmdline) {
    Fstab fstab;
    if (!ReadDefaultFstab(&fstab)) {
        return Error() << "failed to read default fstab for first stage mount";
    }

    bool data_on_userdata = false;
    if (auto entry = GetEntryForMountPoint(&fstab, "/data"); entry) {
        data_on_userdata = entry->blk_device == "/data/block/by-name/userdata";
    }

    std::erase_if(fstab, [](auto& entry) {
        return !entry.fs_mgr_flags.first_stage_mount;
    });
    return std::make_unique<FirstStageMountAndroid>(std::move(fstab),
                                                     data_on_userdata);
}

这里的 fstab 不是“所有分区清单”。/data 会先被观察一次,因为 mount_before_data() 可能要求 apexd 提前初始化 userdata;真正交给 FirstStageMountAndroid 的列表已经过滤过。一个条目没有 first_stage_mount,并不表示它永远不挂载,只表示它不在这个阶段消费。

2.2 构造元数据 ​

基类构造函数读取 super 分区名和设备树中的 vbmeta 列表,并合并 fstab 条目声明的 vbmeta_partition:

源码文件:system/core/init/first_stage_mount.cpp

cpp
FirstStageMount::FirstStageMount(Fstab fstab)
    : fstab_(std::move(fstab)), avb_handle_(nullptr) {
    super_partition_name_ = fs_mgr_get_super_partition_name();

    std::string device_tree_vbmeta_parts;
    read_android_dt_file("vbmeta/parts", &device_tree_vbmeta_parts);
    for (auto&& partition : Split(device_tree_vbmeta_parts, ",")) {
        if (!partition.empty()) {
            vbmeta_partitions_.emplace_back(std::move(partition));
        }
    }

    for (const auto& entry : fstab_) {
        if (!entry.vbmeta_partition.empty()) {
            vbmeta_partitions_.emplace_back(entry.vbmeta_partition);
        }
    }
}

vbmeta_partitions_ 的作用在 GetDmVerityDevices() 才显现:如果任何 first-stage 条目要求 AVB,初始化器必须提前找到验证链上的 vbmeta 分区。不能只等待 system/vendor 自身,因为顶层 vbmeta 可能位于独立分区。

3. 设备就绪 ​

3.1 目标集合 ​

基类 DoCreateDevices() 调用 InitDevices();真正被 FirstStageMain() 调用的是 Android 子类 override,它在基类设备初始化之后处理 metadata、动态分区和 snapshot:

相关源码:

  • system/core/init/first_stage_mount.cpp
  • FirstStageMount::DoCreateDevices
  • FirstStageMount::InitDevices
cpp
bool FirstStageMount::DoCreateDevices() {
    return InitDevices();
}

bool FirstStageMount::InitDevices() {
    if (!block_dev_init_.InitBootDevicesFromPartUuid()) {
        return false;
    }

    std::set<std::string> devices;
    GetSuperDeviceName(&devices);
    GetExtraBlockDevices(&devices);
    if (!GetDmVerityDevices(&devices)) {
        return false;
    }
    if (!InitRequiredDevices(std::move(devices))) {
        return false;
    }
    return true;
}

Android override 的顺序如下。/metadata 必须早于 logical partition 创建,因为 snapshot manager 需要先判断 merge 状态:

源码文件:system/core/init/first_stage_mount_android.cpp

cpp
bool FirstStageMountAndroid::DoCreateDevices() {
    if (!FirstStageMount::DoCreateDevices()) {
        return false;
    }

    auto metadata_partition = std::find_if(
            fstab_.begin(), fstab_.end(), [](const auto& entry) {
                return entry.mount_point == "/metadata";
            });
    if (metadata_partition != fstab_.end()) {
        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
            CopyDsuAvbKeys();
        }
    }

    if (!CreateLogicalPartitions()) return false;
    return true;
}

目标集合中的字符串是分区名,不一定是最终 /dev 路径。BlockDevInitializer 通过 uevent 的 partition_name、设备路径末段或设备名反推它们,再让 DeviceHandler 创建 /dev/block 节点和符号链接。

3.2 Uevent等待 ​

初始化器先重放已经存在于 sysfs 的 uevent,再等待最多 10 秒的新事件:

源码文件:system/core/init/block_dev_initializer.cpp

cpp
bool BlockDevInitializer::InitDevices(std::set<std::string> devices) {
    auto uevent_callback = [&, this](const Uevent& uevent) -> ListenerAction {
        return HandleUevent(uevent, &devices);
    };
    uevent_listener_.RegenerateUevents(uevent_callback);

    if (!devices.empty()) {
        LOG(INFO) << __PRETTY_FUNCTION__
                  << ": partition(s) not found in /sys, waiting for their uevent(s): "
                  << android::base::Join(devices, ", ");
        Timer t;
        uevent_listener_.Poll(uevent_callback, 10s);
        LOG(INFO) << "Wait for partitions returned after " << t;
    }

    if (!devices.empty()) {
        LOG(ERROR) << __PRETTY_FUNCTION__
                   << ": partition(s) not found after polling timeout: "
                   << android::base::Join(devices, ", ");
        return false;
    }
    return true;
}

重放和轮询的组合解释了“设备已经出现但首阶段仍能找到它”的行为。反过来,超时集合非空才是这个函数的失败断言;单条 uevent 被忽略可能只是名称不匹配,并不立即失败。

3.3 设备映射 ​

动态分区和 AVB 需要 device-mapper 设备节点,而 ueventd 还未启动,因此 InitRequiredDevices() 先手工初始化 device-mapper:

源码文件:system/core/init/first_stage_mount.cpp

cpp
bool FirstStageMount::InitRequiredDevices(std::set<std::string> devices) {
    if (!block_dev_init_.InitDeviceMapper()) {
        return false;
    }
    if (devices.empty()) {
        return true;
    }
    return block_dev_init_.InitDevices(std::move(devices));
}

InitDeviceMapper() 通过 /sys/devices/virtual/misc/device-mapper 重放或轮询事件;InitDmDevice() 则对 /sys/block/dm-N 做同样的事情。它们不是创建映射,只是让已经由 libdm 创建的映射获得 /dev 节点。

4. 逻辑分区 ​

4.1 Super路径 ​

当 fstab 中存在 logical 标志时,GetSuperDeviceName() 把 super 分区加入目标集合。设备就绪后,代码解析 /dev/block/by-name/<super>:

源码文件:system/core/init/first_stage_mount.cpp

cpp
if (IsDmLinearEnabled()) {
    auto super_symlink = "/dev/block/by-name/"s + super_partition_name_;
    if (!android::base::Realpath(super_symlink, &super_path_)) {
        PLOG(ERROR) << "realpath failed: " << super_symlink;
        return false;
    }
}

因此 super_path_ 在 InitDevices() 结束前无效。任何在此之前读取逻辑分区 metadata 的调用都属于顺序错误。

4.2 Metadata映射 ​

Android 子类的 CreateLogicalPartitions() 先读取 liblp metadata,再初始化 metadata 中列出的 backing partition,最后创建 dm-linear 映射:

源码文件:system/core/init/first_stage_mount_android.cpp

cpp
bool FirstStageMountAndroid::CreateLogicalPartitions() {
    if (!IsDmLinearEnabled()) return true;
    if (super_path_.empty()) {
        LOG(ERROR) << "Could not locate logical partition tables in partition "
                   << super_partition_name_;
        return false;
    }

    auto metadata = android::fs_mgr::ReadCurrentMetadata(super_path_);
    if (!metadata) {
        LOG(ERROR) << "Could not read logical partition metadata from "
                   << super_path_;
        return false;
    }
    if (!InitDmLinearBackingDevices(*metadata.get())) {
        return false;
    }
    return android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path_);
}

这里的 CreateLogicalPartitions() 名称容易误导:它不负责挂载文件系统,而是把 super 元数据中的 logical partition 映射成可打开的 block device。真正的挂载仍在后面的 MountPartition()。

4.3 Snapshot分支 ​

如果 SnapshotManager::IsSnapshotManagerNeeded() 返回 true,Android 17 会在读取普通 metadata 前进入 snapshot 分支。该分支可能先初始化 userdata,启动 first-stage snapuserd,再通过 callback 为 dm、dm-user 或 ublk 设备重放 uevent:

源码文件:system/core/init/first_stage_mount_android.cpp

cpp
if (!IsMicrodroid() && SnapshotManager::IsSnapshotManagerNeeded()) {
    auto init_devices = [this](const std::string& device) -> bool {
        if (android::base::StartsWith(device, "/dev/block/dm-")) {
            return block_dev_init_.InitDmDevice(device);
        }
        return block_dev_init_.InitDevices({device});
    };

    SnapshotManager::MapTempOtaMetadataPartitionIfNeeded(init_devices);
    auto sm = SnapshotManager::NewForFirstStageMount();
    if (!sm) return false;
    if (sm->NeedSnapshotsInFirstStageMount()) {
        return CreateSnapshotPartitions(sm.get());
    }
}

snapshot 不是 logical partition 的必经步骤;它由 OTA 状态决定。use_snapuserd_ 后续会影响 SaveRamdiskPathToSnapuserd() 和旧 ramdisk 清理,说明这条分支的状态会跨越 FirstStageMount 甚至跨越 root switch。

5. AVB链 ​

5.1 设备清单 ​

GetDmVerityDevices() 从 fstab 构造 AVB 所需设备集合。logical 条目不会通过普通 uevent 查找其最终 dm 名称;非 logical 的 /dev/... 条目则加入路径末段。若任一条目设置 avb,还必须有 vbmeta 分区:

源码文件:system/core/init/first_stage_mount.cpp

cpp
bool FirstStageMount::GetDmVerityDevices(std::set<std::string>* devices) {
    bool need_dm_verity = false;
    std::set<std::string> logical_partitions;

    for (const auto& fstab_entry : fstab_) {
        if (fstab_entry.fs_mgr_flags.avb) {
            need_dm_verity = true;
        }
        if (fstab_entry.fs_type == "overlay") continue;
        if (fstab_entry.fs_mgr_flags.logical) {
            logical_partitions.emplace(basename(fstab_entry.blk_device.c_str()));
            continue;
        }
        if (!fstab_entry.blk_device.starts_with("/dev/")) continue;
        devices->emplace(basename(fstab_entry.blk_device.c_str()));
    }

    if (need_dm_verity) {
        if (vbmeta_partitions_.empty()) {
            LOG(ERROR) << "Missing vbmeta partitions";
            return false;
        }
        std::string ab_suffix = fs_mgr_get_slot_suffix();
        for (const auto& partition : vbmeta_partitions_) {
            std::string partition_name = partition + ab_suffix;
            if (logical_partitions.count(partition_name)) continue;
            devices->emplace(partition_name);
        }
    }
    return true;
}

这里的 avb 标志和 avb_keys、avb_hashtree_digest 不是同一层概念。前者要求内置 AVB 链;后两者可能触发独立 vbmeta 读取,但代码会在真正设置 hashtree 时分支处理。

5.2 Handle缓存 ​

第一次需要 AVB 时,InitAvbHandle() 打开顶层 handle,并把版本放入环境变量:

源码文件:system/core/init/first_stage_mount.cpp

cpp
bool FirstStageMount::InitAvbHandle() {
    if (avb_handle_) return true;

    avb_handle_ = AvbHandle::Open();
    if (!avb_handle_) {
        PLOG(ERROR) << "Failed to open AvbHandle";
        return false;
    }
    setenv("INIT_AVB_VERSION", avb_handle_->avb_version().c_str(), 1);
    return true;
}

handle 是对象级缓存,不是每个分区重新打开。SetupSelinux() 之后 SecondStageMain() 会消费 INIT_AVB_VERSION,设置 ro.boot.avb_version 后清除环境变量;这也是首阶段不能提前 unset 的原因。

5.3 Hashtree分支 ​

SetUpDmVerity() 按 fstab 字段选择验证路径:优先 avb_keys,其次 avb,最后是 avb_hashtree_digest;都没有时直接返回 true:

源码文件:system/core/init/first_stage_mount.cpp

cpp
if (!fstab_entry->avb_keys.empty()) {
    if (!InitAvbHandle()) return false;
    auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(
            *fstab_entry, preload_avb_key_blobs_[fstab_entry->avb_keys]);
    if (!avb_standalone_handle) {
        if (!fstab_entry->fs_mgr_flags.avb) return false;
        hashtree_result = avb_handle_->SetUpAvbHashtree(
                fstab_entry, false /* wait_for_verity_dev */);
    } else {
        hashtree_result = avb_standalone_handle->SetUpAvbHashtree(
                fstab_entry, false /* wait_for_verity_dev */);
    }
} else if (fstab_entry->fs_mgr_flags.avb) {
    if (!InitAvbHandle()) return false;
    hashtree_result = avb_handle_->SetUpAvbHashtree(
            fstab_entry, false /* wait_for_verity_dev */);
} else if (!fstab_entry->avb_hashtree_digest.empty()) {
    if (!InitAvbHandle()) return false;
    auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(*fstab_entry);
    if (!avb_standalone_handle) return false;
    hashtree_result = avb_standalone_handle->SetUpAvbHashtree(
            fstab_entry, false /* wait_for_verity_dev */);
} else {
    return true;
}

成功建立 hashtree 后,AVB handle 会把 fstab_entry->blk_device 改成 /dev/block/dm-*,首阶段再调用 InitDmDevice() 创建节点:

源码文件:system/core/init/first_stage_mount.cpp

cpp
switch (hashtree_result) {
    case AvbHashtreeResult::kDisabled:
        return true;
    case AvbHashtreeResult::kSuccess:
        return block_dev_init_.InitDmDevice(fstab_entry->blk_device);
    default:
        return false;
}

kDisabled 的含义是顶层 vbmeta 禁用了 hashtree,允许直接挂载;它不是“校验成功”。文章和日志都必须区分 disabled、success 和 failure。

6. 挂载顺序 ​

6.1 System优先 ​

MountPartitions() 首先调用 TrySwitchSystemAsRoot()。只要 fstab 有 /system,就先预加载 AVB key、挂载 /system,然后 SwitchRoot("/system"):

源码文件:system/core/init/first_stage_mount.cpp

cpp
bool FirstStageMount::TrySwitchSystemAsRoot() {
    UseDsuIfPresent();
    PreloadAvbKeys();

    auto system_partition = std::find_if(
            fstab_.begin(), fstab_.end(), [](const auto& entry) {
                return entry.mount_point == "/system";
            });
    if (system_partition == fstab_.end()) return true;

    SaveRamdiskPathToSnapuserd();
    if (!MountPartition(system_partition, false /* erase_same_mounts */)) {
        PLOG(ERROR) << "Failed to mount /system";
        return false;
    }
    SwitchRoot("/system");
    return true;
}

PreloadAvbKeys() 必须在切根前执行,因为 key 文件可能只存在于 ramdisk。切根后的路径以 /system 为新根,后续 /vendor、/product 挂载就与 system-as-root 设备使用同一坐标系。

6.2 单条挂载 ​

每个条目的真实顺序是:创建 canonical mount point、更新 logical partition、创建 dm 节点、设置 hashtree、调用 fs_mgr_do_mount_one():

源码文件:system/core/init/first_stage_mount.cpp

cpp
bool FirstStageMount::MountPartition(const Fstab::iterator& begin,
                                     bool erase_same_mounts,
                                     Fstab::iterator* end) {
    if (end) *end = begin + 1;
    if (!fs_mgr_create_canonical_mount_point(begin->mount_point)) return false;

    if (begin->fs_mgr_flags.logical) {
        if (!fs_mgr_update_logical_partition(&(*begin))) return false;
        if (!block_dev_init_.InitDmDevice(begin->blk_device)) return false;
    }
    if (!SetUpDmVerity(&(*begin))) return false;

    bool mounted = (fs_mgr_do_mount_one(*begin) == 0);
    Fstab::iterator current = begin + 1;
    for (; current != fstab_.end() &&
           current->mount_point == begin->mount_point; current++) {
        if (!mounted) {
            current->blk_device = begin->blk_device;
            mounted = (fs_mgr_do_mount_one(*current) == 0);
        }
    }
    if (erase_same_mounts) current = fstab_.erase(begin, current);
    if (end) *end = current;
    return mounted;
}

同一 mount point 的多条 fstab entry 是 fallback 关系:第一条失败才尝试下一条,并复制已经更新过的 blk_device。因此不能把 fstab 行数直接当成挂载次数,也不能把第一条失败日志直接当成最终失败。

6.3 例外条目 ​

主循环跳过三类条目:已在 system 阶段处理的 /system、后处理的 overlayfs、只用于探测 AVB 链的 emmc 原始分区:

源码文件:system/core/init/first_stage_mount.cpp

cpp
if (current->mount_point == "/system") {
    ++current;
    continue;
}
if (current->fs_type == "overlay") {
    ++current;
    continue;
}
if (current->fs_type == "emmc") {
    ++current;
    continue;
}

普通失败按 no_fail、formattable 和必需分区三类处理:前两类记录并继续,最后一类返回 false。formattable 在这里不是“现在格式化”,而是允许该挂载失败而不阻止首阶段继续。

7. 根切换 ​

SwitchRoot() 的调用者是 TrySwitchSystemAsRoot(),而不是每个分区挂载。system 分区挂上后,旧 ramdisk 成为待释放的旧设备;后续 vendor、product、system_ext 在新根坐标中继续挂载。对于没有 /system 条目的 system-as-root 设备,TrySwitchSystemAsRoot() 直接返回,根已经由内核或早期镜像提供。

Android 子类还会在所有普通条目完成后调用 MountOverlays():

源码文件:system/core/init/first_stage_mount.cpp

cpp
bool FirstStageMount::MountPartitions() {
    if (!TrySwitchSystemAsRoot()) return false;
    if (!SkipMountingPartitions(&fstab_, true /* verbose */)) return false;

    for (auto current = fstab_.begin(); current != fstab_.end();) {
        // system、overlay、emmc 条目在循环中分别处理或跳过。
        // 其余条目调用 MountPartition。
        ++current;
    }
    MountOverlays();
    return true;
}

overlayfs 的设备准备、scratch 映射和挂载由 FirstStageMountAndroid::MountOverlays() 负责,不应在普通 MountPartition() 中提前执行,否则会破坏 /system 根切换后的路径假设。

8. 失败路径 ​

8.1 设备超时 ​

InitDevices() 失败时,先判断目标集合:super、vbmeta、普通分区和额外 userdata 都可能缺失。BlockDevInitializer 的 10 秒超时只证明 uevent 重放和轮询后仍有目标未出现,不证明 fstab 路径写错。排查顺序应是:日志中的剩余分区名、/sys 是否存在对应目录、boot device 严格匹配是否过滤了事件。

8.2 映射失败 ​

super_path_ 为空、liblp metadata 无法读取、backing device 未出现或 snapshot 创建失败,都会在挂载前返回 false。此时不应搜索文件系统 mount 错误,因为 fs_mgr_do_mount_one() 尚未被调用。

8.3 校验失败 ​

AvbHandle::Open() 失败、独立 vbmeta 加载失败且没有 avb fallback、rollback 检测失败或 hashtree 返回未知错误,都会阻止当前分区挂载。只有 kDisabled 明确允许直接挂载;设备 unlocked 只影响特定 standalone image rollback 判断,不等价于关闭全部 AVB。

8.4 挂载失败 ​

一个 mount point 有多条 entry 时,失败可能触发 fallback;没有 fallback 时再按 no_fail、formattable 或必需分区决定是否继续。最终所有必需条目都成功后才进入 overlay 和交接。恢复路径是替代 fstab entry 或修复设备状态,不是让 SwitchRoot() 在未挂载 /system 时强行执行。

9. 测试验证 ​

platform/system/core 在这个基线上没有直接把 FirstStageMount 整条路径包进 host 单元测试。完整测试依赖真实设备、sysfs、device-mapper、AVB metadata 和挂载命名空间,因此不能用一个 parser 或 liblp 单测代替启动证明。本文采用源码分支核对加设备实验,并明确每个实验的断言边界:

实验输入核心断言能证明不能证明
正常 boot 的 fstab 与 super目标 dm 节点存在且分区已挂载设备、映射、挂载链完成hashtree 每次 I/O 都正确
缺失一个必需块设备日志保留超时后的分区名设备就绪阶段失败fstab 一定写错
同 mount point 多条 entry第一条失败后第二条成功fallback 消费顺序所有设备都应配置 fallback
no_fail 条目失败后续必需分区仍继续错误被显式降级失败分区功能可用
AVB disabled 启动直接挂载且版本可读取顶层策略允许跳过 hashtree校验成功

源码入口核对命令如下:

bash
rg -n "FirstStageMount::Create|InitDevices|MountPartition|SetUpDmVerity|TrySwitchSystemAsRoot" \
  system/core/init/first_stage_mount.cpp \
  system/core/init/first_stage_mount_android.cpp
rg -n "InitDevices\(|InitDmDevice|RegenerateUevents|Poll\(" \
  system/core/init/block_dev_initializer.cpp
rg -n "ReadCurrentMetadata|CreateLogicalPartitions|CreateSnapshotPartitions" \
  system/core/init/first_stage_mount_android.cpp

有设备或 Cuttlefish 环境时,验证输入、owner 和结果:

bash
adb shell 'cat /proc/mounts | grep -E " /system | /vendor | /product "'
adb shell 'ls -l /dev/block/by-name/super /dev/block/dm-* 2>/dev/null'
adb shell 'dmesg | grep -E "partition|dm-|verity|Failed to mount|SwitchRoot"'
adb shell 'getprop ro.boot.avb_version'

断言要区分四层结果:目标分区是否出现、dm 节点是否出现、hashtree 是否被设置、文件系统是否真正挂载。ro.boot.avb_version 只能证明 AVB handle 版本被二阶段消费,不能证明每个 fstab 条目都使用了 AVB。

10. 设备边界 ​

本文基于 Android 17 源码证明了 FirstStageMount 的对象状态、fstab 过滤、uevent 设备等待、super 与逻辑分区创建、snapshot 分支、AVB hashtree 设置、/system 优先挂载、fallback 条件和 overlay 后处理。

本文没有证明具体设备的 fstab 来源文件内容、liblp 元数据布局、AVB 密钥链的密码学实现、dm-verity 内核 I/O 行为、snapshot COW 算法或厂商 overlayfs 配置。下一篇应从 FirstStageMountAndroid::CreateLogicalPartitions() 或 SetUpDmVerity() 选择一个单独展开,否则会把多个 owner 的证明边界重新混在一起。