最近有一个版本出现启动异常,借此机会回顾一下abl里面的代码
由于问题机器得环境已经被破坏了,因此只能根据uart log去一步一步跟踪代码查看
[17:40:52]UEFI Start [ 768]
[17:40:52]Continue booting UEFI on Core 0
[17:40:52]UFS INQUIRY ID: MICRON MT064GASAO2U21 0202
[17:40:52]HW Wdog Setting from PCD : Disabled
[17:40:52]PM0: 45,
[17:40:53]UsbConfigLibOpenProtocols: PMI8998 not detected
[17:40:53]UsbConfigLibOpenProtocols: gPmicNpaClientSS1 cannot be created
[17:40:53]DisplayDxe: Getting PMIC mode failed with error(4), skipping peripheral power configuration!
[17:40:53]Alternate slot not FoundNo bootable slots found enter fastboot mode
[17:40:53]------ABL FV already mounted
[17:40:53]UEFI Total : 755 ms
[17:40:53]POST Time [ 1523] OS Loader
[17:40:53]avb_slot_verify.c:236: ERROR: boot_a: Hash of data does not match digest in descriptor.
从串口中可以看到一个关键点,那就是avb_slot_verify报错。因此我们先看看这个函数做了什么
if (Avb_StrnCmp ( (CONST CHAR8*)hash_desc.hash_algorithm, "sha256",
avb_strlen ("sha256")) == 0) {
avb_sha256_init(&sha256_ctx);
avb_sha256_update(&sha256_ctx, desc_salt, hash_desc.salt_len);
avb_sha256_update(&sha256_ctx, image_buf, hash_desc.image_size);
digest = avb_sha256_final(&sha256_ctx);
digest_len = AVB_SHA256_DIGEST_SIZE;
} else if (Avb_StrnCmp ( (CONST CHAR8*)hash_desc.hash_algorithm, "sha512",
avb_strlen ("sha512")) == 0) {
avb_sha512_init(&sha512_ctx);
avb_sha512_update(&sha512_ctx, desc_salt, hash_desc.salt_len);
avb_sha512_update(&sha512_ctx, image_buf, hash_desc.image_size);
digest = avb_sha512_final(&sha512_ctx);
digest_len = AVB_SHA512_DIGEST_SIZE;
} else {
avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL);
ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
goto out;
}
if (digest_len != hash_desc.digest_len) {
avb_errorv(
part_name, ": Digest in descriptor not of expected size.\n", NULL);
ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
goto out;
}
if (avb_safe_memcmp(digest, desc_digest, digest_len) != 0) {
avb_errorv(part_name,
": Hash of data does not match digest in descriptor.\n",
NULL);
ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
goto out;
} else {
avb_debugv (part_name, ": success: Image verification completed\n", NULL);
}
这里是调用avb_safe_memcmp函数,对digest,desc_digest内容进行比较发生错误。由于我们用的是sha256的方式进行校验,因此digest是在上面的avb_sha256_final计算得到的。digest裔据image_buf中的数据进行sha256计算,这部分的函数入口为load_and_verify_hash_partition。那么我们就得到第一个结论:abl中对boot分区进行hash校验失败从而导致上面那个打印以及进入fastboot。那么再继续往上查
descriptors =
avb_descriptor_get_all(vbmeta_buf, vbmeta_num_read, &num_descriptors);
if (descriptors == NULL) {
goto out;
}
for (n = 0; n < num_descriptors; n++) {
AvbDescriptor desc;
if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
avb_errorv(full_partition_name, ": Descriptor is invalid.\n", NULL);
ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
goto out;
}
switch (desc.tag) {
case AVB_DESCRIPTOR_TAG_HASH: {
AvbSlotVerifyResult sub_ret;
sub_ret = load_and_verify_hash_partition(ops,
requested_partitions,
ab_suffix,
allow_verification_error,
descriptors[n],
slot_data);
if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
ret = sub_ret;
if (!allow_verification_error || !result_should_continue(ret)) {
goto out;
}
}
} break;
可以看到,通过switch选择后进入load_and_verify_hash_partition函数计算hash值。其中查看一下desc.tag的获取,avb_descriptor_validate_and_byteswap函数做什么并不需要关心(看多则乱),可以看到一个关键的函数avb_descriptor_get_all(vbmeta_buf, vbmeta_num_read, &num_descriptors);在这个函数中会对vbmeta进行解析,这一阶段的函数为load_and_verify_vbmeta,具体解析什么也先不要看。再往上翻翻
/* No vbmeta partition, go through each of the requested partitions... */
for (size_t n = 0; requested_partitions[n] != NULL; n++) {
ret = load_and_verify_vbmeta(ops,
requested_partitions,
ab_suffix,
flags,
allow_verification_error,
0 /* toplevel_vbmeta_flags */,
0 /* rollback_index_location */,
requested_partitions[n],
avb_strlen(requested_partitions[n]),
NULL /* expected_public_key */,
0 /* expected_public_key_length */,
slot_data,
&algorithm_type);
if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) {
goto fail;
}
}
} else {
/* Usual path, load "vbmeta"... */
ret = load_and_verify_vbmeta(ops,
requested_partitions,
ab_suffix,
flags,
allow_verification_error,
0 /* toplevel_vbmeta_flags */,
0 /* rollback_index_location */,
"vbmeta",
avb_strlen("vbmeta"),
NULL /* expected_public_key */,
0 /* expected_public_key_length */,
slot_data,
&algorithm_type);
if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) {
goto fail;
}
}
发现是在这里调用load_and_verify_vbmeta函数,由于sa6125平台存在vbmeta分区,因此我们走的是else的流程。再往上找,看看是哪里调用avb_slot_verify,
if ( ( (!Info->MultiSlotBoot) ||
IsDynamicPartitionSupport ()) &&
Info->BootIntoRecovery) {
if (!Info->MultiSlotBoot)
VerifyFlags = VerifyFlags | AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION;
AddRequestedPartition (RequestedPartitionAll, IMG_RECOVERY);
NumRequestedPartition += 1;
Result = avb_slot_verify (Ops, (CONST CHAR8 *CONST *)RequestedPartition,
SlotSuffix, VerifyFlags, VerityFlags, &SlotData);
if (AllowVerificationError &&
ResultShouldContinue (Result)) {
DEBUG ((EFI_D_VERBOSE, "State: Unlocked, AvbSlotVerify returned "
"%a, continue boot\n",
avb_slot_verify_result_to_string (Result)));
} else if (Result != AVB_SLOT_VERIFY_RESULT_OK) {
DEBUG ((EFI_D_ERROR, "ERROR: Device State %a,AvbSlotVerify returned %a\n",
AllowVerificationError ? "Unlocked" : "Locked",
avb_slot_verify_result_to_string (Result)));
Status = EFI_LOAD_ERROR;
Info->BootState = RED;
goto out;
}
if (SlotData == NULL) {
Status = EFI_LOAD_ERROR;
Info->BootState = RED;
goto out;
}
BOOLEAN HeaderVersion = GetHeaderVersion (SlotData);
DEBUG ( (EFI_D_VERBOSE, "Recovery HeaderVersion %d \n", HeaderVersion));
if (!HeaderVersion) {
AddRequestedPartition (RequestedPartitionAll, IMG_DTBO);
NumRequestedPartition += 1;
if (SlotData != NULL) {
avb_slot_verify_data_free (SlotData);
}
Result = avb_slot_verify (Ops, (CONST CHAR8 *CONST *)RequestedPartition,
SlotSuffix, VerifyFlags, VerityFlags, &SlotData);
}
} else {
if (!Info->NumLoadedImages) {
AddRequestedPartition (RequestedPartitionAll, IMG_BOOT);
NumRequestedPartition += 1;
}
AddRequestedPartition (RequestedPartitionAll, IMG_DTBO);
NumRequestedPartition += 1;
if (IsVmEnabled ()) {
CHAR16 PartiName[MAX_GPT_NAME_SIZE];
Slot CurrentSlot;
GUARD (StrnCpyS (PartiName, (UINTN)MAX_GPT_NAME_SIZE,
(CONST CHAR16 *)L"vm-linux", StrLen (L"vm-linux")));
if (Info->MultiSlotBoot) {
CurrentSlot = GetCurrentSlotSuffix ();
GUARD (StrnCatS (PartiName, MAX_GPT_NAME_SIZE,
CurrentSlot.Suffix, StrLen (CurrentSlot.Suffix)));
}
Index = GetPartitionIndex (PartiName);
}
if (Index == INVALID_PTN ||
Index >= MAX_NUM_PARTITIONS) {
DEBUG ((EFI_D_VERBOSE, "Invalid vm-linux partition\n"));
} else {
AddRequestedPartition (RequestedPartitionAll, IMG_VMLINUX);
NumRequestedPartition += 1;
}
Result = avb_slot_verify (Ops, (CONST CHAR8 *CONST *)RequestedPartition,
SlotSuffix, VerifyFlags, VerityFlags, &SlotData);
}
可以看到这里有几个地方调用了avb_slot_verify,此使机器的情况是slot_a正常启动,并且支持dynamic_partition,因此走的是else逻辑。并且此时我们的机器设置的是locked。
if (AllowVerificationError && ResultShouldContinue (Result)) {
DEBUG ((EFI_D_VERBOSE, "State: Unlocked, AvbSlotVerify returned "
"%a, continue boot\n",
avb_slot_verify_result_to_string (Result)));
} else if (Result != AVB_SLOT_VERIFY_RESULT_OK) {
DEBUG ((EFI_D_ERROR, "ERROR: Device State %a, AvbSlotVerify returned %a\n",
AllowVerificationError ? "Unlocked" : "Locked",
avb_slot_verify_result_to_string (Result)));
Status = EFI_LOAD_ERROR;
Info->BootState = RED;
goto out;
}
在Result=avb_slot_verify (Ops, (CONST CHAR8 *CONST *)RequestedPartition,
SlotSuffix, VerifyFlags, VerityFlags, &SlotData)后,对Result进行判断,此使的Result=AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION.因此执行go out
switch (AVBVersion) {
case NO_AVB:
return LoadImageNoAuthWrapper (Info);
break;
case AVB_1:
Status = LoadImageAndAuthVB1 (Info);
break;
case AVB_2:
Status = LoadImageAndAuthVB2 (Info);
break;
case AVB_LE:
Status = LoadImageAndAuthForLE (Info);
break;
default:
DEBUG ((EFI_D_ERROR, "Unsupported AVB version %d\n", AVBVersion));
Status = EFI_UNSUPPORTED;
}
if (IsUnlocked () && Status != EFI_SUCCESS) {
DEBUG ((EFI_D_ERROR, "LoadImageAndAuth failed %r\n", Status));
return Status;
}
out:
if (Status != EFI_SUCCESS) {
if (SlotData != NULL) {
avb_slot_verify_data_free (SlotData);
}
if (Ops != NULL) {
AvbOpsFree (Ops);
}
if (UserData != NULL) {
avb_free (UserData);
}
if (VBData != NULL) {
avb_free (VBData);
}
Info->BootState = RED;
if (Info->MultiSlotBoot) {
HandleActiveSlotUnbootable ();
/* HandleActiveSlotUnbootable should have swapped slots and
* reboot the device. If no bootable slot found, enter fastboot
*/
DEBUG ((EFI_D_WARN, "No bootable slots found enter fastboot mode\n"));
} else {
DEBUG ((EFI_D_WARN,
"Non Multi-slot: Unbootable entering fastboot mode\n"));
}
}
进入go out,由于当前的系统是没有ab双系统数据同步功能的,因此如果slot_a为异常无法启动的话,那么slot_b也是无法启动的。在这里进行判断结束后可以看到我们串口内的No bootable slots found enter fastboot mode在这里能看到了,最后再看看是哪里调用了LoadImageAndAuth
if (!BootIntoFastboot) {
BootInfo Info = {0};
Info.MultiSlotBoot = MultiSlotBoot;
Info.BootIntoRecovery = BootIntoRecovery;
Info.BootReasonAlarm = BootReasonAlarm;
//avb相关判断入口
Status = LoadImageAndAuth (&Info);
if (Status != EFI_SUCCESS) {
DEBUG ((EFI_D_ERROR, "LoadImageAndAuth failed: %r\n", Status));
goto fastboot;
}
BootLinux (&Info);
}
在LinuxLoaderEntry内,选择进入recovery、fastboot还是normal模式时会进行各种判断,这里就是其中的一种,avb相关的判断,检测镜像的可靠性。如果ap侧镜像hash值与vbmeta内的hash值不一致,那么就会进入fastboot模式。具体问题具体分析,下一篇我会讲另一种进入fastboot模式的例子。
如果有任何逻辑问题,欢迎大佬指点