文章目录
1. Recovery介绍
1.进入方式:reset + vol-, 或者根据各个平台而定
2.用途:
2.1恢复系统到出厂模式,即擦除用户数据和缓存数据
2.2系统升级
系统升级大体来说包含两种方式:1.在线升级,利用网络,系统自动连接服务区下载ota升级包;2.离线升级,将下载到的ota包放在指定位置,然后进入升级模式。每次启动,flash_image程序会检查recovery分区中的image的header。如果与备份的recovery.img不符就会把备份写道recovery分区。出于安全方面的考虑,ota是有签名的,recovery对签名是有要求的。如果想要破解的话就是更换一个不检查签名的recovery的程序
3.结构
从架构上面来说recovery分为三个部分
1.main system:用boot.img驱动linux系统,这种属于android启动的正常工作模式。
2.recovery:用recovery.img启动的linux系统,主要是运行recovery程序
3.bootloader:除了加载/启动系统,还会通过读取flash的misc分区来获得main system和recover的消息,根据读取结果觉得何种操作。
2. Framework recovery 分析
Framework层中的Recovery做的事情比较少,主要作用就是写命令,然后重启。
2.1 Framework Recovery流程
2.2 Framework Recovery 分析
android/framework/base/core/java/android/os/RecoverySystem.java通过浏览的方式选择好升级包之后就会调用下面的函数:
public static void installPackage(Context context, File packageFile)
throws IOException {
String filename = packageFile.getCanonicalPath(); //获取升级包的路径及名称。这里面在升级时,如果升级包是放在sd卡中,获取到的地址是/storage。会导致后面异常提示找不到文件
FileWriter uncryptFile = new FileWriter(UNCRYPT_FILE);
try {
uncryptFile.write(filename + "\n");
} finally {
uncryptFile.close();
}
Log.w(TAG, "devin !!! REBOOTING TO INSTALL " + "sdcard" + " !!!");
// If the package is on the /data partition, write the block map file
// into COMMAND_FILE instead.
if (filename.startsWith("/data/")) {//如果升级包是放在data目录下面,则将文件地址转化为下面的值,也可能导致文件找不到异常
filename = "@/cache/recovery/block.map";
}
//填写参数
final String filenameArg = "--update_package=" + filename;
final String localeArg = "--locale=" + Locale.getDefault().toString();
//这个函数比较重要下面分析一下
bootCommand(context, filenameArg, localeArg);
}
将上述的参数写入到文件中/cache/recovery/command,然后调用powermanager重启系统
private static void bootCommand(Context context, String... args) throws IOException {
RECOVERY_DIR.mkdirs(); //创建文件目录/cache/recovery
COMMAND_FILE.delete(); // 删除之前的/cache/recovery/command文件。这个文件中包含了recovery需要的参数
LOG_FILE.delete();//删除log文件
FileWriter command = new FileWriter(COMMAND_FILE);//创建新的command文件
try {
for (String arg : args) {
if (!TextUtils.isEmpty(arg)) {
command.write(arg);
command.write("\n");
}
}
} finally {
command.close();
}
// Having written the command file, go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);//文件写完之后,调用powermanager重启
throw new IOException("Reboot failed (no permissions?)");
}
通过powermanager最终会调用到PowerManagerService中的reboot函数
PowerManager.java
public void reboot(String reason) {
try {
mService.reboot(false, reason, true);
} catch (RemoteException e) {
}
}
PowerManagerService.java
public void reboot(boolean confirm, String reason, boolean wait) {
//检查权限代码省略
try {
shutdownOrRebootInternal(false, confirm, reason, wait);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,final String reason, boolean wait) {
//其它检查错误的代码省略
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
if (shutdown) {
ShutdownThread.shutdown(mContext, confirm);
} else {//因为是reboot,因此会走这个分支
ShutdownThread.reboot(mContext, reason, confirm);
}
}
}
};
}
ShutdownThread.java
public static void reboot(final Context context, String reason, boolean confirm) {
shutdownInner(context, confirm);
}
static void shutdownInner(final Context context, boolean confirm) {
//这个函数里面处理了一些ui方面的东东,主要的就是下面的函数
beginShutdownSequence(context);
}
在这里建立一个ShutdownThread的实例
private static final ShutdownThread sInstance = new ShutdownThread();
private static void beginShutdownSequence(Context context) {
// start the thread that initiates shutdown
sInstance.mHandler = new Handler() {//给上面的sInstance创建一个handler用来起线程
};
sInstance.start();然后就执行这个线程
}
public void run() {
···
rebootOrShutdown(mContext, mReboot, mRebootReason);
}
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
deviceRebootOrShutdown(reboot, reason);
if (reboot) {//调用PowerManagerService中的接口进入recovery
PowerManagerService.lowLevelReboot(reason);
Log.e(TAG, "Reboot failed, will attempt shutdown instead");
} else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
}
// Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
PowerManagerService.lowLevelShutdown();
}
PowerManagerService.java
public static void lowLevelReboot(String reason) {
if (reason.equals(PowerManager.REBOOT_RECOVERY)) {//进入recovery
// If we are rebooting to go into recovery, instead of
// setting sys.powerctl directly we'll start the
// pre-recovery service which will do some preparation for
// recovery and then reboot for us.
SystemProperties.set("ctl.start", "pre-recovery");//调用init.rc中注册的service
} else {//直接重启
SystemProperties.set("sys.powerctl", "reboot," + reason);
}
}
至此在framework中的出厂设置流程就走完了。
接下来就是运行init.rc中的service
首先看一下pre-recovery这个servce是怎么写的
service pre-recovery /system/bin/uncrypt --reboot
class main
disabled
oneshot
uncrypt.cpp
int main(int argc, char** argv) {
if (argc != 3 && argc != 1 && (argc == 2 && strcmp(argv[1], "--reboot") != 0)) {
fprintf(stderr, "usage: %s [--reboot] [<transform_path> <map_file>]\n", argv[0]);
return 2;
}
// When uncrypt is started with "--reboot", it wipes misc and reboots.
// Otherwise it uncrypts the package and writes the block map.
if (argc == 2) {
if (read_fstab() == NULL) {
return 1;
}
wipe_misc();//这里面就是擦除/misc分区
reboot_to_recovery();
} else {
}
return 0;
}
启动另外一个服务
static void reboot_to_recovery() {
ALOGI("rebooting to recovery");
property_set("sys.powerctl", "reboot,recovery");
sleep(10);
}
init.rc
on property:sys.powerctl=*
powerctl ${sys.powerctl}
根据android/system/core/init/keywords.h中的定义
KEYWORD(powerctl, COMMAND, 1, do_powerctl)会调用 do_powerctl这个函数
android/system/core/init/builtins.cpp
int do_powerctl(int nargs, char **args)
{
res = expand_props(command, args[1], sizeof(command));
if (strncmp(command, "shutdown", 8) == 0) {
cmd = ANDROID_RB_POWEROFF;
len = 8;
} else if (strncmp(command, "reboot", 6) == 0) {//走这个分支
cmd = ANDROID_RB_RESTART2;
len = 6;
} else {
ERROR("powerctl: unrecognized command '%s'\n", command);
return -EINVAL;
}
return android_reboot(cmd, 0, reboot_target);
android/system/core/libcutils/android_reboot.c
int android_reboot(int cmd, int flags UNUSED, const char *arg)
{
int ret;
sync();
remount_ro();
switch (cmd) {
case ANDROID_RB_RESTART:
ret = reboot(RB_AUTOBOOT);
break;
case ANDROID_RB_POWEROFF:
ret = reboot(RB_POWER_OFF);
break;
case ANDROID_RB_RESTART2:
ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, arg);
break;
default:
ret = -1;
}
return ret;
}
kernel/kernel/sys.c
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,void __user *, arg){
case LINUX_REBOOT_CMD_RESTART2:
if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {
ret = -EFAULT;
break;
}
buffer[sizeof(buffer) - 1] = '\0';
kernel_restart(buffer);
break;
}
void kernel_restart(char *cmd)
{
kernel_restart_prepare(cmd);
migrate_to_reboot_cpu();
syscore_shutdown();
if (!cmd)
printk(KERN_EMERG "Restarting system.\n");
else
printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd);
kmsg_dump(KMSG_DUMP_RESTART);
machine_restart(cmd);//recovery
}
void machine_restart(char *cmd)
{
preempt_disable();
smp_send_stop();
/* Flush the console to make sure all the relevant messages make it
* out to the console drivers */
arm_machine_flush_console();
arm_pm_restart(reboot_mode, cmd);
/* Give a grace period for failure to restart of 1s */
mdelay(1000);
/* Whoops - the platform was unable to reboot. Tell the user! */
printk("Reboot failed -- System halted\n");
local_irq_disable();
while (1);
}
看一下arm_pm_restart这个函数是如何实现的,搜索发现这个函数是msm_restart_probe注册进来的
static int msm_restart_probe(struct platform_device *pdev)
{
...
pm_power_off = do_msm_poweroff;
arm_pm_restart = do_msm_restart;
...
}
static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
{
...
msm_restart_prepare(cmd);
...
}
static void msm_restart_prepare(const char *cmd)
{
if (cmd != NULL) {
if (!strncmp(cmd, "bootloader", 10)) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_BOOTLOADER);
__raw_writel(0x77665500, restart_reason);
} else if (!strncmp(cmd, "recovery", 8)) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_RECOVERY);
__raw_writel(0x77665502, restart_reason);//走这个分支,向这个地址写入“recovery”
} else if (!strcmp(cmd, "rtc")) {
} else if (!strncmp(cmd, "oem-", 4)) {
} else if (!strncmp(cmd, "edl", 3)) {
#ifdef CONFIG_REBOOT_POWEROFF
} else if (!strcmp(cmd, "chargemode")) {
} else if (!strcmp(cmd, "hotbatt")) {
#endif
} else {
}
}
}
开机之后的lk阶段会判断这个地址的值,从而进入recovery模式。
3. Recovery分析
3.1 Recovery流程
3.2 Recovery源码分析
系统重启之后最终会调用到android/bootable/recovery/recovery.cpp。
int main(int argc, char **argv) {
load_volume_table();//加载分区列表/etc/recovery.fstab,这个分区表是之前就写好的,放在device/qcom/msm8952_64/recovery.fstab
get_args(&argc, &argv);//读取/cache/recovery/command中的指令(上面framework写进去的),填写bootloader信息
主要的函数是这个
install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required);
Device::BuiltinAction temp = prompt_and_wait(device, status);//这个就是在升级完img之后,进入到recovery界面
之后安装成功,复制升级时输出的log信息到 cache/recovery目录
}
static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount)
{
MemMapping map;
if (sysMapFile(path, &map) != 0) {
LOGE("failed to map file\n");
return INSTALL_CORRUPT;
}
int numKeys;
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
int err;
err = verify_file(map.addr, map.length, loadedKeys, numKeys);//验证升级包的完整性
free(loadedKeys);
ZipArchive zip;
err = mzOpenZipArchive(map.addr, map.length, &zip);//解析压缩包
int result = try_update_binary(path, &zip, wipe_cache);
return result;
}
//这函数的主要作用就是开启一个新的线程去做真正的升级,在这个函数里面会监听得到的消息,用来更新ui或者log信息。
static int try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);//将压缩包读到fd中,通过代码分析,它是将升级文件放到/tmp/update_binary目录下面
下面就是开启一个新的线程去写数据升级系统
pid_t pid = fork();
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(binary, (char* const*)args);//这里面的binary就是下面将要介绍的updater
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
_exit(-1);
}
}
真正执行升级系统的程序是updater。文件路径为:
android/bootable/recovery/updater/updater.c
这个文件的主要作用就是解析压缩包中的META-INF/com/google/android/updater-script文件
在同级目录下面的Makefile中有下列语句:
updater_src_files := \
install.c \
blockimg.c \
updater.c
LOCAL_MODULE := updater//编译生成的文件名称
在android/buildscripts/build_android.sh 文件中有下面语句:
cp out/target/product/msm8952_64/obj_arm/EXECUTABLES/updater_intermediates/updater $EMMC_FILES_FOLDER/UpdateFactoryRom/META-INF/com/google/android/update-binary
cp $PRODUCT_DEVICE/recovery/patch_script/UpdateFactoryRom/updater-script $EMMC_FILES_FOLDER/UpdateFactoryRom/META-INF/com/google/android/updater-script
4. 升级的执行者 Updater分析
看到在最后的时候将中间生成的updater、updater-script拷贝到META-INF/com/google/android/中,并且重命名。
updater.c分析
int main(int argc, char** argv) {
...
int fd = atoi(argv[2]);
FILE* cmd_pipe = fdopen(fd, "wb");
setlinebuf(cmd_pipe);
// Extract the script from the package.
const char* package_filename = argv[3];
printf(“ package_filename: %s\n", package_filename);//sdcard/update.zip
MemMapping map;
//映射文件到内存当中
if (sysMapFile(package_filename, &map) != 0) {
}
//将映射到内存的文件放入到za结构当中
err = mzOpenZipArchive(map.addr, map.length, &za);
}
const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
char* script = malloc(script_entry->uncompLen+1);
if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
printf("failed to read script from package\n");
return 5;
}
script[script_entry->uncompLen] = '\0';
// Configure edify's functions.
//注册函数,用来解析updater-script
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions();
RegisterDeviceExtensions();
FinishRegistration();//重新排序
// Parse the script.
//将脚本中的内容放入到root中
int error = parse_string(script, &root, &error_count);
// Evaluate the parsed script.
char* result = Evaluate(&state, root);// 在这个函数里面调用刚才注册进来的函数。也就是在这里进行真正的升级
if (result == NULL) {//升级失败
} else {//升级成功
}
....
}
5. OTA 目录结构
解压一个升级包,看到他的目录结构为:
其中在patch/system中存放的都是patch。在META_INFO中是升级程序以及验证信息。
在上面提到的script就是updater-script脚本。这个脚本大体分为三个部分:
1.验证patch的有效性
2.打patch
3.更改连接和文件的属性
在第2步打patch中最终会调用bootable/recovery/applypatch/applypatch.c 文件中的applypatch函数。
6. 涉及到的其它内容
在recovery中涉及到了BCB(Bootloader Control Block),BCB是bootloader和recovery通讯的接口,也是boot loader与MainSystem之间的通讯接口。存储在flash中的MISC分区中,占用3个page。BCB结构为:
/* Recovery Message */
struct recovery_message {
char command[32];
char status[32];
char recovery[1024];
};