Android 8.0 OTA 分析——recovery
2018-02-13 10:56
155 查看
这里是OTA分析的第一部分,主要是recovery模块,后面会对脚本进行分析
如果有分析不到位的地方请指出,共同学习进步
从上面的逻辑可以看到,首先要进行电量的检测,然后是启动原因的检查,最后调用
在recovery选择菜单上有这一项,在之前Android 8.0 recovery 流程分析中有说到在
可以看到,调用了
最终也是调用
输入命令
这种方式比较特殊,以后会做补充分析,这次先略过,只需知道最终还是通过
这里有个比较重要的地方是
差分包和全分包这个文件不一样,差分包如下:
另一个比较重要的知识点是pipe(管道)通信,用于父进程(recovery)和子进程(升级进程)
pipe的应用网上例程比较多,可以搜索动手敲一下,这里需要知道
上面一个重要的函数是
从上面可以看到,具体命令的执行是由脚本解释器完成的
脚本解释器在升级包的
可能有人会问,命令都由update-binary完成了,为什么在
OTA模块基本上没有什么问题,更多的工作是如何利用升级达到我们需要实现的功能,以及给其它模块擦屁股:)
如果有分析不到位的地方请指出,共同学习进步
一、首先来看下升级的入口
1.上层写入命令主动升级:
这是最常见的方式,负责升级的APK会向command中写入升级的信息,比如”–update_package”参数和升级包的路径等[recovery.cpp] if (update_package != NULL) { // It's not entirely true that we will modify the flash. But we want // to log the update attempt since update_package is non-NULL. modified_flash = true; if (!is_battery_ok()) { ui->Print("battery capacity is not enough for installing package, needed is %d%%\n", BATTERY_OK_PERCENTAGE); // Log the error code to last_install when installation skips due to // low battery. log_failure_code(kLowBattery, update_package); status = INSTALL_SKIPPED; } else if (bootreason_in_blacklist()) { // Skip update-on-reboot when bootreason is kernel_panic or similar ui->Print("bootreason is in the blacklist; skip OTA installation\n"); log_failure_code(kBootreasonInBlacklist, update_package); status = INSTALL_SKIPPED; } else { status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true, retry_count); if (status == INSTALL_SUCCESS && should_wipe_cache) { wipe_cache(false, device); } if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT // times before we abandon this OTA update. if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { copy_logs(); set_retry_bootloader_message(retry_count, args); // Print retry count on screen. ui->Print("Retry attempt %d\n", retry_count); // Reboot and retry the update if (!reboot("reboot,recovery")) { ui->Print("Reboot failed\n"); } else { while (true) { pause(); } } } // If this is an eng or userdebug build, then automatically // turn the text display on if the script fails so the error // message is visible. if (is_ro_debuggable()) { ui->ShowText(true); } } } }
从上面的逻辑可以看到,首先要进行电量的检测,然后是启动原因的检查,最后调用
install_package升级
2.从SD卡安装升级包
这是我们调试经常要操作的地方:在recovery选择菜单上有这一项,在之前Android 8.0 recovery 流程分析中有说到在
prompt_and_wait中会检测用户选择:
case Device::APPLY_SDCARD: { bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); if (adb) { status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); } else { status = apply_from_sdcard(device, &should_wipe_cache); } if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); copy_logs(); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); } } break;
可以看到,调用了
apply_from_sdcard方法,这个方法会执行挂载SD等准备操作,在项目前期的过程中可能无法挂载,需要驱动调试
最终也是调用
install_package进行升级
3.使用adb方式更新
如果没有SD卡,又要本地调试,那么可以使用这是方法:输入命令
adb sideload XXX.zip就可以进行升级
这种方式比较特殊,以后会做补充分析,这次先略过,只需知道最终还是通过
install_package进行升级即可
二、具体的升级过程分析
install_package是通过
really_install_package进一步安装,详细看看:
[install.cpp] static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector<std::string>& log_buffer, int retry_count, int* max_temperature) { //更新屏幕的提示为正在更新 ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); ui->Print("Finding update package...\n"); // Give verification half the progress bar… //设置进度条的类型 ui->SetProgressType(RecoveryUI::DETERMINATE); //显示进度条 ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); //在log中记录升级包的路径 LOG(INFO) << "Update location: " << path; // Map the update package into memory. ui->Print("Opening update package...\n"); //path是升级包的路径,needs_mount参数代表是否需要挂载,1是挂载 if (path && needs_mount) { //确保升级包所在的目录是挂载的 //这里能看到对加密和非加密有区分,路径名带@的是加密后的手机 if (path[0] == '@') { ensure_path_mounted(path+1); } else { ensure_path_mounted(path); } } //通过mmap把升级包路径映射到内存中 MemMapping map; if (sysMapFile(path, &map) != 0) { LOG(ERROR) << "failed to map file"; return INSTALL_CORRUPT; } // Verify package. if (!verify_package(map.addr, map.length)) { log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); sysReleaseMap(&map); return INSTALL_CORRUPT; } // Try to open the package. ZipArchiveHandle zip; //从内存中打开升级包 int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip); if (err != 0) { LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err); log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); sysReleaseMap(&map); CloseArchive(zip); return INSTALL_CORRUPT; } // Additionally verify the compatibility of the package. //这里可以看到调用verify_package_compatibility函数来检查升级包的兼容性 //从函数的注释中可以看到这个和Treble功能有关 //应该是在升级包中有个compatibility.zip,有的话继续判断,没有就返回true //目前遇到的项目中都没有这个压缩包,所以先不看 //有个疑问是,这个函数里会打出log,但是升级log中没有?? if (!verify_package_compatibility(zip)) { log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure)); sysReleaseMap(&map); CloseArchive(zip); return INSTALL_CORRUPT; } // Verify and install the contents of the package. ui->Print("Installing update...\n"); if (retry_count > 0) { ui->Print("Retry attempt: %d\n", retry_count); } //升级中不能重启手机,这里应该设置相关标志位吧 ui->SetEnableReboot(false); //具体执行升级的地方 int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature); ui->SetEnableReboot(true); ui->Print("\n"); sysReleaseMap(&map); CloseArchive(zip); return result; }
1.校验升级包
这里跟踪下verify_package,比较重要
[install.cpp] bool verify_package(const unsigned char* package_data, size_t package_size) { std::vector<Certificate> loadedKeys; //从/res/keys文件中装载设备的签名文件 //文件路径在out/target/product/msm8937_64/recovery/root/res/下 //如果在recovery模式下连接adb就会看到手机中对应的目录下有keys文件 if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { LOG(ERROR) << "Failed to load keys"; return false; } LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE; // Verify package. ui->Print("Verifying update package...\n"); auto t0 = std::chrono::system_clock::now(); //这里用了bind这个函数模版,目的是在verify_file函数中调用显示进度条的方法 //校验升级包的签名,判断是否被修改,一般在调试ota升级时会把这段代码进行屏蔽,使本地编译的升级包可以正常升级 int err = verify_file(package_data, package_size, loadedKeys, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0; ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); if (err != VERIFY_SUCCESS) { LOG(ERROR) << "Signature verification failed"; LOG(ERROR) << "error: " << kZipVerificationFailure; return false; } return true; }
2.执行升级
最终执行升级的地方是try_update_binary
[install.cpp] static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache, std::vector<std::string>& log_buffer, int retry_count, int* max_temperature) { //这里是先判断升级包中/META-INF/com/android/metadata文件相关值并写入到last_install中, //注意,只有差分升级才会写入,全分不会 read_source_target_build(zip, log_buffer); //创建管道,用于父进程和子进程之间通信 //根据管道的基本知识可以知道,pipefd[0]只能读数据,pipefd[1]只能写数据 int pipefd[2]; pipe(pipefd); std::vector<std::string> args; //根据A/B升级是否开启选择对应的update_binary_command函数,这里看non A/B的情况 int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args); if (ret) { close(pipefd[0]); close(pipefd[1]); return ret; } //下面将args(args就是update_binary_command中的cmd)中的参数转换成数组形式 // Convert the vector to a NULL-terminated char* array suitable for execv. const char* chr_args[args.size() + 1]; chr_args[args.size()] = nullptr; for (size_t i = 0; i < args.size(); i++) { chr_args[i] = args[i].c_str(); } //fork了一个进程 pid_t pid = fork(); if (pid == -1) { close(pipefd[0]); close(pipefd[1]); PLOG(ERROR) << "Failed to fork update binary"; return INSTALL_ERROR; } if (pid == 0) { //当前进程为子进程 umask(022); //子进程准备写入数据,所以需要关闭读的通道 close(pipefd[0]); //调用脚本解释器执行升级脚本 execv(chr_args[0], const_cast<char**>(chr_args)); fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno)); _exit(EXIT_FAILURE); } //父进程读取数据,需要关闭写的通道 close(pipefd[1]); std::thread temperature_logger(log_max_temperature, max_temperature); *wipe_cache = false; bool retry_update = false; char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); //通过fgets方法从pipefd[0]中读取子进程传入的值给buffer //再对这些参数进行解析,执行相应的命令,比如设置进度条之类的 while (fgets(buffer, sizeof(buffer), from_child) != nullptr) { std::string line(buffer); size_t space = line.find_first_of(" \n"); std::string command(line.substr(0, space)); if (command.empty()) continue; // Get rid of the leading and trailing space and/or newline. std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space)); if (command == "progress") { std::vector<std::string> tokens = android::base::Split(args, " "); double fraction; int seconds; if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) && android::base::ParseInt(tokens[1], &seconds)) { ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds); } else { LOG(ERROR) << "invalid \"progress\" parameters: " << line; } } else if //其它命令省略 …… } //用完即关好习惯 fclose(from_child); int status; //等待子进程执行完毕 waitpid(pid, &status, 0); finish_log_temperature.notify_one(); temperature_logger.join(); if (retry_update) { return INSTALL_RETRY; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")"; return INSTALL_ERROR; } return INSTALL_SUCCESS; }
这里有个比较重要的地方是
read_source_target_build这里会读取
/META-INF/com/android/metadata
差分包和全分包这个文件不一样,差分包如下:
metadata记录了几个重要数据: ota-type(BLOCK或者FILE) post-build(目标版本的fingerprint) post-build-incremental,pre-build-incremental(这两个和差分升级有关,在`parse_build_number`中会用到) post-timestamp(时间戳) pre-build(基础版本的fingerprint) pre-device(设备名)
另一个比较重要的知识点是pipe(管道)通信,用于父进程(recovery)和子进程(升级进程)
pipe的应用网上例程比较多,可以搜索动手敲一下,这里需要知道
pipefd[0]只能读数据,pipefd[1]只能写数据所以升级进程向pipefd[1]写数据,recovery从pipefd[0]读数据
上面一个重要的函数是
update_binary_command在A/B和non A/B下有不同的重载函数,A/B下的函数做了一些升级前的准备工作
[install.cpp] int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, int status_fd, std::vector<std::string>* cmd) { CHECK(cmd != nullptr); // On traditional updates we extract the update binary from the package. static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary"; ZipString binary_name(UPDATE_BINARY_NAME); ZipEntry binary_entry; //在升级包中找到脚本解释器update-binary if (FindEntry(zip, binary_name, &binary_entry) != 0) { LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME; return INSTALL_CORRUPT; } const char* binary = "/tmp/update_binary"; unlink(binary); //创建/tmp/update_binary文件夹,并赋予对应权限 int fd = creat(binary, 0755); if (fd == -1) { PLOG(ERROR) << "Failed to create " << binary; return INSTALL_ERROR; } //将升级包中的脚本解释器拷贝到创建的文件夹中 int32_t error = ExtractEntryToFile(zip, &binary_entry, fd); close(fd); if (error != 0) { LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error); return INSTALL_ERROR; } //cmd实际上是即将传给子进程执行的命令 *cmd = { binary,//脚本解释器 EXPAND(RECOVERY_API_VERSION), // defined in Android.mk,这里是3 std::to_string(status_fd), //status_fd是管道的写入端,应该是让子进程写数据的 path, }; if (retry_count > 0) { cmd->push_back("retry"); } return 0; }
从上面可以看到,具体命令的执行是由脚本解释器完成的
脚本解释器在升级包的
/META-INF/com/google/android/update-binary,代码位于
bootable/recovery/updater/可以单编这个模块,生成updater可执行文件,再重命名push到升级包中
可能有人会问,命令都由update-binary完成了,为什么在
try_update_binary中仍然有对命令的处理逻辑,这是因为update-binary只能处理一些简单的命令,复杂的命令无法完成,比如在屏幕上打印log的
ui_print等,这就需要recovery进程来处理
3.升级后的扫尾工作
待续……三、总结
如果嫌update-binary提供的命令不太好用,可以自己添加额外的命令,在bootable/recovery/updater/install.cpp添加额外命令,并且在
RegisterInstallFunctions中注册一下即可
OTA模块基本上没有什么问题,更多的工作是如何利用升级达到我们需要实现的功能,以及给其它模块擦屁股:)
相关文章推荐
- Android 8.0 OTA 分析——recovery
- Android 8.0 OTA 分析——recovery
- Android 8.0 OTA 分析——recovery
- Android 8.0 OTA 分析——recovery
- Android 8.0 OTA 分析——recovery
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android OTA升级原理和流程分析(五)---update.zip包从上层进入Recovery服务
- Android OTA升级原理和流程分析(七)---Recovery服务的核心install_package函数
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android OTA升级原理和流程分析(五)---update.zip包从上层进入Recovery服务
- Android OTA升级原理和流程分析(六)---Recovery服务流程细节
- Android OTA升级原理和流程分析(六)---Recovery服务流程细节
- Android OTA升级原理和流程分析(六)---Recovery服务流程细节
- Android OTA升级原理和流程分析(七)---Recovery服务的核心install_package函数
- Android OTA升级原理和流程分析(七)---Recovery服务的核心install_package函数
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android 8.0系统源码分析--相机createCaptureSession创建过程源码分析
- Android Recovery 的流程分析
- Android系统Recovery工作原理之使用update.zip升级过程分析(四)---Android系统Recovery模式的工作